Revert "Revert "Merge branch 'euitables-ruleset-1829' of https://github.com/wazuh/wazuh-kibana-app into 3.12-7.4""

This reverts commit 79d029fb9a.
This commit is contained in:
JuanCarlos 2019-11-22 12:29:47 +01:00
parent 79d029fb9a
commit bc657bf2b5
No known key found for this signature in database
GPG Key ID: B1C4FB733616273A
69 changed files with 6515 additions and 1246 deletions

4
.gitignore vendored
View File

@ -64,4 +64,6 @@ package-lock.json
build/ build/
yarn.lock yarn.lock
server/wazuh-registry.json

View File

@ -38,6 +38,7 @@
"angular-chart.js": "1.1.1", "angular-chart.js": "1.1.1",
"angular-cookies": "1.6.5", "angular-cookies": "1.6.5",
"angular-material": "1.1.18", "angular-material": "1.1.18",
"axios": "^0.19.0",
"babel-polyfill": "^6.13.0", "babel-polyfill": "^6.13.0",
"dom-to-image": "^2.6.0", "dom-to-image": "^2.6.0",
"install": "^0.10.1", "install": "^0.10.1",
@ -48,6 +49,9 @@
"pdfmake": "^0.1.37", "pdfmake": "^0.1.37",
"pug-loader": "^2.4.0", "pug-loader": "^2.4.0",
"querystring-browser": "1.0.4", "querystring-browser": "1.0.4",
"react-redux": "^7.1.1",
"react-codemirror": "^1.0.0",
"redux": "^4.0.4",
"simple-tail": "^1.1.0", "simple-tail": "^1.1.0",
"timsort": "^0.3.0", "timsort": "^0.3.0",
"winston": "3.0.0" "winston": "3.0.0"

View File

@ -7,4 +7,4 @@
#wazuh-app > div > div.euiComboBoxOptionsList { #wazuh-app > div > div.euiComboBoxOptionsList {
width: 25%!important; width: 25%!important;
} }

View File

@ -0,0 +1,120 @@
/*
* Wazuh app - React component for show search and filter in the rules,decoder and CDB lists.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import PropTypes, {InferProps} from 'prop-types';
import {
EuiSearchBar,
EuiButtonEmpty,
EuiFormRow,
EuiPopover,
EuiButton,
EuiFlexItem,
EuiFlexGroup,
} from '@elastic/eui';
import { filter } from 'bluebird';
interface filter {
label: string,
value: string,
}
export default class WzSearchBarFilter extends Component {
state: {
isPopoverOpen: boolean,
query: string,
}
props!: {
filters: filter[]
}
constructor(props) {
super(props);
this.state = {
isPopoverOpen: false,
query: '',
}
}
closePopover(): void {
this.setState({ isPopoverOpen: false });
}
renderPopOver(): JSX.Element {
const { query } = this.state;
const button = (
<EuiButton
fill
style={{ padding: 12, borderTopRightRadius: 0, borderBottomRightRadius: 0 }}
color='primary'
onClick={() => {this.setState({ isPopoverOpen:true })}}
iconType="logstashFilter"
aria-label="Filter">
Filters
</EuiButton>
);
return (
<EuiPopover
id="trapFocus"
ownFocus
button={button}
isOpen={this.state.isPopoverOpen}
anchorPosition="downRight"
closePopover={this.closePopover.bind(this)}>
{this.props.filters.map((filter, idx) => (
<div key={idx}>
<EuiButtonEmpty size="s"
iconSide='right'
// TODO: Add the logic to applyFilters
onClick={() => this.setState({query:`${query} ${filter.value}:`})}>
{filter.label}
</EuiButtonEmpty>
</div>
)
)
}
</EuiPopover>
)
}
renderSearchBar(): JSX.Element {
const { query } = this.state
return (
<EuiFormRow
className="wz-form-row"
isInvalid={false}
error={"Gola"}
>
<EuiSearchBar
onChange={() => {}}
query={query} />
</EuiFormRow>
);
}
render() {
const popOver = this.props.filters ? this.renderPopOver(): null;
const searchBar = this.renderSearchBar();
return (
<EuiFlexGroup>
<EuiFlexItem grow={false} style={{marginRight: 0}}>
{popOver}
</EuiFlexItem>
<EuiFlexItem style={{marginLeft: 0}}>
{searchBar}
</EuiFlexItem>
</EuiFlexGroup>
)
}
}
WzSearchBarFilter.propTypes = {
filters: PropTypes.array,
}

View File

@ -105,7 +105,6 @@ export class AgentsPreviewController {
getCurrentApiAddress: () => this.getCurrentApiAddress(), getCurrentApiAddress: () => this.getCurrentApiAddress(),
needsPassword: () => this.needsPassword() needsPassword: () => this.needsPassword()
}; };
this.hasAgents = true; this.hasAgents = true;
this.init = false; this.init = false;
const instance = new DataFactory( const instance = new DataFactory(

View File

@ -115,14 +115,15 @@ export class ExportConfiguration extends Component {
render() { render() {
const button = ( const button = (
<EuiButton <EuiButtonEmpty
iconType="importAction" iconType="importAction"
iconSide="left" iconSide="left"
size="s" size="s"
style={{ marginTop: '4px' }}
onClick={this.exportClick.bind(this)} onClick={this.exportClick.bind(this)}
> >
PDF Export PDF
</EuiButton> </EuiButtonEmpty>
); );
return ( return (
<EuiPopover <EuiPopover

View File

@ -0,0 +1,274 @@
/*
* Wazuh app - React component for building the groups table.
*
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import {
EuiInMemoryTable,
EuiButtonIcon,
EuiFlexItem,
EuiFlexGroup,
EuiPanel,
EuiTitle,
EuiButtonEmpty,
EuiText,
EuiToolTip
} from '@elastic/eui';
import { ExportConfiguration } from '../../agent/components/export-configuration';
export class AgentsInGroupTable extends Component {
constructor(props) {
super(props);
this.state = {
groupName: this.props.group.name || 'Group',
agents: [],
originalAgents: [],
isLoading: false,
};
this.filters = { name: 'search', value: '' };
}
async componentDidMount() {
try {
const agents = await this.props.getAgentsByGroup(this.props.group.name);
this.setState({
agents: agents,
originalAgents: agents
});
} catch (error) {
console.error('error mounting the component ', error)
}
}
onQueryChange = ({ query }) => {
if (query) {
this.setState({ isLoading: true });
const filter = query.text || "";
this.filters.value = filter;
const items = filter
? this.state.originalAgents.filter(item => {
return item.name.toLowerCase().includes(filter.toLowerCase());
})
: this.state.originalAgents;
this.setState({
isLoading: false,
agents: items,
});
}
};
/**
* Refresh the agents
*/
async refresh() {
try {
this.setState({ refreshingAgents: true });
const agents = await this.props.getAgentsByGroup(this.props.group.name);
this.setState({
originalAgents: agents,
refreshingAgents: false
});
} catch (error) {
this.setState({ refreshingAgents: false });
console.error('error refreshing agents ', error)
}
}
showConfirm(groupName) {
this.setState({
showConfirm: groupName
});
}
render() {
const columns = [
{
field: 'id',
name: 'ID',
sortable: true
},
{
field: 'name',
name: 'Name',
sortable: true
},
{
field: 'ip',
name: 'IP'
},
{
field: 'status',
name: 'Status',
sortable: true
},
{
field: 'os.name',
name: 'OS name',
sortable: true
},
{
field: 'os.version',
name: 'OS version',
sortable: true
},
{
field: 'version',
name: 'Version',
sortable: true
},
{
name: 'Actions',
render: item => {
return (
<Fragment>
<EuiFlexGroup>
{this.state.showConfirm !== item.name &&
item.name !== 'default' && (
<Fragment>
<EuiFlexItem grow={false}>
<EuiToolTip
position="right"
content="Go to the agent"
>
<EuiButtonIcon
aria-label="View agent details"
onClick={() => this.props.goToAgent(item)}
iconType="eye"
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
position="right"
content="Delete agent"
>
<EuiButtonIcon
aria-label="Delete groups"
onClick={() => this.showConfirm(item.name)}
iconType="trash"
color="danger"
/>
</EuiToolTip>
</EuiFlexItem>
</Fragment>
)}
{this.state.showConfirm === item.name && (
<EuiFlexItem grow={true}>
<EuiText>
<p>
Are you sure you want to delete the {item.name} agent?
<EuiButtonEmpty onClick={() => this.showConfirm(false)}>
No
</EuiButtonEmpty>
<EuiButtonEmpty
onClick={async () => {
this.showConfirm(false);
await this.props.removeAgentFromGroup(item.id, this.state.groupName);
this.refresh();
}}
color="danger"
>
Yes
</EuiButtonEmpty>
</p>
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
</Fragment>
);
}
}
];
const search = {
onChange: this.onQueryChange,
box: {
incremental: this.state.incremental,
schema: true
}
};
return (
<EuiPanel paddingSize="l" className='wz-margin-16'>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle>
<h2>{this.state.groupName}</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconSide="left"
iconType="folderClosed"
onClick={() => this.props.addAgents()}
>
Manage agents
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ExportConfiguration
exportConfiguration={enabledComponents => this.props.exportConfigurationProps.exportConfiguration(enabledComponents)}
type={this.props.exportConfigurationProps.type} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="importAction"
onClick={async () => await this.props.export(this.state.groupName, [this.filters])}
>
Export formatted
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="refresh" onClick={() => this.refresh()}>
Refresh
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiText color="subdued" style={{ paddingBottom: '15px' }}>
From here you can list and manage your agents
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable
itemId="id"
items={this.state.agents}
columns={columns}
search={search}
pagination={true}
loading={this.state.refreshingAgents || this.state.isLoading}
/>
</EuiPanel>
);
}
}
AgentsInGroupTable.propTypes = {
group: PropTypes.object,
getAgentsByGroup: PropTypes.func,
addAgents: PropTypes.func,
export: PropTypes.func,
removeAgentFromGroup: PropTypes.func,
goToAgent: PropTypes.func,
exportConfigurationProps: PropTypes.object
};

View File

@ -0,0 +1,197 @@
/*
* Wazuh app - React component for building the groups table.
*
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import {
EuiInMemoryTable,
EuiButtonIcon,
EuiFlexItem,
EuiFlexGroup,
EuiPanel,
EuiTitle,
EuiButtonEmpty,
EuiText,
EuiToolTip
} from '@elastic/eui';
import { ExportConfiguration } from '../../agent/components/export-configuration';
export class FilesInGroupTable extends Component {
constructor(props) {
super(props);
this.state = {
groupName: this.props.group.name || 'Group',
files: [],
originalfiles: [],
isLoading: false,
};
this.filters = { name: 'search', value: '' };
}
async componentDidMount() {
try {
const files = await this.props.getFilesFromGroup(this.props.group.name);
this.setState({
files: files,
originalfiles: files
});
} catch (error) {
console.error('error mounting the component ', error)
}
}
onQueryChange = ({ query }) => {
if (query) {
this.setState({ isLoading: true });
const filter = query.text || "";
this.filters.value = filter;
const items = filter
? this.state.originalfiles.filter(item => {
return item.filename.toLowerCase().includes(filter.toLowerCase());
})
: this.state.originalfiles;
this.setState({
isLoading: false,
files: items,
});
}
};
/**
* Refresh the agents
*/
async refresh() {
try {
this.setState({ refreshingFiles: true });
const files = await this.props.getFilesFromGroup(this.props.group.name);
this.setState({
originalfiles: files,
refreshingFiles: false
});
} catch (error) {
this.setState({ refreshingFiles: false });
console.error('error refreshing files ', error)
}
}
render() {
const columns = [
{
field: 'filename',
name: 'File',
sortable: true
},
{
field: 'hash',
name: 'Checksum',
sortable: true
},
{
name: 'Actions',
render: item => {
return (
<EuiToolTip
position="right"
content="See file content"
>
<EuiButtonIcon
aria-label="See file content"
onClick={() => this.props.openFileContent(this.state.groupName, item.filename)}
iconType="eye"
/>
</EuiToolTip>
);
}
}
];
const search = {
onChange: this.onQueryChange,
box: {
incremental: this.state.incremental,
schema: true
}
};
return (
<EuiPanel paddingSize="l" className='wz-margin-16'>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle>
<h2>{this.state.groupName}</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconSide="left"
iconType="pencil"
onClick={() => this.props.editConfig()}
>
Edit group configuration
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<ExportConfiguration
exportConfiguration={enabledComponents => this.props.exportConfigurationProps.exportConfiguration(enabledComponents)}
type={this.props.exportConfigurationProps.type} />
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="importAction"
onClick={async () => await this.props.export(this.state.groupName, [this.filters])}
>
Export formatted
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="refresh" onClick={() => this.refresh()}>
Refresh
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiText color="subdued" style={{ paddingBottom: '15px' }}>
From here you can list and see your group files, also, you can edit the group configuration
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable
itemId="id"
items={this.state.files}
columns={columns}
search={search}
pagination={true}
loading={this.state.refreshingFiles || this.state.isLoading}
/>
</EuiPanel>
);
}
}
FilesInGroupTable.propTypes = {
group: PropTypes.object,
getFilesFromGroup: PropTypes.func,
export: PropTypes.func,
exportConfigurationProps: PropTypes.object,
editConfig: PropTypes.func,
openFileContent: PropTypes.func
};

View File

@ -0,0 +1,395 @@
/*
* Wazuh app - React component for building the groups table.
*
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component, Fragment } from 'react';
import PropTypes from 'prop-types';
import {
EuiInMemoryTable,
EuiButtonIcon,
EuiFlexItem,
EuiFlexGroup,
EuiPanel,
EuiTitle,
EuiButtonEmpty,
EuiText,
EuiPopover,
EuiFormRow,
EuiFieldText,
EuiSpacer,
EuiButton,
EuiCallOut,
EuiToolTip,
EuiPage
} from '@elastic/eui';
export class GroupsTable extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.state = {
items: this.props.items,
originalItems: this.props.items,
pageIndex: 0,
pageSize: 10,
showPerPageOptions: true,
showConfirm: false,
newGroupName: '',
isPopoverOpen: false,
msg: false,
isLoading: false
};
this.filters = { name: 'search', value: '' };
}
/**
* Refresh the groups entries
*/
async refresh() {
try {
this.setState({ refreshingGroups: true });
await this.props.refresh();
this.setState({
originalItems: this.props.items,
refreshingGroups: false
});
} catch (error) {
this.setState({
refreshingGroups: false
});
}
}
UNSAFE_componentWillReceiveProps(nextProps) {
this.setState({
items: nextProps.items
});
}
componentDidMount() {
this._isMounted = true;
if (this._isMounted) this.bindEnterToInput();
}
componentDidUpdate() {
this.bindEnterToInput();
}
componentWillUnmount() {
this._isMounted = false;
}
/**
* Looking for the input element to bind the keypress event, once the input is found the interval is clear
*/
bindEnterToInput() {
try {
const interval = setInterval(async () => {
const input = document.getElementsByClassName('groupNameInput');
if (input.length) {
const i = input[0];
if (!i.onkeypress) {
i.onkeypress = async (e) => {
if (e.which === 13) {
await this.createGroup(this.state.newGroupName);
}
};
}
clearInterval(interval);
}
}, 150);
} catch (error) { }
}
togglePopover() {
if (this.state.isPopoverOpen) {
this.closePopover();
} else {
this.setState({ isPopoverOpen: true });
}
}
closePopover() {
this.setState({
isPopoverOpen: false,
msg: false,
newGroupName: ''
});
}
clearGroupName() {
this.setState({
newGroupName: ''
});
}
onChangeNewGroupName = e => {
this.setState({
newGroupName: e.target.value
});
};
showConfirm(groupName) {
this.setState({
showConfirm: groupName
});
}
async createGroup() {
try {
this.setState({ msg: false });
const groupName = this.state.newGroupName;
await this.props.createGroup(groupName);
this.clearGroupName();
this.refresh();
this.setState({ msg: { msg: `${groupName} created`, type: 'success' } });
} catch (error) {
this.setState({ msg: { msg: error, type: 'danger' } });
}
}
onQueryChange = ({ query }) => {
if (query) {
this.setState({ isLoading: true });
const filter = query.text || "";
this.filters.value = filter;
const items = filter
? this.state.originalItems.filter(item => {
return item.name.toLowerCase().includes(filter.toLowerCase());
})
: this.state.originalItems;
this.setState({
isLoading: false,
items: items,
});
}
};
render() {
const columns = [
{
field: 'name',
name: 'Name',
sortable: true
},
{
field: 'count',
name: 'Agents',
sortable: true
},
{
field: 'mergedSum',
name: 'Configuration checksum',
sortable: true
},
{
name: 'Actions',
render: item => {
return (
<div>
<EuiFlexGroup>
{this.state.showConfirm !== item.name && (
<Fragment>
<EuiFlexItem grow={false}>
<EuiToolTip
position="right"
content="View group details"
>
<EuiButtonIcon
aria-label="View group details"
onClick={() => this.props.goGroup(item)}
iconType="eye"
/>
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiToolTip
position="right"
content="Edit group configuration"
>
<EuiButtonIcon
aria-label="Edit group configuration"
onClick={() => this.props.editGroup(item)}
iconType="pencil"
/>
</EuiToolTip>
</EuiFlexItem>
</Fragment>
)}
{this.state.showConfirm !== item.name &&
item.name !== 'default' && (
<EuiFlexItem grow={false}>
<EuiToolTip
position="right"
content="Delete group"
>
<EuiButtonIcon
aria-label="Delete groups"
onClick={() => this.showConfirm(item.name)}
iconType="trash"
color="danger"
/>
</EuiToolTip>
</EuiFlexItem>
)}
{this.state.showConfirm === item.name && (
<EuiFlexItem grow={true}>
<EuiText>
<p>
Are you sure you want to delete this group?
<EuiButtonEmpty onClick={() => this.showConfirm(false)}>
No
</EuiButtonEmpty>
<EuiButtonEmpty
onClick={async () => {
this.showConfirm(false);
await this.props.deleteGroup(item);
this.refresh();
}}
color="danger"
>
Yes
</EuiButtonEmpty>
</p>
</EuiText>
</EuiFlexItem>
)}
</EuiFlexGroup>
</div>
);
}
}
];
const search = {
onChange: this.onQueryChange,
box: {
incremental: this.state.incremental,
schema: true
}
};
const newGroupButton = (
<EuiButtonEmpty
iconSide="left"
iconType="plusInCircle"
onClick={() => this.togglePopover()}
>
Add new groups
</EuiButtonEmpty>
);
return (
<EuiPage style={{ background: 'transparent' }}>
<EuiPanel>
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle>
<h2>Groups</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiPopover
id="popover"
button={newGroupButton}
isOpen={this.state.isPopoverOpen}
closePopover={() => this.closePopover()}
>
<EuiFormRow label="Introduce the group name" id="">
<EuiFieldText
className="groupNameInput"
value={this.state.newGroupName}
onChange={this.onChangeNewGroupName}
aria-label=""
/>
</EuiFormRow>
<EuiSpacer size="xs" />
{this.state.msg && (
<Fragment>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCallOut title={this.state.msg.msg} color={this.state.msg.type} iconType={this.state.msg.type === 'danger' ? 'cross' : 'check'} />
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="xs" />
</Fragment>
)}
<EuiSpacer size="xs" />
<EuiFlexGroup>
<EuiFlexItem>
<EuiButton
iconType="save"
fill
onClick={async () => {
await this.createGroup(this.state.newGroupName);
this.clearGroupName();
this.refresh();
}}
>
Save new group
</EuiButton>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPopover>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty
iconType="exportAction"
onClick={async () => await this.props.export([this.filters])}
>
Export formatted
</EuiButtonEmpty>
</EuiFlexItem>
<EuiFlexItem grow={false}>
<EuiButtonEmpty iconType="refresh" onClick={() => this.refresh()}>
Refresh
</EuiButtonEmpty>
</EuiFlexItem>
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiText color="subdued" style={{ paddingBottom: '15px' }}>
From here you can list and check your groups, its agents and
files.
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<EuiInMemoryTable
itemId="id"
items={this.state.items}
columns={columns}
search={search}
pagination={true}
loading={this.state.refreshingGroups || this.state.isLoading}
/>
</EuiPanel>
</EuiPage>
);
}
}
GroupsTable.propTypes = {
items: PropTypes.array,
createGroup: PropTypes.func,
goGroup: PropTypes.func,
editGroup: PropTypes.func,
export: PropTypes.func,
refresh: PropTypes.func,
deleteGroup: PropTypes.func
};

View File

@ -0,0 +1,59 @@
/*
* Wazuh app - React component for all management section.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import {
EuiFlexGroup,
EuiFlexItem
} from '@elastic/eui';
// Redux
import store from '../../../../redux/store';
import WzManagementSideMenu from './management-side-menu';
import WzRuleset from './ruleset/main-ruleset';
import { GroupsTable } from './groups/groups-table';
import { changeManagementSection } from '../../../../redux/reducers/managementReducers';
import { connect } from 'react-redux';
class WzManagementMain extends Component {
constructor(props) {
super(props);
this.state = {};
this.store = store;
}
render() {
const { section } = this.props;
const ruleset = ['ruleset', 'rules', 'decoders', 'lists']
return (
<EuiFlexGroup>
<EuiFlexItem grow={false} style={{width:190}}>
<WzManagementSideMenu section={section} {...this.props}/>
</EuiFlexItem>
<EuiFlexItem>
<div style={{margin: '12px 12px 0px 0px'}}>
{
ruleset.includes(section) && (<WzRuleset />)
}
</div>
</EuiFlexItem>
</EuiFlexGroup>
)
}
}
function mapStateToProps(state) {
return {
section: changeManagementSection(state),
};
}
export default connect(mapStateToProps, {})(WzManagementMain);

View File

@ -0,0 +1,21 @@
import React, { Component } from 'react';
// Redux
import store from '../../../../redux/store';
import WzReduxProvider from '../../../../redux/wz-redux-provider';
import WzManagementMain from '../management/management-main'
export default class WzManagement extends Component {
constructor(props) {
super(props);
this.state = {};
this.store = store;
}
render() {
return (
<WzReduxProvider>
<WzManagementMain {...this.props}/>
</WzReduxProvider>
)
}
}

View File

@ -0,0 +1,247 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import {
EuiFlexItem,
EuiButtonEmpty,
EuiSideNav,
EuiIcon
} from '@elastic/eui';
import {
updateRulesetSection,
updateLoadingStatus,
toggleShowFiles,
cleanFilters,
updateAdminMode,
updateError,
updateIsProcessing,
updatePageIndex,
updateSortDirection,
updateSortField,
cleanInfo,
} from '../../../../redux/actions/rulesetActions';
import {
updateManagementSection,
} from '../../../../redux/actions/managementActions';
import checkAdminMode from './ruleset/utils/check-admin-mode';
import { WzRequest } from '../../../../react-services/wz-request';
import { connect } from 'react-redux';
class WzManagementSideMenu extends Component {
constructor(props) {
super(props);
this.state = {
selectedItemName: this.props.section || 'ruleset'
};
this.managementSections = {
management: { id: 'management', text: 'Management' },
administration: { id: 'administration', text: 'Administration' },
ruleset: { id: 'ruleset', text: 'Ruleset' },
rules: { id: 'rules', text: 'Rules' },
decoders: { id: 'decoders', text: 'Decoders' },
lists: { id: 'lists', text: 'CDB lists' },
groups: { id: 'groups', text: 'Groups' },
configuration: { id: 'configuration', text: 'Configuration' },
statusReports: { id: 'statusReports', text: 'Status and reports' },
status: { id: 'status', text: 'Status' },
cluster: { id: 'monitoring', text: 'Cluster' },
logs: { id: 'logs', text: 'Logs' },
reporting: { id: 'reporting', text: 'Reporting' },
};
this.paths = {
rules: '/rules',
decoders: '/decoders',
lists: '/lists/files'
}
this.wzReq = WzRequest;
}
UNSAFE_componentWillReceiveProps(nextProps) {
// You don't have to do this check first, but it can help prevent an unneeded render
if (nextProps.section !== this.state.selectedItemName) {
this.setState({ selectedItemName: nextProps.section });
}
}
componentDidMount() {
// Fetch the data in the first mount
if (['rules', 'decoders', 'lists'].includes(this.state.selectedItemName)) {
this.fetchData(this.managementSections.rules.id); }
this.props.changeManagementSection(this.state.selectedItemName);
}
/**
* Fetch the data for a section: rules, decoders, lists...
* @param {String} newSection
*/
async fetchData(newSection) {
try {
const currentSection = this.props.state.section;
if (Object.keys(this.props.state.filters).length && newSection === currentSection) return; // If there's any filter and the section is de same doesn't fetch again
this.props.changeRulesetSection(newSection);
this.props.cleanInfo();
this.props.updateLoadingStatus(true);
const result = await this.wzReq.apiReq('GET', this.paths[newSection], {});
const items = result.data.data.items;
//Set the admin mode
const admin = await checkAdminMode();
this.props.updateAdminMode(admin);
this.props.toggleShowFiles(false);
this.props.changeRulesetSection(newSection);
this.props.updateLoadingStatus(false);
} catch (error) {
this.props.updateError(error);
}
}
clickMenuItem = name => {
const fromSection = this.state.selectedItemName;
let section = name;
if (this.state.selectedItemName !== section) {
this.setState({
selectedItemName: section,
});
this.props.updateSortDirection('asc');
this.props.updateSortField(section === 'rules' ? 'id' : 'name');
this.props.cleanFilters();
this.props.updateIsProcessing(true);
this.props.updatePageIndex(0);
const managementSections = ['rules', 'decoders', 'lists'];
if (managementSections.includes(section) && managementSections.includes(fromSection)) {
this.fetchData(section);
} else if (managementSections.includes(section) && !managementSections.includes(fromSection)) {
this.props.changeManagementSection('ruleset');
this.props.switchTab('rules');
this.fetchData(section);
} else if (section === 'groups' && managementSections.includes(fromSection)) {
this.props.changeManagementSection('groups');
} else {
if(section === 'cluster'){
section = 'monitoring';
}
this.props.changeManagementSection(section);
this.props.switchTab(section);
}
}
};
createItem = (item, data = {}) => {
// NOTE: Duplicate `name` values will cause `id` collisions.
return {
...data,
id: item.id,
name: item.text,
isSelected: this.state.selectedItemName === item.id,
onClick: () => this.clickMenuItem(item.id),
};
};
render() {
const sideNavAdmin = [
this.createItem(this.managementSections.administration, {
disabled: true,
icon: <EuiIcon type="managementApp" />,
items: [
this.createItem(this.managementSections.ruleset, {
disabled: true,
icon: <EuiIcon type="indexRollupApp" />,
forceOpen: true,
items: [
this.createItem(this.managementSections.rules),
this.createItem(this.managementSections.decoders),
this.createItem(this.managementSections.lists),
],
}),
this.createItem(this.managementSections.groups, {
icon: <EuiIcon type="spacesApp" />,
}),
this.createItem(this.managementSections.configuration, {
icon: <EuiIcon type="devToolsApp" />,
})
],
})
];
const sideNavStatus = [
this.createItem(this.managementSections.statusReports, {
disabled: true,
icon: <EuiIcon type="indexSettings" />,
items: [
this.createItem(this.managementSections.status, {
icon: <EuiIcon type="uptimeApp" />,
}),
this.createItem(this.managementSections.cluster, {
icon: <EuiIcon type="packetbeatApp" />,
}),
this.createItem(this.managementSections.logs, {
icon: <EuiIcon type="filebeatApp" />,
}),
this.createItem(this.managementSections.reporting, {
icon: <EuiIcon type="reportingApp" />,
})
],
})
];
return (
<div style={{ position: 'fixed' }} >
<EuiFlexItem grow={false}>
<EuiButtonEmpty
style={{ margin: "0px 6px" }}
size="s"
onClick={() => this.props.switchTab('welcome')}
iconType="arrowLeft"
className={'sideMenuButton'}>
Management
</EuiButtonEmpty>
</EuiFlexItem>
<EuiSideNav
items={sideNavAdmin}
style={{ padding: 16 }}
/>
<EuiSideNav
items={sideNavStatus}
style={{ padding: 16 }}
/>
</div>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
const mapDispatchToProps = (dispatch) => {
return {
changeRulesetSection: section => dispatch(updateRulesetSection(section)),
updateLoadingStatus: status => dispatch(updateLoadingStatus(status)),
toggleShowFiles: status => dispatch(toggleShowFiles(status)),
cleanFilters: () => dispatch(cleanFilters()),
updateAdminMode: status => dispatch(updateAdminMode(status)),
updateError: error => dispatch(updateError(error)),
updateIsProcessing: isPorcessing => dispatch(updateIsProcessing(isPorcessing)),
updatePageIndex: pageIndex => dispatch(updatePageIndex(pageIndex)),
updateSortDirection: sortDirection => dispatch(updateSortDirection(sortDirection)),
updateSortField: sortField => dispatch(updateSortField(sortField)),
changeManagementSection: section => dispatch(updateManagementSection(section)),
cleanInfo: () => dispatch(cleanInfo()),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(WzManagementSideMenu);

View File

@ -0,0 +1,268 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component, Fragment } from 'react';
// Eui components
import {
EuiFlexItem,
EuiButtonEmpty,
EuiGlobalToastList
} from '@elastic/eui';
import { connect } from 'react-redux';
import {
toggleShowFiles,
updateLoadingStatus,
updteAddingRulesetFile,
updateListContent,
updateIsProcessing,
updatePageIndex,
} from '../../../../../redux/actions/rulesetActions';
import { WzRequest } from '../../../../../react-services/wz-request';
import exportCsv from '../../../../../react-services/wz-csv';
import { UploadFiles } from '../../upload-files';
import columns from './utils/columns';
import RulesetHandler from './utils/ruleset-handler';
class WzRulesetActionButtons extends Component {
constructor(props) {
super(props);
this.state = { generatingCsv: false };
this.exportCsv = exportCsv;
this.wzReq = WzRequest;
this.paths = {
rules: '/rules',
decoders: '/decoders',
lists: '/lists/files'
}
this.columns = columns;
this.rulesetHandler = RulesetHandler;
this.refreshTimeoutId = null;
}
/**
* Generates a CSV
*/
async generateCsv() {
try {
this.setState({ generatingCsv: true });
const { section, filters } = this.props.state; //TODO get filters from the search bar from the REDUX store
await this.exportCsv(`/${section}`, filters, section);
} catch (error) {
console.error('Error exporting as CSV ', error);
}
this.setState({ generatingCsv: false });
}
/**
* Uploads the files
* @param {Array} files
* @param {String} path
*/
async uploadFiles(files, path) {
try {
let errors = false;
let results = [];
let upload;
if (path === 'etc/rules') {
upload = this.rulesetHandler.sendRuleConfiguration;
} else if (path === 'etc/decoders') {
upload = this.rulesetHandler.sendDecoderConfiguration;
} else {
upload = this.rulesetHandler.sendCdbList;
}
for (let idx in files) {
const { file, content } = files[idx];
try {
await upload(file, content, true); // True does not overwrite the file
results.push({
index: idx,
uploaded: true,
file: file,
error: 0
});
} catch (error) {
console.error('ERROR FILE ONLY ONE ', error)
errors = true;
results.push({
index: idx,
uploaded: false,
file: file,
error: error
});
}
}
if (errors) throw results;
//this.errorHandler.info('Upload successful');
console.log('UPLOAD SUCCESS');
return;
} catch (error) {
if (Array.isArray(error) && error.length) return Promise.reject(error);
console.error('Errors uploading several files ', error);
//TODO handle the erros
//this.errorHandler.handle('Files cannot be uploaded');
}
}
/**
* Toggle between files and rules or decoders
*/
async toggleFiles() {
try {
this.props.updateLoadingStatus(true);
const { showingFiles, } = this.props.state;
this.props.toggleShowFiles(!showingFiles);
this.props.updateIsProcessing(true);
this.props.updatePageIndex(0);
this.props.updateLoadingStatus(false);
} catch (error) {
console.error('error toggling ', error)
}
}
/**
* Refresh the items
*/
async refresh() {
try {
this.props.updateIsProcessing(true);
this.onRefreshLoading();
} catch (error) {
return Promise.reject(error);
}
}
onRefreshLoading() {
clearInterval(this.refreshTimeoutId);
this.props.updateLoadingStatus(true);
this.refreshTimeoutId = setInterval(() => {
if(!this.props.state.isProcessing) {
this.props.updateLoadingStatus(false);
clearInterval(this.refreshTimeoutId);
}
}, 100);
}
render() {
const { section, showingFiles, adminMode } = this.props.state;
// Export button
const exportButton = (
<EuiButtonEmpty
iconType="exportAction"
onClick={async () => await this.generateCsv()}
isLoading={this.state.generatingCsv}
>
Export formatted
</EuiButtonEmpty>
);
// Add new rule button
const addNewRuleButton = (
<EuiButtonEmpty
iconType="plusInCircle"
onClick={() => this.props.updteAddingRulesetFile({ name: '', content: '<!-- Modify it at your will. -->', path: `etc/${section}` })}
>
{`Add new ${section} file`}
</EuiButtonEmpty>
);
//Add new CDB list button
const addNewCdbListButton = (
<EuiButtonEmpty
iconType="plusInCircle"
onClick={() => this.props.updateListContent({ name: false, content: '', path: 'etc/lists' })}
>
{`Add new ${section} file`}
</EuiButtonEmpty>
);
// Manage files
const manageFiles = (
<EuiButtonEmpty
iconType={showingFiles ? 'apmTrace' : 'folderClosed'}
onClick={async () => await this.toggleFiles()}
>
{showingFiles ? `Manage ${section}` : `Manage ${section} files`}
</EuiButtonEmpty>
);
// Refresh
const refresh = (
<EuiButtonEmpty
iconType="refresh"
onClick={async () => await this.refresh()}
>
Refresh
</EuiButtonEmpty>
);
return (
<Fragment>
{(section !== 'lists' && adminMode) && (
<EuiFlexItem grow={false}>
{manageFiles}
</EuiFlexItem>
)
}
{(adminMode && section !== 'lists') && (
<EuiFlexItem grow={false}>
{addNewRuleButton}
</EuiFlexItem>
)}
{(adminMode && section === 'lists') && (
<EuiFlexItem grow={false}>
{addNewCdbListButton}
</EuiFlexItem>
)}
{((section === 'lists' || showingFiles) && adminMode) && (
<EuiFlexItem grow={false}>
<UploadFiles
msg={section}
path={`etc/${section}`}
upload={async (files, path) => await this.uploadFiles(files, path)} />
</EuiFlexItem>
)}
<EuiFlexItem grow={false}>
{exportButton}
</EuiFlexItem>
<EuiFlexItem grow={false}>
{refresh}
</EuiFlexItem>
</Fragment>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
const mapDispatchToProps = (dispatch) => {
return {
toggleShowFiles: status => dispatch(toggleShowFiles(status)),
updateLoadingStatus: status => dispatch(updateLoadingStatus(status)),
updteAddingRulesetFile: content => dispatch(updteAddingRulesetFile(content)),
updateListContent: content => dispatch(updateListContent(content)),
updateIsProcessing: isProcessing => dispatch(updateIsProcessing(isProcessing)),
updatePageIndex: pageIndex => dispatch(updatePageIndex(pageIndex)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(WzRulesetActionButtons);

View File

@ -0,0 +1,320 @@
import React, { Component, Fragment } from 'react';
// Eui components
import {
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiPage,
EuiButtonIcon,
EuiTitle,
EuiToolTip,
EuiText,
EuiSpacer,
EuiInMemoryTable,
EuiLink
} from '@elastic/eui';
import { connect } from 'react-redux';
import RulesetHandler from './utils/ruleset-handler';
import { colors } from './utils/colors';
import {
updateFileContent,
cleanFileContent,
cleanInfo,
updateFilters,
cleanFilters
} from '../../../../../redux/actions/rulesetActions';
class WzDecoderInfo extends Component {
constructor(props) {
super(props);
this.rulesetHandler = RulesetHandler;
this.columns = [
{
field: 'name',
name: 'Name',
align: 'left',
sortable: true,
render: value => {
return (
<EuiToolTip position="top" content={`Show ${value} decoder information`}>
<EuiLink onClick={() => {
this.changeBetweenDecoders(value);
}
}>
{value}
</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'details.program_name',
name: 'Program name',
align: 'left',
sortable: true
},
{
field: 'details.order',
name: 'Order',
align: 'left',
sortable: true
},
{
field: 'file',
name: 'File',
align: 'left',
sortable: true,
render: (value, item) => {
return (
<EuiToolTip position="top" content={`Show ${value} content`}>
<EuiLink onClick={async () => {
const noLocal = item.path.startsWith('ruleset/');
const result = await this.rulesetHandler.getDecoderContent(value, noLocal);
const file = { name: value, content: result, path: item.path };
this.props.updateFileContent(file);
}
}>{value}</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'path',
name: 'Path',
align: 'left',
sortable: true
}
];
}
componentWillUnmount() {
// When the component is going to be unmounted its info is clear
this.props.cleanInfo();
}
/**
* Clean the existing filters and sets the new ones and back to the previous section
*/
setNewFiltersAndBack(filters) {
const fil = filters.filters || filters;
this.props.cleanFilters();
this.props.updateFilters(fil);
this.props.cleanInfo();
}
/**
* Render the basic information in a list
* @param {Number} position
* @param {String} file
* @param {String} path
*/
renderInfo(position, file, path) {
return (
<ul>
<li key="position"><b>Position:</b>&nbsp;<span className="subdued-color">{position}</span></li>
<EuiSpacer size="s" />
<li key="file"><b>File:</b>
<EuiToolTip position="top" content={`Filter by this file: ${file}`}>
<EuiLink onClick={async () => this.setNewFiltersAndBack({ file: file })}>
&nbsp;{file}
</EuiLink>
</EuiToolTip>
</li>
<EuiSpacer size="s" />
<li key="path"><b>Path:</b>
<EuiToolTip position="top" content={`Filter by this path: ${path}`}>
<EuiLink onClick={async () => this.setNewFiltersAndBack({ path: path })}>
&nbsp;{path}
</EuiLink>
</EuiToolTip>
</li>
<EuiSpacer size="s" />
</ul>
)
}
/**
* Render a list with the details
* @param {Array} details
*/
renderDetails(details) {
const detailsToRender = [];
Object.keys(details).forEach(key => {
let content = details[key]
if (key === 'regex') {
content = this.colorRegex(content);
} else if (key === 'order') {
content = this.colorOrder(content);
} else {
content = <span className="subdued-color">{details[key]}</span>;
}
detailsToRender.push(
<Fragment>
<li key={key}><b>{key}:</b>&nbsp;{content}</li>
<EuiSpacer size="s" />
</Fragment>
);
});
return (
<ul>
{detailsToRender}
</ul>
)
}
/**
* This set a color to a given order
* @param {String} order
*/
colorOrder(order) {
order = order.toString();
let valuesArray = order.split(',');
const result = [];
for (let i = 0, len = valuesArray.length; i < len; i++) {
const coloredString = <span style={{ color: colors[i] }}>{valuesArray[i]}</span>;
result.push(coloredString);
}
return result;
}
/**
* This set a color to a given regex
* @param {String} regex
*/
colorRegex(regex) {
regex = regex.toString();
const starts = <span className="subdued-color">{regex.split('(')[0]}</span>;
let valuesArray = regex.match(/\(((?!<\/span>).)*?\)(?!<\/span>)/gim);
const result = [starts];
for (let i = 0, len = valuesArray.length; i < len; i++) {
const coloredString = <span style={{ color: colors[i] }}>{valuesArray[i]}</span>;
result.push(coloredString);
}
return result;
}
/**
* Changes between decoders
* @param {Number} name
*/
changeBetweenDecoders(name) {
this.setState({ currentDecoder: name });
}
render() {
const { decoderInfo, isLoading } = this.props.state;
const currentDecoder = (this.state && this.state.currentDecoder) ? this.state.currentDecoder : decoderInfo.current;
const decoders = decoderInfo.items;
const currentDecoderArr = decoders.filter(r => { return r.name === currentDecoder });
const currentDecoderInfo = currentDecoderArr[0];
const { position, details, file, name, path } = currentDecoderInfo;
const columns = this.columns;
return (
<EuiPage style={{ background: 'transparent' }}>
<EuiFlexGroup>
<EuiFlexItem>
{/* Decoder description name */}
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiTitle>
<h2>
<EuiToolTip position="right" content="Back to decoders">
<EuiButtonIcon
aria-label="Back"
color="subdued"
iconSize="l"
iconType="arrowLeft"
onClick={() => this.props.cleanInfo()} />
</EuiToolTip>
{name}
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
{/* Cards */}
<EuiFlexGroup>
{/* General info */}
<EuiFlexItem>
<EuiPanel paddingSize="s">
<EuiText color="subdued">Information</EuiText>
<EuiSpacer size="xs" className="subdued-background" />
<EuiSpacer size="s" />
{this.renderInfo(position, file, path)}
</EuiPanel>
</EuiFlexItem>
{/* Details */}
<EuiFlexItem>
<EuiPanel paddingSize="s">
<EuiText color="subdued">Details</EuiText>
<EuiSpacer size="xs" className="subdued-background" />
<EuiSpacer size="s" />
{this.renderDetails(details)}
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
{/* Table */}
<EuiSpacer size="l" />
<EuiPanel paddingSize="m">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="s">
<h5>
Related decoders
</h5>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem>
<EuiInMemoryTable
itemId="id"
items={decoders}
columns={columns}
pagination={true}
loading={isLoading}
sorting={true}
message={null}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPage>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
const mapDispatchToProps = (dispatch) => {
return {
updateFileContent: content => dispatch(updateFileContent(content)),
cleanFileContent: () => dispatch(cleanFileContent()),
updateFilters: filters => dispatch(updateFilters(filters)),
cleanFilters: () => dispatch(cleanFilters()),
cleanInfo: () => dispatch(cleanInfo())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(WzDecoderInfo);

View File

@ -0,0 +1,530 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component, Fragment } from 'react';
import {
EuiInMemoryTable,
EuiPage,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiToolTip,
EuiButtonIcon,
EuiButton,
EuiText,
EuiButtonEmpty,
EuiPopover,
EuiFieldText,
EuiSpacer,
EuiPanel,
} from '@elastic/eui';
import { connect } from 'react-redux';
import { cleanInfo, updateListContent } from '../../../../../redux/actions/rulesetActions';
import RulesetHandler from './utils/ruleset-handler';
import { toastNotifications } from 'ui/notify';
class WzListEditor extends Component {
constructor(props) {
super(props);
this.state = {
items: [],
isSaving: false,
editing: false,
isPopoverOpen: false,
addingKey: '',
addingValue: '',
editingValue: '',
newListName: '',
};
this.items = {};
this.rulesetHandler = RulesetHandler;
this.columns = [
{
field: 'key',
name: 'Key',
align: 'left',
sortable: true,
},
{
field: 'value',
name: 'Value',
align: 'left',
sortable: true,
},
];
this.adminColumns = [
{
field: 'key',
name: 'Key',
align: 'left',
sortable: true,
},
{
field: 'value',
name: 'Value',
align: 'left',
sortable: true,
render: (value, item) => {
if (this.state.editing === item.key) {
return (
<EuiFieldText
placeholder="New value"
value={this.state.editingValue}
onChange={this.onChangeEditingValue}
aria-label="Use aria labels when no actual label is in use"
/>
);
} else {
return <span>{value}</span>;
}
},
},
{
name: 'Actions',
align: 'left',
render: item => {
if (this.state.editing === item.key) {
return (
<Fragment>
<EuiText color="subdued">{'Are you sure?'}</EuiText>
<EuiToolTip position="top" content={'Yes'}>
<EuiButtonIcon
aria-label="Confirm value"
iconType="check"
onClick={() => {
this.setEditedValue();
}}
color="primary"
/>
</EuiToolTip>
<EuiToolTip position="top" content={'No'}>
<EuiButtonIcon
aria-label="Cancel edition"
iconType="cross"
onClick={() => this.setState({ editing: false }) }
color="danger"
/>
</EuiToolTip>
</Fragment>
);
} else {
return (
<Fragment>
<EuiToolTip position="top" content={`Edit ${item.key}`}>
<EuiButtonIcon
aria-label="Edit content"
iconType="pencil"
onClick={() => {
this.setState({ editing: item.key, editingValue: item.value });
}}
color="primary"
/>
</EuiToolTip>
<EuiToolTip position="top" content={`Remove ${item.key}`}>
<EuiButtonIcon
aria-label="Show content"
iconType="trash"
onClick={() => this.deleteItem(item.key) }
color="danger"
/>
</EuiToolTip>
</Fragment>
);
}
},
},
];
}
componentDidMount() {
const { listInfo } = this.props.state;
const { content } = listInfo;
const obj = this.contentToObject(content);
this.items = { ...obj };
const items = this.contentToArray(obj);
this.setState({ items });
}
/**
* When getting a CDB list is returned a raw text, this function parses it to an array
* @param {Object} obj
*/
contentToArray(obj) {
const items = [];
for (const key in obj) {
const value = obj[key];
items.push(Object.assign({ key, value }));
}
return items;
}
/**
* Save in the state as object the items for an easy modification by key-value
* @param {String} content
*/
contentToObject(content) {
const items = {};
const lines = content.split('\n');
lines.forEach(line => {
const split = line.split(':');
const key = split[0];
const value = split[1] || '';
if (key) items[key] = value; // Prevent add empty keys
});
return items;
}
/**
* Transform this.items (an object) into a raw string
*/
itemsToRaw() {
let raw = '';
Object.keys(this.items).forEach(key => {
raw = raw ? `${raw}\n${key}:${this.items[key]}` : `${key}:${this.items[key]}`;
});
return raw;
}
/**
* Save the list
* @param {String} name
* @param {String} path
*/
async saveList(name, path, addingNew = false) {
try {
if (!name) {
this.showToast('warning', 'Invalid name', 'Please insert a valid name', 3000);
return;
}
const overwrite = addingNew; // If adding new disable the overwrite
const raw = this.itemsToRaw();
if (!raw) {
this.showToast(
'warning',
'Please insert at least one item',
'Please insert at least one item, a CDB list cannot be empty',
3000
);
return;
}
this.setState({ isSaving: true });
await this.rulesetHandler.sendCdbList(name, path, raw, overwrite);
if (!addingNew) {
const result = await this.rulesetHandler.getCdbList(`${path}/${name}`);
const file = { name: name, content: result, path: path };
this.props.updateListContent(file);
this.showToast('success', 'Success', 'CBD List successfully created', 3000);
} else {
this.showToast('success', 'Success', 'CBD List updated', 3000);
}
} catch (error) {
this.showToast('danger', 'Error', 'Error saving CDB list: ' + error, 3000);
}
this.setState({ isSaving: false });
}
showToast = (color, title, text, time) => {
toastNotifications.add({
color: color,
title: title,
text: text,
toastLifeTimeMs: time,
});
};
openPopover = () => {
this.setState({
isPopoverOpen: true,
});
};
closePopover = () => {
this.setState({
isPopoverOpen: false,
addingKey: 'key',
});
};
onChangeKey = e => {
this.setState({
addingKey: e.target.value,
});
};
onChangeValue = e => {
this.setState({
addingValue: e.target.value,
});
};
onChangeEditingValue = e => {
this.setState({
editingValue: e.target.value,
});
};
onNewListNameChange = e => {
this.setState({
newListName: e.target.value,
});
};
/**
* Append a key value to this.items and after that if everything works ok re-create the array for the table
*/
addItem() {
const { addingKey, addingValue } = this.state;
if (!addingKey || Object.keys(this.items).includes(addingKey)) {
console.log('Key empty or already exists');
return;
}
this.items[addingKey] = addingValue;
const itemsArr = this.contentToArray(this.items);
this.setState({
items: itemsArr,
addingKey: '',
addingValue: '',
});
}
/**
* Set the new value in the input field when editing a item value (this.props.editingValue)
*/
setEditedValue() {
const key = this.state.editing;
const value = this.state.editingValue;
this.items[key] = value;
const itemsArr = this.contentToArray(this.items);
this.setState({
items: itemsArr,
editing: false,
editingValue: '',
});
}
/**
* Delete a item from the list
* @param {String} key
*/
deleteItem(key) {
delete this.items[key];
const items = this.contentToArray(this.items);
this.setState({ items });
}
/**
* Render an input in order to set a cdb list name
*/
renderInputNameForNewCdbList() {
return (
<Fragment>
<EuiFlexItem grow={false}>
<EuiTitle>
<h2>
<EuiToolTip position="right" content={'Back to lists'}>
<EuiButtonIcon
aria-label="Back"
color="subdued"
iconSize="l"
iconType="arrowLeft"
onClick={() => this.props.cleanInfo()}
/>
</EuiToolTip>
{name}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem style={{ marginLeft: '-5px !important' }}>
<EuiFieldText
style={{ marginLeft: '-18px' }}
placeholder="New CDB list name"
value={this.state.newListName}
onChange={this.onNewListNameChange}
aria-label="Use aria labels when no actual label is in use"
/>
</EuiFlexItem>
</Fragment>
);
}
/**
* Render an add buton with a popover to add new key and values and the save button for saving the list changes
* @param {String} name
* @param {String} path
*/
renderAddAndSave(name, path, newList = false) {
const addButton = <EuiButtonEmpty onClick={() => this.openPopover()}>Add</EuiButtonEmpty>;
const saveButton = (
<EuiButton
fill
iconType="save"
isLoading={this.state.isSaving}
onClick={async () => this.saveList(name, path, newList)}
>
Save
</EuiButton>
);
const addItemButton = (
<EuiButton fill onClick={() => this.addItem()}>
Add
</EuiButton>
);
const closeButton = <EuiButtonEmpty onClick={() => this.closePopover()}>Close</EuiButtonEmpty>;
return (
<Fragment>
<EuiFlexItem style={{ textAlign: 'rigth' }} grow={false}>
<EuiPopover
id="addKeyValuePopover"
ownFocus
button={addButton}
isOpen={this.state.isPopoverOpen}
anchorPosition="leftCenter"
closePopover={() => this.closePopover()}
>
<EuiFieldText
placeholder="key"
value={this.state.addingKey}
onChange={this.onChangeKey}
aria-label="Use aria labels when no actual label is in use"
/>
<EuiSpacer size="s" />
<EuiFieldText
placeholder="value"
value={this.state.addingValue}
onChange={this.onChangeValue}
aria-label="Use aria labels when no actual label is in use"
/>
<div style={{ textAlign: 'center' }}>
<EuiSpacer size="m" />
{addItemButton}
<EuiSpacer size="s" />
{closeButton}
</div>
</EuiPopover>
</EuiFlexItem>
{/* Save button */}
<EuiFlexItem grow={false}>{saveButton}</EuiFlexItem>
</Fragment>
);
}
/**
* Render the list name, path, and back button
* @param {String} name
* @param {String} path
*/
renderTitle(name, path) {
return (
<Fragment>
<EuiFlexItem grow={false}>
<EuiTitle>
<h2>
<EuiToolTip position="right" content={'Back to lists'}>
<EuiButtonIcon
aria-label="Back"
color="subdued"
iconSize="l"
iconType="arrowLeft"
onClick={() => this.props.cleanInfo()}
/>
</EuiToolTip>
{name}
</h2>
</EuiTitle>
</EuiFlexItem>
<EuiFlexItem style={{ marginLeft: '-5px !important' }}>
<EuiText color="subdued" style={{ marginTop: '10px' }}>
{path}
</EuiText>
</EuiFlexItem>
</Fragment>
);
}
//isDisabled={nameForSaving.length <= 4}
render() {
const { listInfo, isLoading, error, adminMode } = this.props.state;
const { name, path } = listInfo;
const message = isLoading ? false : 'No results...';
const columns = adminMode ? this.adminColumns : this.columns;
const addingNew = name === false || !name;
const listName = this.state.newListName || name;
return (
<EuiPage style={{ background: 'transparent' }}>
<EuiPanel>
<EuiFlexGroup>
<EuiFlexItem>
{/* File name and back button when watching or editing a CDB list */}
<EuiFlexGroup>
{(!addingNew && this.renderTitle(name, path)) || this.renderInputNameForNewCdbList()}
<EuiFlexItem />
{/* This flex item is for separating between title and save button */}
{/* Pop over to add new key and value */}
{adminMode &&
!this.state.editing &&
this.renderAddAndSave(listName, path, !addingNew)}
</EuiFlexGroup>
{/* CDB list table */}
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem style={{ marginTop: '30px' }}>
<EuiInMemoryTable
itemId="id"
items={this.state.items}
columns={columns}
pagination={{ pageSizeOptions: [10, 15] }}
loading={isLoading}
sorting={true}
message={message}
search={{ box: { incremental: true } }}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiPage>
);
}
}
const mapStateToProps = state => {
return {
state: state.rulesetReducers,
};
};
const mapDispatchToProps = dispatch => {
return {
cleanInfo: () => dispatch(cleanInfo()),
updateListContent: content => dispatch(updateListContent(content)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(WzListEditor);

View File

@ -0,0 +1,61 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
// Redux
import store from '../../../../../redux/store';
import WzReduxProvider from '../../../../../redux/wz-redux-provider';
//Wazuh ruleset tables(rules, decoder, lists)
import WzRulesetOverview from './ruleset-overview';
//Information about rule or decoder
import WzRuleInfo from './rule-info';
import WzDecoderInfo from './decoder-info';
import WzRulesetEditor from './ruleset-editor';
import WzListEditor from './list-editor';
export default class WzRuleset extends Component {
constructor(props) {
super(props);
this.state = {}; //Init state empty to avoid fails when try to read any parameter and this.state is not defined yet
this.store = store;
}
UNSAFE_componentWillMount() {
this.store.subscribe(() => {
const state = this.store.getState().rulesetReducers;
this.setState(state);
});
}
componentWillUnmount() {
// When the component is going to be unmounted the ruleset state is reset
const { ruleInfo, decoderInfo, listInfo, fileContent, addingRulesetFile } = this.state;
if (!ruleInfo && !decoderInfo && !listInfo && !fileContent, !addingRulesetFile) this.store.dispatch({ type: 'RESET' });
}
render() {
const { ruleInfo, decoderInfo, listInfo, fileContent, addingRulesetFile } = this.state;
return (
<WzReduxProvider>
{
ruleInfo && (<WzRuleInfo />)
|| decoderInfo && (<WzDecoderInfo />)
|| listInfo && (<WzListEditor />)
|| (fileContent || addingRulesetFile) && (<WzRulesetEditor />)
|| (<WzRulesetOverview />)
}
</WzReduxProvider>
)
}
}

View File

@ -0,0 +1,431 @@
import React, { Component, Fragment } from 'react';
// Eui components
import {
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiPage,
EuiButtonIcon,
EuiTitle,
EuiToolTip,
EuiText,
EuiSpacer,
EuiInMemoryTable,
EuiLink
} from '@elastic/eui';
import { connect } from 'react-redux';
import RulesetHandler from './utils/ruleset-handler';
import {
updateFileContent,
cleanFileContent,
cleanInfo,
updateFilters,
cleanFilters
} from '../../../../../redux/actions/rulesetActions';
class WzRuleInfo extends Component {
constructor(props) {
super(props);
this.complianceEquivalences = {
pci: 'PCI DSS',
gdpr: 'GDPR',
gpg13: 'GPG 13',
hipaa: 'HIPAA',
'nist-800-53': 'NIST-800-53'
}
this.rulesetHandler = RulesetHandler;
this.columns = [
{
field: 'id',
name: 'ID',
align: 'left',
sortable: true,
width: '5%',
render: value => {
return (
<EuiToolTip position="top" content={`Show rule ID ${value} information`}>
<EuiLink onClick={() => {
this.changeBetweenRules(value);
}
}>
{value}
</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'description',
name: 'Description',
align: 'left',
sortable: true,
width: '30%'
},
{
field: 'groups',
name: 'Groups',
align: 'left',
sortable: true,
width: '10%'
},
{
field: 'pci',
name: 'PCI',
align: 'left',
sortable: true,
width: '10%'
},
{
field: 'gdpr',
name: 'GDPR',
align: 'left',
sortable: true,
width: '10%'
},
{
field: 'hipaa',
name: 'HIPAA',
align: 'left',
sortable: true,
width: '10%'
},
{
field: 'nist-800-53',
name: 'NIST 800-53',
align: 'left',
sortable: true,
width: '10%'
},
{
field: 'level',
name: 'Level',
align: 'left',
sortable: true,
width: '5%'
},
{
field: 'file',
name: 'File',
align: 'left',
sortable: true,
width: '15%',
render: (value, item) => {
return (
<EuiToolTip position="top" content={`Show ${value} content`}>
<EuiLink onClick={async () => {
const noLocal = item.path.startsWith('ruleset/');
const result = await this.rulesetHandler.getRuleContent(value, noLocal);
const file = { name: value, content: result, path: item.path };
this.props.updateFileContent(file);
}
}>
{value}
</EuiLink>
</EuiToolTip>
)
}
}
];
}
componentWillUnmount() {
// When the component is going to be unmounted its info is clear
this.props.cleanInfo();
}
/**
* Build an object with the compliance info about a rule
* @param {Object} ruleInfo
*/
buildCompliance(ruleInfo) {
const compliance = {};
const complianceKeys = ['gdpr', 'gpg13', 'hipaa', 'nist-800-53', 'pci'];
Object.keys(ruleInfo).forEach(key => {
if (complianceKeys.includes(key) && ruleInfo[key].length) compliance[key] = ruleInfo[key]
});
return compliance || {};
}
/**
* Clean the existing filters and sets the new ones and back to the previous section
*/
setNewFiltersAndBack(filters) {
const fil = filters.filters || filters;
this.props.cleanFilters();
this.props.updateFilters(fil);
this.props.cleanInfo();
}
/**
* Render the basic information in a list
* @param {Number} id
* @param {Number} level
* @param {String} file
* @param {String} path
*/
renderInfo(id, level, file, path) {
return (
<ul>
<li key="id"><b>ID:</b>&nbsp;{id}</li>
<EuiSpacer size="s" />
<li key="level"><b>Level:</b>
<EuiToolTip position="top" content={`Filter by this level: ${level}`}>
<EuiLink onClick={async () => this.setNewFiltersAndBack({ level: level })}>
&nbsp;{level}
</EuiLink>
</EuiToolTip>
</li>
<EuiSpacer size="s" />
<li key="file"><b>File:</b>
<EuiToolTip position="top" content={`Filter by this file: ${file}`}>
<EuiLink onClick={async () => this.setNewFiltersAndBack({ file: file })}>
&nbsp;{file}
</EuiLink>
</EuiToolTip>
</li>
<EuiSpacer size="s" />
<li key="path"><b>Path:</b>
<EuiToolTip position="top" content={`Filter by this path: ${path}`}>
<EuiLink onClick={async () => this.setNewFiltersAndBack({ path: path })}>
&nbsp;{path}
</EuiLink>
</EuiToolTip>
</li>
<EuiSpacer size="s" />
</ul>
)
}
/**
* Render a list with the details
* @param {Array} details
*/
renderDetails(details) {
const detailsToRender = [];
Object.keys(details).forEach(key => {
detailsToRender.push(
<Fragment>
<li key={key}><b>{key}:</b>&nbsp;{details[key]}</li>
<EuiSpacer size="s" />
</Fragment>
);
});
return (
<ul>
{detailsToRender}
</ul>
)
}
/**
* Render the groups
* @param {Array} groups
*/
renderGroups(groups) {
const listGroups = [];
groups.forEach(group => {
listGroups.push(
<Fragment>
<EuiLink onClick={async () => this.setNewFiltersAndBack({ group: group })}>
<EuiToolTip position="top" content={`Filter by this group: ${group}`}>
<li key={group}>
{group}
</li>
</EuiToolTip>
</EuiLink>
<EuiSpacer size="s" />
</Fragment>
);
});
return (
<ul>
{listGroups}
</ul>
)
}
/**
* Render the compliance(HIPAA, NIST...)
* @param {Array} compliance
*/
renderCompliance(compliance) {
const listCompliance = [];
const keys = Object.keys(compliance);
for (let i in Object.keys(keys)) {
const key = keys[i];
listCompliance.push(
<Fragment>
<li key={key}><b>{this.complianceEquivalences[key]}</b></li>
<EuiSpacer size="s" />
</Fragment>
)
compliance[key].forEach(element => {
const filters = {};
filters[key] = element;
listCompliance.push(
<Fragment>
<EuiLink onClick={async () => this.setNewFiltersAndBack({ filters })}>
<EuiToolTip position="top" content="Filter by this compliance">
<li key={element}>{element}</li>
</EuiToolTip>
</EuiLink>
<EuiSpacer size="s" />
</Fragment>
);
});
}
return (
<ul>
{listCompliance}
</ul>
)
}
/**
* Changes between rules
* @param {Number} ruleId
*/
changeBetweenRules(ruleId) {
this.setState({ currentRuleId: ruleId });
}
render() {
const { ruleInfo, isLoading } = this.props.state;
const currentRuleId = (this.state && this.state.currentRuleId) ? this.state.currentRuleId : ruleInfo.current;
const rules = ruleInfo.items;
const currentRuleArr = rules.filter(r => { return r.id === currentRuleId });
const currentRuleInfo = currentRuleArr[0];
const { description, details, file, path, level, id, groups } = currentRuleInfo;
const compliance = this.buildCompliance(currentRuleInfo);
const columns = this.columns;
return (
<EuiPage style={{ background: 'transparent' }}>
<EuiFlexGroup>
<EuiFlexItem>
{/* Rule description name */}
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiTitle>
<h2>
<EuiToolTip position="right" content="Back to rules">
<EuiButtonIcon
aria-label="Back"
color="subdued"
iconSize="l"
iconType="arrowLeft"
onClick={() => this.props.cleanInfo()} />
</EuiToolTip>
{description}
</h2>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
{/* Cards */}
<EuiFlexGroup>
{/* General info */}
<EuiFlexItem>
<EuiPanel paddingSize="s">
<EuiText color="subdued">Information</EuiText>
<EuiSpacer size="xs" className="subdued-background" />
<EuiSpacer size="s" />
{this.renderInfo(id, level, file, path)}
</EuiPanel>
</EuiFlexItem>
{/* Details */}
<EuiFlexItem>
<EuiPanel paddingSize="s">
<EuiText color="subdued">Details</EuiText>
<EuiSpacer size="xs" className="subdued-background" />
<EuiSpacer size="s" />
{this.renderDetails(details)}
</EuiPanel>
</EuiFlexItem>
{/* Groups */}
<EuiFlexItem>
<EuiPanel paddingSize="s">
<EuiText color="subdued">Groups</EuiText>
<EuiSpacer size="xs" className="subdued-background" />
<EuiSpacer size="s" />
{this.renderGroups(groups)}
</EuiPanel>
</EuiFlexItem>
{/* Compliance */}
{Object.keys(compliance).length > 0 && (
<EuiFlexItem>
<EuiPanel paddingSize="s">
<EuiText color="subdued">Compliance</EuiText>
<EuiSpacer size="xs" className="subdued-background" />
<EuiSpacer size="s" />
{this.renderCompliance(compliance)}
</EuiPanel>
</EuiFlexItem>
)}
</EuiFlexGroup>
{/* Table */}
<EuiSpacer size="l" />
<EuiPanel paddingSize="m">
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiTitle size="s">
<h5>
Related rules
</h5>
</EuiTitle>
</EuiFlexItem>
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem>
<EuiInMemoryTable
itemId="id"
items={rules}
columns={columns}
pagination={true}
loading={isLoading}
sorting={true}
message={false}
/>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPage>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
const mapDispatchToProps = (dispatch) => {
return {
updateFileContent: content => dispatch(updateFileContent(content)),
cleanFileContent: () => dispatch(cleanFileContent()),
updateFilters: filters => dispatch(updateFilters(filters)),
cleanFilters: () => dispatch(cleanFilters()),
cleanInfo: () => dispatch(cleanInfo())
}
};
export default connect(mapStateToProps, mapDispatchToProps)(WzRuleInfo);

View File

@ -0,0 +1,249 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
cleanInfo,
updateFileContent,
} from '../../../../../redux/actions/rulesetActions';
// Eui components
import {
EuiPage,
EuiSpacer,
EuiFlexGroup,
EuiFlexItem,
EuiTitle,
EuiToolTip,
EuiButtonIcon,
EuiButton,
EuiFieldText,
EuiCodeEditor,
EuiPanel,
} from '@elastic/eui';
import RulesetHandler from './utils/ruleset-handler';
import validateConfigAfterSent from './utils/valid-configuration';
import { toastNotifications } from 'ui/notify';
class WzRulesetEditor extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.codeEditorOptions = {
fontSize: '14px',
enableBasicAutocompletion: true,
enableSnippets: true,
enableLiveAutocompletion: true
}
this.rulesetHandler = RulesetHandler;
const { fileContent, addingRulesetFile } = this.props.state;
const { name, content, path } = fileContent ? fileContent : addingRulesetFile;
this.state = {
isSaving: false,
error: false,
inputValue: '',
content,
name,
path,
}
}
componentWillUnmount() {
// When the component is going to be unmounted its info is clear
this._isMounted = false;
this.props.cleanInfo();
}
componentDidMount() {
this._isMounted = true;
}
/**
* Save the new content
* @param {String} name
* @param {Boolean} overwrite
*/
async save(name, overwrite = true) {
if (!this._isMounted) {
return;
}
try {
const { content } = this.state
this.setState({ isSaving: true, error: false });
const { section } = this.props.state;
let saver = this.rulesetHandler.sendRuleConfiguration; // By default the saver is for rules
if (section === 'decoders') saver = this.rulesetHandler.sendDecoderConfiguration;
await saver(name, content , overwrite);
try {
await validateConfigAfterSent();
} catch (error) {
const warning = Object.assign(error, { savedMessage: `File ${name} saved, but there were found several error while validating the configuration.` });
this.setState({ isSaving: false });
this.goToEdit(name);
this.showToast('warning', warning.savedMessage, warning.details, 3000);
return;
}
this.setState({ isSaving: false });
this.goToEdit(name);
let textSuccess = 'New file successfully created'
if(overwrite) {
textSuccess = 'File successfully edited'
}
this.showToast('success', 'Success', textSuccess, 3000);
} catch (error) {
this.setState({ error, isSaving: false });
this.showToast('danger', 'Error', 'Error saving CDB list: ' + error, 3000);
}
}
showToast = (color, title, text, time) => {
toastNotifications.add({
color: color,
title: title,
text: text,
toastLifeTimeMs: time,
});
};
goToEdit = (name) => {
const { content, path } = this.state;
const file = { name: name, content: content, path: path };
this.props.updateFileContent(file);
}
/**
* onChange the input value in case adding new file
*/
onChange = e => {
this.setState({
inputValue: e.target.value,
});
};
render() {
const { section, adminMode, addingRulesetFile, fileContent } = this.props.state;
const { name, content, path } = this.state;
const isEditable = addingRulesetFile ? true : (path !== 'ruleset/rules' && path !== 'ruleset/decoders' && adminMode);
let nameForSaving = addingRulesetFile ? this.state.inputValue : name;
nameForSaving = name.endsWith('.xml') ? nameForSaving : `${nameForSaving}.xml`;
const overwrite = fileContent ? true : false;
const saveButton = (
<EuiButton
fill
iconType="save"
isLoading={this.state.isSaving}
isDisabled={nameForSaving.length <= 4}
onClick={() => this.save(nameForSaving, overwrite)}>
Save
</EuiButton>
);
return (
<EuiPage style={{ background: 'transparent' }}>
<EuiPanel>
<EuiFlexGroup>
<EuiFlexItem>
{/* File name and back button */}
<EuiFlexGroup>
<EuiFlexItem>
{!fileContent && (
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiToolTip position="right" content={`Back to ${section}`}>
<EuiButtonIcon
aria-label="Back"
color="subdued"
iconSize="l"
iconType="arrowLeft"
onClick={() => this.props.cleanInfo()} />
</EuiToolTip>
</EuiFlexItem>
<EuiFlexItem>
<EuiFieldText
style={{ width: '300px' }}
placeholder={`Type your new ${section} file name here`}
value={this.state.inputValue}
onChange={this.onChange}
aria-label="aria-label to prevent react warning"
/>
</EuiFlexItem>
</EuiFlexGroup>
) || (
<EuiTitle>
<h2>
<EuiToolTip position="right" content={`Back to ${section}`}>
<EuiButtonIcon
aria-label="Back"
color="subdued"
iconSize="l"
iconType="arrowLeft"
onClick={() => this.props.cleanInfo()} />
</EuiToolTip>
{nameForSaving}
</h2>
</EuiTitle>
)}
</EuiFlexItem>
<EuiFlexItem />{/* This flex item is for separating between title and save button */}
{isEditable && (
<EuiFlexItem grow={false}>
{saveButton}
</EuiFlexItem>
)}
</EuiFlexGroup>
<EuiSpacer size="m" />
<EuiFlexGroup>
<EuiFlexItem>
<EuiFlexGroup>
<EuiFlexItem>
<EuiCodeEditor
width="100%"
height="calc(100vh - 250px)"
value={content}
onChange={newContent => this.setState({content: newContent})}
mode="xml"
isReadOnly={!isEditable}
setOptions={this.codeEditorOptions}
aria-label="Code Editor"
></EuiCodeEditor>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiPage >
)
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
const mapDispatchToProps = (dispatch) => {
return {
cleanInfo: () => dispatch(cleanInfo()),
updateFileContent: content => dispatch(updateFileContent(content)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(WzRulesetEditor);

View File

@ -0,0 +1,226 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import { EuiComboBox, EuiFormRow } from '@elastic/eui';
import { connect } from 'react-redux';
import {
updateLoadingStatus,
updateFilters,
updateError
} from '../../../../../redux/actions/rulesetActions';
import RulesetHandler from './utils/ruleset-handler';
class WzRulesetFilterBar extends Component {
constructor(props) {
super(props);
this.state = {
isInvalid: false,
selectedOptions: [],
};
this.rulesetHandler = RulesetHandler;
this.availableOptions = {
rules: ['nist-800-53', 'hipaa', 'gdpr', 'pci', 'gpg13', 'group', 'level', 'path', 'file'],
decoders: ['path', 'file'],
lists: []
}
this.notValidMessage = false;
}
componentDidMount() {
this.buildSelectedOptions(this.props.state.filters); // If there are any filter in the redux store it will be restored when the component was mounted
}
isValid = value => {
const { section, showingFiles } = this.props.state;
if (section === 'lists' || showingFiles) return true;//There are not filters for lists
const lowerValue = value.toLowerCase()
const availableOptions = this.availableOptions[this.props.state.section].toString();
this.notValidMessage = false;
const options = this.availableOptions[this.props.state.section];
const valueSplit = lowerValue.split(':');
const oneTwoDots = valueSplit.length - 1 === 1; // Has : once
const moreTwoDots = valueSplit.length - 1 > 1; // Has : several times
const notAvailable = !options.includes(valueSplit[0]); // Not include in the available options
if (moreTwoDots || (oneTwoDots && notAvailable)) {
if (oneTwoDots) {
this.notValidMessage = `${valueSplit[0]} is a not valid filter, the available filters are: ${availableOptions}`;
} else {
this.notValidMessage = 'Only allow ":" once';
}
return false;
}
return true;
}
/**
* Set a valid array of objects for the options in the combo box [{label: value}, {label: value}]
*/
async buildSelectedOptions(filters) {
try {
const selectedOptions = [];
Object.keys(filters).forEach(key => {
const value = filters[key];
const option = key === 'search' ? value : `${key}:${value}`;
const newOption = {
label: option,
};
selectedOptions.push(newOption);
});
this.setState({ selectedOptions });
//const result = await this.wzReq.apiReq('GET', this.paths[section], {})
if (Object.keys(filters).length) await this.fetchItems(filters);
} catch (error) {
console.error('error building selected options ', error)
}
}
/**
* Fetch items (rules, decoders)
* @param {Object} filters
*/
async fetchItems(filters) {
try {
const { section } = this.props.state;
let fetcher = this.rulesetHandler.getRules// By default the fetcher is for rules
if (section === 'decoders') fetcher = this.rulesetHandler.getDecoders; // If section is decoders the fetcher changes
if (section === 'lists') fetcher = this.rulesetHandler.getLists// If the sections is lists the fetcher changes too
this.props.updateLoadingStatus(true);
const result = await fetcher(filters);
this.props.updateLoadingStatus(false);
} catch (error) {
this.props.updateError(error);
return Promise.reject(error);
}
}
/**
* When any element is removed from the this.state.selectedOptions is removed too from this.props.state.filters
* @param {Array} selectedOptions
*/
async cleanCurrentOption(selectedOptions) {
try {
const remainingKeys = [];
const currentOptions = { ...this.props.state.filters };
selectedOptions.forEach(option => {
const value = option.label;
const valueSplit = value.split(':');
const isSearch = valueSplit.length === 1;
const keyToRemove = isSearch ? 'search' : valueSplit[0];
remainingKeys.push(keyToRemove);
});
const currentOptiosnKeys = Object.keys(currentOptions);
const keysToRemove = currentOptiosnKeys.filter(option => { return !remainingKeys.includes(option) });
keysToRemove.forEach(key => delete currentOptions[key]);
this.props.updateFilters(currentOptions);
await this.fetchItems(currentOptions);
} catch (error) {
console.error('error cleaning current options ', error);
}
}
onCreateOption = searchValue => {
const isList = this.props.state.section === 'lists';
const lowerValue = searchValue.toLowerCase();
const currentOptions = { ...this.props.state.filters };
const creatingSplit = lowerValue.split(':');
let key = 'search';
let value;
if (!isList) {
if (creatingSplit.length > 1) {
key = creatingSplit[0];
value = creatingSplit[1];
} else {
value = creatingSplit[0];
}
if (!this.isValid(lowerValue) || !value) return false; // Return false to explicitly reject the user's input.
} else {
value = lowerValue;
}
currentOptions[key] = value;
this.props.updateFilters(currentOptions);
this.buildSelectedOptions(currentOptions);
};
// When writting in the filter bar
onSearchChange = searchValue => {
if (!searchValue) {
this.setState({
isInvalid: false,
});
return;
}
this.setState({
isInvalid: !this.isValid(searchValue),
});
};
onChange = selectedOptions => {
this.setState({
selectedOptions,
isInvalid: false,
});
this.cleanCurrentOption(selectedOptions);
};
render() {
const { section, showingFiles } = this.props.state;
const { selectedOptions, isInvalid } = this.state;
const options = !Object.keys(this.props.state.filters).length ? [] : selectedOptions;
const filters = !showingFiles ? `Filter ${section}...` : `Search ${section} files...`;
return (
<EuiFormRow
className="wz-form-row"
isInvalid={isInvalid}
error={this.notValidMessage}
>
<EuiComboBox
noSuggestions
placeholder={filters}
selectedOptions={options}
onCreateOption={this.onCreateOption}
onChange={this.onChange}
onSearchChange={this.onSearchChange}
isInvalid={isInvalid}
/>
</EuiFormRow>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
const mapDispatchToProps = (dispatch) => {
return {
updateLoadingStatus: status => dispatch(updateLoadingStatus(status)),
updateFilters: filters => dispatch(updateFilters(filters)),
updateError: error => dispatch(updateError(error))
}
};
export default connect(mapStateToProps, mapDispatchToProps)(WzRulesetFilterBar);

View File

@ -0,0 +1,3 @@
.euiSideNavItemButton__content{
justify-content: initial;
}

View File

@ -0,0 +1,118 @@
import React, { Component } from 'react';
// Eui components
import {
EuiFlexGroup,
EuiFlexItem,
EuiPanel,
EuiPage,
EuiText,
EuiTitle,
EuiSwitch,
EuiPopover,
EuiButton,
EuiButtonEmpty
} from '@elastic/eui';
import { connect } from 'react-redux';
// Wazuh components
import WzRulesetTable from './ruleset-table';
import WzRulesetActionButtons from './actions-buttons';
import './ruleset-overview.css';
import WzSearchBarFilter from '../../../../../components/wz-search-bar/wz-search-bar'
class WzRulesetOverview extends Component {
constructor(props) {
super(props);
this.sectionNames = {
rules: 'Rules',
decoders: 'Decoders',
lists: 'CDB lists'
}
this.model = [
{
label: 'Level',
options: [
{
label: '0',
group: 'level'
},
{
label: '1',
group: 'level'
},
{
label: '2',
group: 'level'
}
]
},
];
this.filters = {
rules: [
{ label: 'File', value: 'file' }, { label: 'Path', value: 'path' }, { label: 'Level', value: 'level' },
{ label: 'Group', value: 'group' }, { label: 'PCI control', value: 'pci' }, { label: 'GDPR', value: 'gdpr' }, { label: 'HIPAA', value: 'hipaa' }, { label: 'NIST-800-53', value: 'nist-800-53' }
],
decoders: [
{ label: 'File', value: 'file' }, { label: 'Path', value: 'path' }
]
};
}
clickActionFilterBar(obj) {
console.log('clicking ', obj)
}
render() {
const { section } = this.props.state;
return (
<EuiPage style={{ background: 'transparent' }}>
<EuiPanel>
<EuiFlexGroup>
<EuiFlexItem grow={false}>
<EuiTitle>
<h2>{this.sectionNames[section]}</h2>
</EuiTitle>
</EuiFlexItem>
{(section == 'rules' || section === 'decoders') && (
<EuiFlexItem grow={false} style={{ paddingTop: 7 }}>
<EuiSwitch
label={`Custom ${this.sectionNames[section]}`}
checked={false}
onChange={this.clickActionFilterBar}
/>
</EuiFlexItem>
)}
<EuiFlexItem>
</EuiFlexItem>
<WzRulesetActionButtons />
</EuiFlexGroup>
<EuiFlexGroup>
<EuiFlexItem>
<EuiText color="subdued">
{`From here you can manage your ${section}.`}
</EuiText>
</EuiFlexItem>
</EuiFlexGroup>
<WzSearchBarFilter
filters={this.filters[section]} />
<EuiFlexGroup>
<EuiFlexItem>
<WzRulesetTable />
</EuiFlexItem>
</EuiFlexGroup>
</EuiPanel>
</EuiPage>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
export default connect(mapStateToProps)(WzRulesetOverview);

View File

@ -0,0 +1,106 @@
/*
* Wazuh app - React component for show filter list.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import { connect } from 'react-redux';
import {
EuiFlexItem,
EuiPopover,
EuiButton,
EuiButtonEmpty
} from '@elastic/eui';
class WzPopoverFilters extends Component {
filters: {
rules: { label: string; value: string; }[];
decoders: { label: string; value: string; }[];
};
constructor(props) {
super(props);
this.state = {
isPopoverOpen: false
}
this.filters = {
rules: [
{ label: 'File', value: 'file' }, { label: 'Path', value: 'path' }, { label: 'Level', value: 'level' },
{ label: 'Group', value: 'group' }, { label: 'PCI control', value: 'pci' }, { label: 'GDPR', value: 'gdpr' }, { label: 'HIPAA', value: 'hipaa' }, { label: 'NIST-800-53', value: 'nist-800-53' }
],
decoders: [
{ label: 'File', value: 'file' }, { label: 'Path', value: 'path' }
]
};
}
onButtonClick() {
this.setState({
isPopoverOpen: !this.state['isPopoverOpen'],
});
}
closePopover() {
this.setState({
isPopoverOpen: false,
});
}
render() {
const { section } = this.props['state'];
const button = (
<EuiButton
fill
style={{ padding: 12 }}
color='primary'
onClick={() => this.onButtonClick()}
iconType="logstashFilter"
aria-label="Filter">
Filters
</EuiButton>
);
return (
<EuiFlexItem grow={false} style={{ marginLeft: 0 }}>
<EuiPopover
id="trapFocus"
ownFocus
button={button}
isOpen={this.state['isPopoverOpen']}
anchorPosition="downRight"
closePopover={this.closePopover.bind(this)}>
{this.filters[section].map((filter, idx) => (
<div key={idx}>
<EuiButtonEmpty size="s"
iconSide='right'
// TODO: Add the logic to applyFilters
onClick={() => null}>
{filter.label}
</EuiButtonEmpty>
</div>
))}
</EuiPopover>
</EuiFlexItem>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers,
};
};
const mapDispatchToProps = (dispatch) => {
return {
};
};
export default connect(mapStateToProps, mapDispatchToProps)(WzPopoverFilters);

View File

@ -0,0 +1,264 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import {
EuiBasicTable,
EuiCallOut,
EuiOverlayMask,
EuiConfirmModal
} from '@elastic/eui';
import { connect } from 'react-redux';
import RulesetHandler from './utils/ruleset-handler';
import { toastNotifications } from 'ui/notify';
import {
updateLoadingStatus,
updateFileContent,
updateRuleInfo,
updateDecoderInfo,
updateListContent,
updateIsProcessing,
updatePageIndex,
updateShowModal,
updateListItemsForRemove,
updateSortDirection,
updateSortField,
updateDefaultItems,
} from '../../../../../redux/actions/rulesetActions';
import RulesetColums from './utils/columns';
import { WzRequest } from '../../../../../react-services/wz-request';
class WzRulesetTable extends Component {
_isMounted = false;
constructor(props) {
super(props);
this.wzReq = (...args) => WzRequest.apiReq(...args);
this.state = {
items: [],
pageSize: 10,
totalItems: 0,
};
this.paths = {
rules: '/rules',
decoders: '/decoders',
lists: '/lists/files',
};
this.rulesetHandler = RulesetHandler;
}
async componentDidMount() {
this.props.updateIsProcessing(true);
this._isMounted = true;
}
async componentDidUpdate() {
if (this.props.state.isProcessing && this._isMounted) {
await this.getItems();
}
}
componentWillUnmount() {
this._isMounted = false;
}
async getItems() {
const { section, showingFiles } = this.props.state;
if(this.props.state.defaultItems.length === 0 && section === 'lists'){
await this.setDefaultItems();
}
const rawItems = await this.wzReq(
'GET',
`${this.paths[section]}${showingFiles ? '/files': ''}`,
this.buildFilter(),
)
const { items, totalItems } = ((rawItems || {}).data || {}).data;
this.setState({
items,
totalItems,
isProcessing: false,
});
this.props.updateIsProcessing(false);
}
async setDefaultItems() {
const requestDefaultItems = await this.wzReq(
'GET',
'/manager/configuration',
{
'wait_for_complete' : false,
'section': 'ruleset',
'field': 'list'
}
);
const defaultItems = ((requestDefaultItems || {}).data || {}).data;
this.props.updateDefaultItems(defaultItems);
}
buildFilter() {
const { pageIndex } = this.props.state;
const { pageSize } = this.state;
const filter = {
offset: pageIndex * pageSize,
limit: pageSize,
sort: this.buildSortFilter(),
};
return filter;
}
buildSortFilter() {
const {sortField, sortDirection} = this.props.state;
const field = sortField;
const direction = (sortDirection === 'asc') ? '+' : '-';
return direction+field;
}
onTableChange = ({ page = {}, sort = {} }) => {
const { index: pageIndex, size: pageSize } = page;
const { field: sortField, direction: sortDirection } = sort;
this.setState({ pageSize });
this.props.updatePageIndex(pageIndex);
this.props.updateSortDirection(sortDirection);
this.props.updateSortField(sortField);
this.props.updateIsProcessing(true);
};
render() {
this.rulesetColums = new RulesetColums(this.props);
const {
isLoading,
section,
pageIndex,
showingFiles,
error,
sortField,
sortDirection,
} = this.props.state;
const { items, pageSize, totalItems, } = this.state;
const rulesetColums = this.rulesetColums.columns;
const columns = showingFiles ? rulesetColums.files : rulesetColums[section];
const message = isLoading ? null : 'No results...';
const pagination = {
pageIndex: pageIndex,
pageSize: pageSize,
totalItemCount: totalItems,
pageSizeOptions: [10, 25, 50, 100],
};
const sorting = {
sort: {
field: sortField,
direction: sortDirection,
},
}
if (!error) {
const itemList = this.props.state.itemList;
return (
<div>
<EuiBasicTable
itemId="id"
items={items}
columns={columns}
pagination={pagination}
onChange={this.onTableChange}
loading={isLoading}
sorting={sorting}
message={message}
/>
{this.props.state.showModal ? (
<EuiOverlayMask>
<EuiConfirmModal
title="Are you sure?"
onCancel={() => this.props.updateShowModal(false)}
onConfirm={() => {
this.removeItems(itemList);
this.props.updateShowModal(false);
}}
cancelButtonText="No, don't do it"
confirmButtonText="Yes, do it"
defaultFocusedButton="cancel"
buttonColor="danger"
>
<p>Are you sure you want to remove?</p>
<div>
{itemList.map(function(item, i) {
return (
<li key={i}>{(item.file)? item.file: item.name}</li>
);
})}
</div>
</EuiConfirmModal>
</EuiOverlayMask>
) : null}
</div>
);
} else {
return <EuiCallOut color="warning" title={error} iconType="gear" />;
}
}
showToast = (color, title, text, time) => {
toastNotifications.add({
color: color,
title: title,
text: text,
toastLifeTimeMs: time,
});
};
async removeItems(items) {
this.props.updateLoadingStatus(true);
const results = items.map(async (item, i) => {
await this.rulesetHandler.deleteFile((item.file)? item.file: item.name, item.path);
});
Promise.all(results).then((completed) => {
this.props.updateIsProcessing(true);
this.props.updateLoadingStatus(false);
this.showToast('success', 'Success', 'Deleted correctly', 3000);
});
};
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers,
};
};
const mapDispatchToProps = (dispatch) => {
return {
updateLoadingStatus: status => dispatch(updateLoadingStatus(status)),
updateFileContent: content => dispatch(updateFileContent(content)),
updateRuleInfo: info => dispatch(updateRuleInfo(info)),
updateDecoderInfo: info => dispatch(updateDecoderInfo(info)),
updateListContent: content => dispatch(updateListContent(content)),
updateDefaultItems: defaultItems => dispatch(updateDefaultItems(defaultItems)),
updateIsProcessing: isProcessing => dispatch(updateIsProcessing(isProcessing)),
updatePageIndex: pageIndex => dispatch(updatePageIndex(pageIndex)),
updateShowModal: showModal => dispatch(updateShowModal(showModal)),
updateListItemsForRemove: itemList => dispatch(updateListItemsForRemove(itemList)),
updateSortDirection: sortDirection => dispatch(updateSortDirection(sortDirection)),
updateSortField: sortField => dispatch(updateSortField(sortField)),
};
};
export default connect(mapStateToProps, mapDispatchToProps)(WzRulesetTable);

View File

@ -0,0 +1,112 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React, { Component } from 'react';
import {
EuiSelect
} from '@elastic/eui';
import { connect } from 'react-redux';
import {
updateRulesetSection,
updateLoadingStatus,
toggleShowFiles,
cleanFilters,
updateAdminMode,
updateError,
updateIsProcessing,
} from '../../../../redux/actions/rulesetActions';
import { WzRequest } from '../../../../react-services/wz-request';
import checkAdminMode from './utils/check-admin-mode';
class WzSectionSelector extends Component {
constructor(props) {
super(props);
this.sections = [
{ value: 'rules', text: 'Rules' },
{ value: 'decoders', text: 'Decoders' },
{ value: 'lists', text: 'CDB lists' },
];
this.paths = {
rules: '/rules',
decoders: '/decoders',
lists: '/lists/files'
}
this.wzReq = WzRequest;
}
/**
* Fetch the data for a section: rules, decoders, lists...
* @param {String} newSection
*/
async fetchData(newSection) {
try {
const currentSection = this.props.state.section;
if (Object.keys(this.props.state.filters).length && newSection === currentSection) return; // If there's any filter and the section is de same doesn't fetch again
this.props.changeSection(newSection);
this.props.updateLoadingStatus(true);
const result = await this.wzReq.apiReq('GET', this.paths[newSection], {});
const items = result.data.data.items;
//Set the admin mode
const admin = await checkAdminMode();
this.props.updateAdminMode(admin);
this.props.toggleShowFiles(false);
this.props.changeSection(newSection);
this.props.updateLoadingStatus(false);
} catch (error) {
this.props.updateError(error);
}
}
onChange = async e => {
const section = e.target.value;
this.props.cleanFilters();
this.props.updateIsProcessing(true);
this.fetchData(section);
};
render() {
return (
<EuiSelect
id="wzSelector"
options={this.sections}
value={this.props.state.section}
onChange={this.onChange}
aria-label="Section selector"
/>
);
}
}
const mapStateToProps = (state) => {
return {
state: state.rulesetReducers
};
};
const mapDispatchToProps = (dispatch) => {
return {
changeSection: section => dispatch(updateRulesetSection(section)),
updateLoadingStatus: status => dispatch(updateLoadingStatus(status)),
toggleShowFiles: status => dispatch(toggleShowFiles(status)),
cleanFilters: () => dispatch(cleanFilters()),
updateAdminMode: status => dispatch(updateAdminMode(status)),
updateError: error => dispatch(updateError(error)),
updateIsProcessing: isProcessing => dispatch(updateIsProcessing(isProcessing)),
}
};
export default connect(mapStateToProps, mapDispatchToProps)(WzSectionSelector);

View File

@ -0,0 +1,18 @@
import { WzRequest } from '../../../../../../react-services/wz-request';
/**
* Check de admin mode and return true or false(if admin mode is not set in the wazuh.yml the default value is true)
*/
const checkAdminMode = async () => {
try {
let admin = true;
const result = await WzRequest.genericReq('GET', '/utils/configuration', {});
const data = (((result || {}).data) || {}).data || {};
if (Object.keys(data).includes('admin')) admin = data.admin;
return admin;
} catch (error) {
return Promise.error(error);
}
}
export default checkAdminMode;

View File

@ -0,0 +1,43 @@
/*
* Wazuh app - Palette color for ruleset
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
export const colors = [
'#004A65',
'#00665F',
'#BF4B45',
'#BF9037',
'#1D8C2E',
'BB3ABF',
'#00B1F1',
'#00F2E2',
'#7F322E',
'#7F6025',
'#104C19',
'7C267F',
'#0079A5',
'#00A69B',
'#FF645C',
'#FFC04A',
'#2ACC43',
'F94DFF',
'#0082B2',
'#00B3A7',
'#401917',
'#403012',
'#2DD947',
'3E1340',
'#00668B',
'#008C83',
'#E55A53',
'#E5AD43',
'#25B23B',
'E045E5'
];

View File

@ -0,0 +1,305 @@
import React from 'react';
import { EuiToolTip, EuiButtonIcon, EuiLink, EuiButtonEmpty, EuiOverlayMask, EuiConfirmModal } from '@elastic/eui';
import RulesetHandler from './ruleset-handler';
export default class RulesetColumns {
constructor(tableProps) {
this.tableProps = tableProps;
this.rulesetHandler = RulesetHandler;
this.adminMode = this.tableProps.state.adminMode;
this.buildColumns = () => {
this.columns = {
rules: [
{
field: 'id',
name: 'ID',
align: 'left',
sortable: true,
width: '5%',
render: (value, item) => {
return (
<EuiToolTip position="top" content={`Show rule ID ${value} information`}>
<EuiLink onClick={async () => {
const result = await this.rulesetHandler.getRuleInformation(item.file, value);
this.tableProps.updateRuleInfo(result);
}
}>
{value}
</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'description',
name: 'Description',
align: 'left',
sortable: true,
width: '30%'
},
{
field: 'groups',
name: 'Groups',
align: 'left',
sortable: false,
width: '10%'
},
{
field: 'pci',
name: 'PCI',
align: 'left',
sortable: false,
width: '10%'
},
{
field: 'gdpr',
name: 'GDPR',
align: 'left',
sortable: false,
width: '10%'
},
{
field: 'hipaa',
name: 'HIPAA',
align: 'left',
sortable: false,
width: '10%'
},
{
field: 'nist-800-53',
name: 'NIST 800-53',
align: 'left',
sortable: false,
width: '10%'
},
{
field: 'level',
name: 'Level',
align: 'left',
sortable: true,
width: '5%'
},
{
field: 'file',
name: 'File',
align: 'left',
sortable: true,
width: '15%',
render: (value, item) => {
return (
<EuiToolTip position="top" content={`Show ${value} content`}>
<EuiLink onClick={async () => {
const noLocal = item.path.startsWith('ruleset/');
const result = await this.rulesetHandler.getRuleContent(value, noLocal);
const file = { name: value, content: result, path: item.path };
this.tableProps.updateFileContent(file);
}
}>
{value}
</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'path',
name: 'Path',
align: 'left',
sortable: true,
width: '10%'
}
],
decoders: [
{
field: 'name',
name: 'Name',
align: 'left',
sortable: true,
render: (value, item) => {
return (
<EuiToolTip position="top" content={`Show ${value} decoder information`}>
<EuiLink onClick={async () => {
const result = await this.rulesetHandler.getDecoderInformation(item.file, value);
this.tableProps.updateDecoderInfo(result);
}
}>
{value}
</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'details.program_name',
name: 'Program name',
align: 'left',
sortable: false
},
{
field: 'details.order',
name: 'Order',
align: 'left',
sortable: false
},
{
field: 'file',
name: 'File',
align: 'left',
sortable: true,
render: (value, item) => {
return (
<EuiToolTip position="top" content={`Show ${value} content`}>
<EuiLink onClick={async () => {
const noLocal = item.path.startsWith('ruleset/');
const result = await this.rulesetHandler.getDecoderContent(value, noLocal);
const file = { name: value, content: result, path: item.path };
this.tableProps.updateFileContent(file);
}
}>{value}</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'path',
name: 'Path',
align: 'left',
sortable: true
}
],
lists: [
{
field: 'name',
name: 'Name',
align: 'left',
sortable: true,
render: (value, item) => {
return (
<EuiToolTip position="top" content={`Show ${value} content`}>
<EuiLink onClick={async () => {
const result = await this.rulesetHandler.getCdbList(`${item.path}/${item.name}`);
const file = { name: item.name, content: result, path: item.path };
this.tableProps.updateListContent(file);
}}>
{value}
</EuiLink>
</EuiToolTip>
)
}
},
{
field: 'path',
name: 'Path',
align: 'left',
sortable: true
}
],
files: [
{
field: 'file',
name: 'File',
align: 'left',
sortable: true
},
{
name: 'Actions',
align: 'left',
render: item => {
if (item.path.startsWith('ruleset/')) {
return (
<EuiToolTip position="top" content={`Show ${item.file} content`}>
<EuiButtonIcon
aria-label="Show content"
iconType="eye"
onClick={async () => {
const result = await this.rulesetHandler.getFileContent(`${item.path}/${item.file}`);
const file = { name: item.file, content: result, path: item.path };
this.tableProps.updateFileContent(file);
}}
color="primary"
/>
</EuiToolTip>
)
} else {
return (
<div>
<div>
<EuiToolTip position="top" content={`Edit ${item.file} content`}>
<EuiButtonIcon
aria-label="Edit content"
iconType="pencil"
onClick={async () => {
const result = await this.rulesetHandler.getFileContent(`${item.path}/${item.file}`);
const file = { name: item.file, content: result, path: item.path };
this.tableProps.updateFileContent(file);
}}
color="primary"
/>
</EuiToolTip>
<EuiToolTip position="top" content={`Remove ${item.file} file`}>
<EuiButtonIcon
aria-label="Delete content"
iconType="trash"
onClick={() => {
this.tableProps.updateListItemsForRemove([item]);
this.tableProps.updateShowModal(true);
}}
color="danger"
/>
</EuiToolTip>
</div>
</div>
)
}
}
}
]
}
// If the admin mode is enabled the action column in CDB lists is shown
if (this.adminMode) {
this.columns.lists.push(
{
name: 'Actions',
align: 'left',
render: item => {
const defaultItems = this.tableProps.state.defaultItems;
return (
<div>
<EuiToolTip position="top" content={`Edit ${item.name} content`}>
<EuiButtonIcon
aria-label="Edit content"
iconType="pencil"
onClick={async () => {
const result = await this.rulesetHandler.getCdbList(`${item.path}/${item.name}`);
const file = { name: item.name, content: result, path: item.path };
this.tableProps.updateListContent(file);
}}
color="primary"
/>
</EuiToolTip>
<EuiToolTip position="top" content={(defaultItems.indexOf(`${item.path}/${item.name}`) === -1)? `Delete ${item.name}`: `The ${item.name} list cannot be deleted`}>
<EuiButtonIcon
aria-label="Show content"
iconType="trash"
onClick={async () => {
this.tableProps.updateListItemsForRemove([item]);
this.tableProps.updateShowModal(true);
}}
color="danger"
disabled={defaultItems.indexOf(`${item.path}/${item.name}`) !== -1}
/>
</EuiToolTip>
</div>
)
}
}
);
}
}
this.buildColumns();
}
}

View File

@ -0,0 +1,278 @@
/*
* Wazuh app - Ruleset handler service
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import { WzRequest } from '../../../../../../react-services/wz-request';
export default class RulesetHandler {
/**
* Get the information about a rule
* @param {String} file
* @param {Number} id
*/
static async getRuleInformation(file, id) {
try {
const result = await WzRequest.apiReq('GET', `/rules`, {
file
});
const info = ((result || {}).data || {}).data || false;
if (info) Object.assign(info, { current: id }); //Assign the current rule ID to filter later in the component
return info;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the default about a decoder
* @param {String} file
*/
static async getDecoderInformation(file, name) {
try {
const result = await WzRequest.apiReq('GET', `/decoders`, {
file
});
const info = ((result || {}).data || {}).data || false;
if (info) Object.assign(info, { current: name });
return info;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the rules
*/
static async getRules(filters = {}) {
try {
const result = await WzRequest.apiReq('GET', `/rules`, filters);
return (((result || {}).data || {}).data || {}).items || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the rules files
*/
static async getRulesFiles(filters = {}) {
try {
const result = await WzRequest.apiReq('GET', `/rules/files`, filters);
return (((result || {}).data || {}).data || {}).items || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the CDB lists
* @param {Object} filters
*/
static async getLists(filters = {}) {
try {
const result = await WzRequest.apiReq('GET', `/lists/files`, filters);
return (((result || {}).data || {}).data || {}).items || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the decoders
*/
static async getDecoders(filters = {}) {
try {
const result = await WzRequest.apiReq('GET', `/decoders`, filters);
return (((result || {}).data || {}).data || {}).items || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the decoder files
*/
static async getDecodersFiles(filters = {}) {
try {
const result = await WzRequest.apiReq('GET', `/decoders/files`, filters);
return (((result || {}).data || {}).data || {}).items || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the local rules
*/
static async getLocalRules() {
try {
const result = await WzRequest.apiReq('GET', `/rules`, {
path: 'etc/rules'
});
return (((result || {}).data || {}).data || {}).items || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the local decoders
*/
static async getLocalDecoders() {
try {
const result = await WzRequest.apiReq('GET', `/decoders`, {
path: 'etc/decoders'
});
return (((result || {}).data || {}).data || {}).items || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the content of a rule file
* @param {String} path
* @param {Boolean} nolocal
*/
static async getRuleContent(path, nolocal = true) {
try {
const _path = nolocal ? `ruleset/rules/${path}` : `etc/rules/${path}`;
const result = await this.getFileContent(_path);
return result;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the content of a decoder file
* @param {String} path
* @param {Boolean} nolocal
*/
static async getDecoderContent(path, nolocal = true) {
try {
const _path = nolocal
? `ruleset/decoders/${path}`
: `etc/decoders/${path}`;
const result = await this.getFileContent(_path);
return result;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the content of a CDB list
* @param {String} path
*/
static async getCdbList(path) {
try {
const result = await this.getFileContent(path);
return result;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Get the content of any type of file Rules, Decoders, CDB lists...
* @param {String} path
*/
static async getFileContent(path) {
try {
const result = await WzRequest.apiReq('GET', `/manager/files`, {
path: path
});
return ((result || {}).data || {}).data || false;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Send the rule content
* @param {String} rule
* @param {String} content
* @param {Boolean} overwrite
*/
static async sendRuleConfiguration(rule, content, overwrite) {
try {
const result = await WzRequest.apiReq(
'POST',
`/manager/files?path=etc/rules/${rule.file ||
rule}&overwrite=${overwrite}`,
{ content, origin: 'xmleditor' }
);
return result;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Send the decoders content
* @param {String} decoder
* @param {String} content
* @param {Boolean} overwrite
*/
static async sendDecoderConfiguration(decoder, content, overwrite) {
try {
const result = await WzRequest.apiReq(
'POST',
`/manager/files?path=etc/decoders/${decoder.file ||
decoder}&overwrite=${overwrite}`,
{ content, origin: 'xmleditor' }
);
return result;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Send the cdb list content
* @param {String} list
* @param {String} path
* @param {String} content
* @param {Boolean} overwrite
*/
static async sendCdbList(list, path, content, overwrite) {
try {
const result = await WzRequest.apiReq(
'POST',
`/manager/files?path=${path}/${list}&overwrite=${overwrite}`,
{ content, origin: 'raw' }
);
return result;
} catch (error) {
return Promise.reject(error);
}
}
/**
* Delete a file
* @param {String} file
* @param {String} path
*/
static async deleteFile(file, path) {
const fullPath = `${path}/${file}`;
try {
const result = await WzRequest.apiReq('DELETE', '/manager/files', {
path: fullPath
});
return result;
} catch (error) {
return Promise.reject(error);
}
}
}

View File

@ -0,0 +1,58 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import { WzRequest } from '../../../../../../react-services/wz-request';
const validateConfigAfterSent = async (node = false) => {
try {
const clusterStatus = await WzRequest.apiReq(
'GET',
`/cluster/status`,
{}
);
const clusterData = ((clusterStatus || {}).data || {}).data || {};
const isCluster =
clusterData.enabled === 'yes' && clusterData.running === 'yes';
let validation = false;
if (node && isCluster) {
validation = await WzRequest.apiReq(
'GET',
`/cluster/${node}/configuration/validation`,
{}
);
} else {
validation = isCluster
? await WzRequest.apiReq(
'GET',
`/cluster/configuration/validation`,
{}
)
: await WzRequest.apiReq(
'GET',
`/manager/configuration/validation`,
{}
);
}
const data = ((validation || {}).data || {}).data || {};
const isOk = data.status === 'OK';
if (!isOk && Array.isArray(data.details)) {
throw data;
}
return true;
} catch (error) {
return Promise.reject(error);
}
};
export default validateConfigAfterSent;

View File

@ -0,0 +1,32 @@
/*
* Wazuh app - React component for building the management welcome screen.
*
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*
* DELETE THIS WRAPPER WHEN WELCOME SCREEN WAS NOT BE CALLED FROM ANGULARJS
*/
import React, { Component } from 'react';
import WelcomeScreen from './welcome'
import WzReduxProvider from '../../../redux/wz-redux-provider';
export class WelcomeWrapper extends Component {
constructor(props) {
super(props);
this.state = {};
}
render() {
return (
<WzReduxProvider>
<WelcomeScreen {...this.props} />
</WzReduxProvider>
);
}
}

View File

@ -21,16 +21,27 @@ import {
EuiSpacer EuiSpacer
} from '@elastic/eui'; } from '@elastic/eui';
export class WelcomeScreen extends Component { import {
updateManagementSection,
} from '../../../redux/actions/managementActions';
import WzReduxProvider from '../../../redux/wz-redux-provider';
import { connect } from 'react-redux';
class WelcomeScreen extends Component {
constructor(props) { constructor(props) {
super(props); super(props);
this.state = {}; this.state = {};
} }
switchSection(section) {
this.props.switchTab(section, true);
this.props.changeManagementSection(section);
}
render() { render() {
return ( return (
<div> <WzReduxProvider>
<EuiFlexGroup> <EuiFlexGroup>
<EuiFlexItem> <EuiFlexItem>
<EuiPanel betaBadgeLabel="Administration"> <EuiPanel betaBadgeLabel="Administration">
@ -41,7 +52,7 @@ export class WelcomeScreen extends Component {
layout="horizontal" layout="horizontal"
icon={<EuiIcon size="xl" type="indexRollupApp" />} icon={<EuiIcon size="xl" type="indexRollupApp" />}
title="Ruleset" title="Ruleset"
onClick={() => this.props.switchTab('ruleset', true)} onClick={() => this.switchSection('ruleset')}
description="Manage your Wazuh cluster ruleset." description="Manage your Wazuh cluster ruleset."
/> />
</EuiFlexItem> </EuiFlexItem>
@ -50,7 +61,7 @@ export class WelcomeScreen extends Component {
layout="horizontal" layout="horizontal"
icon={<EuiIcon size="xl" type="usersRolesApp" />} icon={<EuiIcon size="xl" type="usersRolesApp" />}
title="Groups" title="Groups"
onClick={() => this.props.switchTab('groups', true)} onClick={() => this.switchSection('groups')}
description="Manage your agent groups." description="Manage your agent groups."
/> />
</EuiFlexItem> </EuiFlexItem>
@ -61,7 +72,7 @@ export class WelcomeScreen extends Component {
layout="horizontal" layout="horizontal"
icon={<EuiIcon size="xl" type="devToolsApp" />} icon={<EuiIcon size="xl" type="devToolsApp" />}
title="Configuration" title="Configuration"
onClick={() => this.props.switchTab('configuration', true)} onClick={() => this.switchSection('configuration')}
description="Manage your Wazuh cluster configuration." description="Manage your Wazuh cluster configuration."
/> />
</EuiFlexItem> </EuiFlexItem>
@ -78,7 +89,7 @@ export class WelcomeScreen extends Component {
layout="horizontal" layout="horizontal"
icon={<EuiIcon size="xl" type="uptimeApp" />} icon={<EuiIcon size="xl" type="uptimeApp" />}
title="Status" title="Status"
onClick={() => this.props.switchTab('status', true)} onClick={() => this.switchSection('status')}
description="Manage your Wazuh cluster status." description="Manage your Wazuh cluster status."
/> />
</EuiFlexItem> </EuiFlexItem>
@ -87,7 +98,7 @@ export class WelcomeScreen extends Component {
layout="horizontal" layout="horizontal"
icon={<EuiIcon size="xl" type="indexPatternApp" />} icon={<EuiIcon size="xl" type="indexPatternApp" />}
title="Cluster" title="Cluster"
onClick={() => this.props.switchTab('monitoring', true)} onClick={() => this.switchSection('monitoring')}
description="Visualize your Wazuh cluster." description="Visualize your Wazuh cluster."
/> />
</EuiFlexItem> </EuiFlexItem>
@ -98,7 +109,7 @@ export class WelcomeScreen extends Component {
layout="horizontal" layout="horizontal"
icon={<EuiIcon size="xl" type="filebeatApp" />} icon={<EuiIcon size="xl" type="filebeatApp" />}
title="Logs" title="Logs"
onClick={() => this.props.switchTab('logs', true)} onClick={() => this.switchSection('logs')}
description="Logs from your Wazuh cluster." description="Logs from your Wazuh cluster."
/> />
</EuiFlexItem> </EuiFlexItem>
@ -107,7 +118,7 @@ export class WelcomeScreen extends Component {
layout="horizontal" layout="horizontal"
icon={<EuiIcon size="xl" type="reportingApp" />} icon={<EuiIcon size="xl" type="reportingApp" />}
title="Reporting" title="Reporting"
onClick={() => this.props.switchTab('reporting', true)} onClick={() => this.switchSection('reporting')}
description="Check your stored Wazuh reports." description="Check your stored Wazuh reports."
/> />
</EuiFlexItem> </EuiFlexItem>
@ -115,7 +126,7 @@ export class WelcomeScreen extends Component {
</EuiPanel> </EuiPanel>
</EuiFlexItem> </EuiFlexItem>
</EuiFlexGroup> </EuiFlexGroup>
</div> </WzReduxProvider>
); );
} }
} }
@ -123,3 +134,11 @@ export class WelcomeScreen extends Component {
WelcomeScreen.propTypes = { WelcomeScreen.propTypes = {
switchTab: PropTypes.func switchTab: PropTypes.func
}; };
const mapDispatchToProps = (dispatch) => {
return {
changeManagementSection: section => dispatch(updateManagementSection(section)),
}
};
export default connect(null, mapDispatchToProps)(WelcomeScreen);

View File

@ -96,16 +96,6 @@ export class ConfigurationGroupsController {
this.$scope.switchAddingGroup = () => { this.$scope.switchAddingGroup = () => {
this.$scope.addingGroup = !this.$scope.addingGroup; this.$scope.addingGroup = !this.$scope.addingGroup;
}; };
this.$scope.createGroup = async name => {
try {
this.$scope.addingGroup = false;
await this.groupHandler.createGroup(name);
this.errorHandler.info(`Group ${name} has been created`);
} catch (error) {
this.errorHandler.handle(error.message || error);
}
this.$scope.$broadcast('wazuhSearch', {});
};
this.$scope.closeEditingFile(); this.$scope.closeEditingFile();
this.$scope.selectData; this.$scope.selectData;

View File

@ -66,7 +66,6 @@ export class FilesController {
this.$scope.closeEditingFile = (flag = false) => { this.$scope.closeEditingFile = (flag = false) => {
this.$scope.viewingDetail = false; this.$scope.viewingDetail = false;
this.$scope.editingFile = false; this.$scope.editingFile = false;
this.$scope.editingFile = false;
this.$scope.editorReadOnly = false; this.$scope.editorReadOnly = false;
this.$scope.fetchedXML = null; this.$scope.fetchedXML = null;
if (this.$scope.goBack || this.$scope.mctrl.openedFileDirect) { if (this.$scope.goBack || this.$scope.mctrl.openedFileDirect) {

File diff suppressed because it is too large Load Diff

View File

@ -25,8 +25,15 @@ import { ConfigurationGroupsController } from './config-groups';
import { EditionController } from './edition'; import { EditionController } from './edition';
import { FilesController } from './files'; import { FilesController } from './files';
import { WelcomeScreen } from './components/welcome'; import { WelcomeScreen } from './components/welcome';
import { WelcomeWrapper } from './components/welcome-wrapper';
import { ReportingTable } from './components/reporting-table'; import { ReportingTable } from './components/reporting-table';
import { AgentsInGroupTable } from './components/agents-groups-table';
import { FilesInGroupTable } from './components/files-group-table';
import { GroupsTable } from './components/management/groups/groups-table';
import { UploadFiles } from './components/upload-files'; import { UploadFiles } from './components/upload-files';
import WzRuleset from './components/management/ruleset/main-ruleset';
import WzManagement from './components/management/management-provider';
import WzManagementMain from './components/management/management-main';
const app = uiModules.get('app/wazuh', []); const app = uiModules.get('app/wazuh', []);
@ -43,7 +50,15 @@ app
.controller('configurationRulesetController', ConfigurationRulesetController) .controller('configurationRulesetController', ConfigurationRulesetController)
.controller('configurationGroupsController', ConfigurationGroupsController) .controller('configurationGroupsController', ConfigurationGroupsController)
.controller('editionController', EditionController) .controller('editionController', EditionController)
.controller('filesController', FilesController) .controller('filesController', FilesController)
.value('WelcomeScreenManagement', WelcomeScreen) .value('WelcomeScreenManagement', WelcomeScreen)
.value('WelcomeWrapper', WelcomeWrapper)
.value('ReportingTable', ReportingTable) .value('ReportingTable', ReportingTable)
.value('UploadFiles', UploadFiles)
.value('WzRuleset', WzRuleset)
.value('WzManagement', WzManagement)
.value('WzManagementMain', WzManagementMain)
.value('GroupsTable', GroupsTable)
.value('AgentsInGroupTable', AgentsInGroupTable)
.value('FilesInGroupTable', FilesInGroupTable)
.value('UploadFiles', UploadFiles); .value('UploadFiles', UploadFiles);

View File

@ -156,16 +156,6 @@ export class ManagementController {
switchTab: (tab, setNav) => this.switchTab(tab, setNav) switchTab: (tab, setNav) => this.switchTab(tab, setNav)
}; };
this.rulesetTabsProps = {
clickAction: tab => this.setRulesTab(tab),
selectedTab: this.rulesetTab || 'rules',
tabs: [
{ id: 'rules', name: 'Rules' },
{ id: 'decoders', name: 'Decoders' },
{ id: 'lists', name: 'Lists' }
]
};
this.managementTabsProps = { this.managementTabsProps = {
clickAction: tab => this.switchTab(tab, true), clickAction: tab => this.switchTab(tab, true),
selectedTab: this.tab, selectedTab: this.tab,
@ -182,6 +172,12 @@ export class ManagementController {
close: () => this.openLogtest(), close: () => this.openLogtest(),
showClose: true showClose: true
}; };
this.managementProps = {
switchTab: (section) => this.switchTab(section, true),
section: "",
groupsProps: {},
}
} }
/** /**
@ -324,6 +320,7 @@ export class ManagementController {
this.currentList = false; this.currentList = false;
this.managementTabsProps.selectedTab = this.tab; this.managementTabsProps.selectedTab = this.tab;
} }
this.managementProps.section = this.tab === 'ruleset' ? this.rulesetTab : this.tab;
this.$location.search('tab', this.tab); this.$location.search('tab', this.tab);
this.loadNodeList(); this.loadNodeList();
} }

View File

@ -1,5 +1,5 @@
/* /*
* Wazuh app - Ruleset controllers * @param {Objet} * Wazuh app - Ruleset controllers
* Copyright (C) 2015-2019 Wazuh, Inc. * Copyright (C) 2015-2019 Wazuh, Inc.
* *
* This program is free software; you can redistribute it and/or modify * This program is free software; you can redistribute it and/or modify
@ -13,185 +13,296 @@ import * as FileSaver from '../../services/file-saver';
import { colors } from './colors'; import { colors } from './colors';
export function RulesController(
$scope, export class RulesController {
$sce, /**
errorHandler, * Class constructor
appState, * @param {Objet} $scope
csvReq, * @param {Objet} $sce
wzTableFilter, * @param {Objet} errorHandler
$location, * @param {Objet} appState
apiReq, * @param {Objet} csvReq
wazuhConfig, * @param {Objet} wzTableFilter
rulesetHandler * @param {Objet} $location
) { * @param {Objet} apiReq
$scope.overwriteError = false; * @param {Objet} wazuhConfig
$scope.isObject = item => typeof item === 'object'; * @param {Objet} rulesetHandler
$scope.mctrl = $scope.$parent.$parent.$parent.mctrl; */
$scope.mctrl.showingLocalRules = false;
$scope.mctrl.onlyLocalFiles = false; constructor(
$scope.appliedFilters = []; $scope,
$sce,
errorHandler,
appState,
csvReq,
wzTableFilter,
$location,
apiReq,
wazuhConfig,
rulesetHandler
) {
this.scope = $scope;
this.sce = $sce;
this.errorHandler = errorHandler;
this.appState = appState;
this.csvReq = csvReq;
this.wzTableFilter = wzTableFilter;
this.location = $location;
this.apiReq = apiReq;
this.wazuhConfig = wazuhConfig;
this.rulesetHandler = rulesetHandler;
this.overwriteError = false;
this.isObject = item => typeof item === 'object';
this.mctrl = this.scope.mctrl;
this.mctrl.showingLocalRules = false;
this.mctrl.onlyLocalFiles = false;
this.appliedFilters = [];
}
async $onInit() {
// Props
this.mainRulesProps = {
section: 'rules',
wzReq: (method, path, body) => this.apiReq.request(method, path, body)
}
//Initialization
this.searchTerm = '';
this.viewingDetail = false;
this.isArray = Array.isArray;
const configuration = this.wazuhConfig.getConfig();
this.adminMode = !!(configuration || {}).admin;
// Listeners
this.scope.$on('closeRuleView', () => {
this.closeDetailView();
});
this.scope.$on('rulesetIsReloaded', () => {
this.viewingDetail = false;
this.scope.$applyAsync();
});
this.scope.$on('wazuhShowRule', (event, parameters) => {
this.currentRule = parameters.rule;
this.scope.$emit('setCurrentRule', { currentRule: this.currentRule });
if (!(Object.keys((this.currentRule || {}).details || {}) || []).length) {
this.currentRule.details = false;
}
this.viewingDetail = true;
this.scope.$applyAsync();
});
this.scope.$on('showRestart', () => {
this.restartBtn = true;
this.scope.$applyAsync();
});
this.scope.$on('showSaveAndOverwrite', () => {
this.overwriteError = true;
this.scope.$applyAsync();
});
this.scope.$on('applyFilter', (event, parameters) => {
this.scope.search(parameters.filter, true);
});
this.scope.$on('viewFileOnlyTable', (event, parameters) => {
parameters.viewingDetail = this.viewingDetail;
this.mctrl.switchFilesSubTab('rules', { parameters });
});
if (this.location.search() && this.location.search().ruleid) {
const incomingRule = this.location.search().ruleid;
this.location.search('ruleid', null);
try {
const data = await this.apiReq.request('get', `/rules/${incomingRule}`, {});
const response = (((data || {}).data || {}).data || {}).items || [];
if (response.length) {
const result = response.filter(rule => rule.details.overwrite);
this.currentRule = result.length ? result[0] : response[0];
}
this.scope.$emit('setCurrentRule', { currentRule: this.currentRule });
if (
!(Object.keys((this.currentRule || {}).details || {}) || []).length
) {
this.currentRule.details = false;
}
this.viewingDetail = true;
this.scope.$applyAsync();
} catch (error) {
this.errorHandler.handle(
`Error fetching rule: ${incomingRule} from the Wazuh API`
)
}
}
}
/** /**
* This performs a search with a given term * This performs a search with a given term
* @param {String} term
* @param {Boolean} fromClick
*/ */
$scope.search = (term, fromClick = false) => { search(term, fromClick = false) {
let clearInput = true; let clearInput = true;
if (term && term.startsWith('group:') && term.split('group:')[1].trim()) { if (term && term.startsWith('group:') && term.split('group:')[1].trim()) {
$scope.custom_search = ''; this.custom_search = '';
const filter = { name: 'group', value: term.split('group:')[1].trim() }; const filter = { name: 'group', value: term.split('group:')[1].trim() };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'group' item => item.name !== 'group'
); );
$scope.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.appliedFilters.push(filter);
this.scope.$broadcast('wazuhFilter', { filter });
} else if ( } else if (
term && term &&
term.startsWith('level:') && term.startsWith('level:') &&
term.split('level:')[1].trim() term.split('level:')[1].trim()
) { ) {
$scope.custom_search = ''; this.custom_search = '';
const filter = { name: 'level', value: term.split('level:')[1].trim() }; const filter = { name: 'level', value: term.split('level:')[1].trim() };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'level' item => item.name !== 'level'
); );
$scope.appliedFilters.push(filter); this.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.scope.$broadcast('wazuhFilter', { filter });
} else if ( } else if (
term && term &&
term.startsWith('pci:') && term.startsWith('pci:') &&
term.split('pci:')[1].trim() term.split('pci:')[1].trim()
) { ) {
$scope.custom_search = ''; this.custom_search = '';
const filter = { name: 'pci', value: term.split('pci:')[1].trim() }; const filter = { name: 'pci', value: term.split('pci:')[1].trim() };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'pci' item => item.name !== 'pci'
); );
$scope.appliedFilters.push(filter); this.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.scope.$broadcast('wazuhFilter', { filter });
} else if ( } else if (
term && term &&
term.startsWith('gdpr:') && term.startsWith('gdpr:') &&
term.split('gdpr:')[1].trim() term.split('gdpr:')[1].trim()
) { ) {
$scope.custom_search = ''; this.custom_search = '';
const filter = { name: 'gdpr', value: term.split('gdpr:')[1].trim() }; const filter = { name: 'gdpr', value: term.split('gdpr:')[1].trim() };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'gdpr' item => item.name !== 'gdpr'
); );
$scope.appliedFilters.push(filter); this.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.scope.$broadcast('wazuhFilter', { filter });
} else if ( } else if (
term && term &&
term.startsWith('hipaa:') && term.startsWith('hipaa:') &&
term.split('hipaa:')[1].trim() term.split('hipaa:')[1].trim()
) { ) {
$scope.custom_search = ''; this.custom_search = '';
const filter = { name: 'hipaa', value: term.split('hipaa:')[1].trim() }; const filter = { name: 'hipaa', value: term.split('hipaa:')[1].trim() };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'hipaa' item => item.name !== 'hipaa'
); );
$scope.appliedFilters.push(filter); this.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.scope.$broadcast('wazuhFilter', { filter });
} else if ( } else if (
term && term &&
term.startsWith('nist-800-53:') && term.startsWith('nist-800-53:') &&
term.split('nist-800-53:')[1].trim() term.split('nist-800-53:')[1].trim()
) { ) {
$scope.custom_search = ''; this.custom_search = '';
const filter = { const filter = {
name: 'nist-800-53', name: 'nist-800-53',
value: term.split('nist-800-53:')[1].trim() value: term.split('nist-800-53:')[1].trim()
}; };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'nist-800-53' item => item.name !== 'nist-800-53'
); );
$scope.appliedFilters.push(filter); this.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.scope.$broadcast('wazuhFilter', { filter });
} else if ( } else if (
term && term &&
term.startsWith('file:') && term.startsWith('file:') &&
term.split('file:')[1].trim() term.split('file:')[1].trim()
) { ) {
$scope.custom_search = ''; this.custom_search = '';
const filter = { name: 'file', value: term.split('file:')[1].trim() }; const filter = { name: 'file', value: term.split('file:')[1].trim() };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'file' item => item.name !== 'file'
); );
$scope.appliedFilters.push(filter); this.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.scope.$broadcast('wazuhFilter', { filter });
} else if ( } else if (
term && term &&
term.startsWith('path:') && term.startsWith('path:') &&
term.split('path:')[1].trim() term.split('path:')[1].trim()
) { ) {
$scope.custom_search = ''; this.custom_search = '';
if (!$scope.mctrl.showingLocalRules) { if (!this.mctrl.showingLocalRules) {
const filter = { name: 'path', value: term.split('path:')[1].trim() }; const filter = { name: 'path', value: term.split('path:')[1].trim() };
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== 'path' item => item.name !== 'path'
); );
$scope.appliedFilters.push(filter); this.appliedFilters.push(filter);
$scope.$broadcast('wazuhFilter', { filter }); this.scope.$broadcast('wazuhFilter', { filter });
} }
} else { } else {
clearInput = false; clearInput = false;
$scope.$broadcast('wazuhSearch', { term, removeFilters: 0 }); this.scope.$broadcast('wazuhSearch', { term, removeFilters: 0 });
} }
if (clearInput && !fromClick) { if (clearInput && !fromClick) {
const searchBar = $('#search-input-rules'); const searchBar = $('#search-input-rules');
searchBar.val(''); searchBar.val('');
} }
$scope.$applyAsync(); this.scope.$applyAsync();
}; }
/** /**
* This show us if new filter is already included in filters * This show us if new filter is already included in filters
* @param {String} filterName * @param {String} filterName
*/ */
$scope.includesFilter = filterName => includesFilter(filterName) {
$scope.appliedFilters.map(item => item.name).includes(filterName); return this.appliedFilters.map(item => item.name).includes(filterName);
}
/** /**
* Get a filter given its name * Get a filter given its name
* @param {String} filterName * @param {String} filterName
*/ */
$scope.getFilter = filterName => { getFilter(filterName) {
const filtered = $scope.appliedFilters.filter( const filtered = this.appliedFilters.filter(
item => item.name === filterName item => item.name === filterName
); );
return filtered.length ? filtered[0].value : ''; const filter = filtered.length ? filtered[0].value : '';
}; return filter;
}
$scope.switchLocalRules = () => {
$scope.removeFilter('path'); /**
if (!$scope.mctrl.showingLocalRules) { * Swotch between tabs
$scope.appliedFilters.push({ name: 'path', value: 'etc/rules' }); */
} switchLocalRules() {
}; this.removeFilter('path');
if (!this.mctrl.showingLocalRules) this.appliedFilters.push({ name: 'path', value: 'etc/rules' });
}
/** /**
* This a the filter given its name * This a the filter given its name
* @param {String} filterName * @param {String} filterName
*/ */
$scope.removeFilter = filterName => { removeFilter(filterName) {
$scope.appliedFilters = $scope.appliedFilters.filter( this.appliedFilters = this.appliedFilters.filter(
item => item.name !== filterName item => item.name !== filterName
); );
return $scope.$broadcast('wazuhRemoveFilter', { filterName }); return this.scope.$broadcast('wazuhRemoveFilter', { filterName });
}; }
//Initialization
$scope.searchTerm = '';
$scope.viewingDetail = false;
$scope.isArray = Array.isArray;
const configuration = wazuhConfig.getConfig();
$scope.adminMode = !!(configuration || {}).admin;
/** /**
* This set color to a given rule argument * This set color to a given rule argument
*/ * @param {String} ruleArg
$scope.colorRuleArg = ruleArg => { */
colorRuleArg(ruleArg) {
ruleArg = ruleArg.toString(); ruleArg = ruleArg.toString();
let valuesArray = ruleArg.match(/\$\(((?!<\/span>).)*?\)(?!<\/span>)/gim); let valuesArray = ruleArg.match(/\$\(((?!<\/span>).)*?\)(?!<\/span>)/gim);
let coloredString = ruleArg; let coloredString = ruleArg;
@ -204,38 +315,28 @@ export function RulesController(
coloredString = coloredString.replace( coloredString = coloredString.replace(
/\$\(((?!<\/span>).)*?\)(?!<\/span>)/im, /\$\(((?!<\/span>).)*?\)(?!<\/span>)/im,
'<span style="color: ' + '<span style="color: ' +
colors[i] + colors[i] +
' ">' + ' ">' +
valuesArray[i] + valuesArray[i] +
'</span>' '</span>'
); );
} }
} }
return $sce.trustAsHtml(coloredString); return this.sce.trustAsHtml(coloredString);
}; }
$scope.$on('closeRuleView', () => {
$scope.closeDetailView();
});
// Reloading event listener
$scope.$on('rulesetIsReloaded', () => {
$scope.viewingDetail = false;
$scope.$applyAsync();
});
/** /**
* Get full data on CSV format * Get full data on CSV format
*/ */
$scope.downloadCsv = async () => { async downloadCsv() {
try { try {
errorHandler.info('Your download should begin automatically...', 'CSV'); this.errorHandler.info('Your download should begin automatically...', 'CSV');
const currentApi = JSON.parse(appState.getCurrentAPI()).id; const currentApi = JSON.parse(this.appState.getCurrentAPI()).id;
const output = await csvReq.fetch( const output = await this.csvReq.fetch(
'/rules', '/rules',
currentApi, currentApi,
wzTableFilter.get() this.wzTableFilter.get()
); );
const blob = new Blob([output], { type: 'text/csv' }); // eslint-disable-line const blob = new Blob([output], { type: 'text/csv' }); // eslint-disable-line
@ -243,188 +344,164 @@ export function RulesController(
return; return;
} catch (error) { } catch (error) {
errorHandler.handle(error, 'Download CSV'); this.errorHandler.handle(error, 'Download CSV');
} }
return; return;
}; }
/** /**
* This function takes back to the list but adding a filter from the detail view * This function takes back to the list but adding a filter from the detail view
*/ * @param {String} name
$scope.addDetailFilter = (name, value) => { * @param {String} value
*/
addDetailFilter(name, value) {
// Go back to the list // Go back to the list
$scope.closeDetailView(); this.closeDetailView();
$scope.search(`${name}:${value}`); this.search(`${name}:${value}`);
}; }
$scope.openFile = (file, path) => { /**
* Open a file
* @param {String} file
* @param {String} path
*/
openFile(file, path) {
if (file && path) { if (file && path) {
$scope.mctrl.switchFilesSubTab('rules', { this.mctrl.switchFilesSubTab('rules', {
parameters: { parameters: {
file: { file, path }, file: { file, path },
path, path,
viewingDetail: $scope.viewingDetail viewingDetail: this.viewingDetail
} }
}); });
} }
}; }
//listeners /**
$scope.$on('wazuhShowRule', (event, parameters) => { * Open an edit a rules file
$scope.currentRule = parameters.rule; */
$scope.$emit('setCurrentRule', { currentRule: $scope.currentRule }); async editRulesConfig() {
if (!(Object.keys(($scope.currentRule || {}).details || {}) || []).length) { this.editingFile = true;
$scope.currentRule.details = false;
}
$scope.viewingDetail = true;
$scope.$applyAsync();
});
$scope.editRulesConfig = async () => {
$scope.editingFile = true;
try { try {
$scope.fetchedXML = await rulesetHandler.getRuleConfiguration( this.fetchedXML = await this.rulesetHandler.getRuleConfiguration(
$scope.currentRule.file this.currentRule.file
); );
$location.search('editingFile', true); this.location.search('editingFile', true);
appState.setNavigation({ status: true }); this.appState.setNavigation({ status: true });
$scope.$applyAsync(); this.scope.$applyAsync();
$scope.$broadcast('fetchedFile', { data: $scope.fetchedXML }); this.scope.$broadcast('fetchedFile', { data: this.scope.fetchedXML });
} catch (error) { } catch (error) {
$scope.fetchedXML = null; this.fetchedXML = null;
errorHandler.handle(error, 'Fetch file error'); this.errorHandler.handle(error, 'Fetch file error');
} }
}; }
$scope.closeEditingFile = async () => {
if ($scope.currentRule) { /**
* Close the edition of the file
*/
async closeEditingFile() {
if (this.currentRule) {
try { try {
const ruleReloaded = await apiReq.request( const ruleReloaded = await this.apiReq.request(
'GET', 'GET',
`/rules/${$scope.currentRule.id}`, `/rules/${this.currentRule.id}`,
{} {}
); );
const response = const response =
(((ruleReloaded || {}).data || {}).data || {}).items || []; (((ruleReloaded || {}).data || {}).data || {}).items || [];
if (response.length) { if (response.length) {
const result = response.filter(rule => rule.details.overwrite); const result = response.filter(rule => rule.details.overwrite);
$scope.currentRule = result.length ? result[0] : response[0]; this.currentRule = result.length ? result[0] : response[0];
} else { } else {
$scope.currentRule = false; this.currentRule = false;
$scope.closeDetailView(true); this.closeDetailView(true);
} }
$scope.fetchedXML = false; this.fetchedXML = false;
} catch (error) { } catch (error) {
errorHandler.handle(error.message || error); this.errorHandler.handle(error.message || error);
} }
} }
$scope.editingFile = false; this.editingFile = false;
$scope.$applyAsync(); this.scope.$applyAsync();
appState.setNavigation({ status: true }); this.appState.setNavigation({ status: true });
$scope.$broadcast('closeEditXmlFile', {}); this.scope.$broadcast('closeEditXmlFile', {});
$scope.$applyAsync(); this.scope.$applyAsync();
}; }
$scope.xmlIsValid = valid => { /**
$scope.xmlHasErrors = valid; * Checks if the XML is false
$scope.$applyAsync(); */
}; xmlIsValid() {
this.xmlHasErrors = valid;
this.scope.$applyAsync();
}
/** /**
* This function changes to the rules list view * This function changes to the rules list view
*/ */
$scope.closeDetailView = clear => { closeDetailView(clear) {
$scope.mctrl.showingLocalRules = !$scope.mctrl.showingLocalRules; this.mctrl.showingLocalRules = !this.mctrl.showingLocalRules;
if (clear) if (clear)
$scope.appliedFilters = $scope.appliedFilters.slice( this.appliedFilters = this.appliedFilters.slice(
0, 0,
$scope.appliedFilters.length - 1 this.appliedFilters.length - 1
);
$scope.viewingDetail = false;
$scope.currentRule = false;
$scope.closeEditingFile();
$scope.$emit('removeCurrentRule');
$scope.switchLocalRules();
$scope.mctrl.showingLocalRules = !$scope.mctrl.showingLocalRules;
$scope.$applyAsync();
};
if ($location.search() && $location.search().ruleid) {
const incomingRule = $location.search().ruleid;
$location.search('ruleid', null);
apiReq
.request('get', `/rules/${incomingRule}`, {})
.then(data => {
const response = (((data || {}).data || {}).data || {}).items || [];
if (response.length) {
const result = response.filter(rule => rule.details.overwrite);
$scope.currentRule = result.length ? result[0] : response[0];
}
$scope.$emit('setCurrentRule', { currentRule: $scope.currentRule });
if (
!(Object.keys(($scope.currentRule || {}).details || {}) || []).length
) {
$scope.currentRule.details = false;
}
$scope.viewingDetail = true;
$scope.$applyAsync();
})
.catch(() =>
errorHandler.handle(
`Error fetching rule: ${incomingRule} from the Wazuh API`
)
); );
this.viewingDetail = false;
this.currentRule = false;
this.closeEditingFile();
this.scope.$emit('removeCurrentRule');
this.switchLocalRules();
this.mctrl.showingLocalRules = !this.mctrl.showingLocalRules;
this.scope.$applyAsync();
} }
$scope.toggleSaveConfig = () => { /**
$scope.doingSaving = false; * Enable the save
$scope.$applyAsync(); */
}; toggleSaveConfig() {
this.doingSaving = false;
this.scope.$applyAsync();
}
$scope.toggleRestartMsg = () => { /**
$scope.restartBtn = false; * Enable the restart
$scope.$applyAsync(); */
}; toggleRestartMsg() {
this.restartBtn = false;
this.scope.$applyAsync();
}
$scope.cancelSaveAndOverwrite = () => { /**
$scope.overwriteError = false; * Cancel the save
$scope.$applyAsync(); */
}; cancelSaveAndOverwrite() {
this.overwriteError = false;
this.scope.$applyAsync();
}
$scope.doSaveConfig = () => { /**
const clusterInfo = appState.getClusterInfo(); * Emit the event to save the config
*/
doSaveConfig() {
const clusterInfo = this.appState.getClusterInfo();
const showRestartManager = const showRestartManager =
clusterInfo.status === 'enabled' ? 'cluster' : 'manager'; clusterInfo.status === 'enabled' ? 'cluster' : 'manager';
$scope.doingSaving = true; this.doingSaving = true;
const objParam = { const objParam = {
rule: $scope.currentRule, rule: this.currentRule,
showRestartManager, showRestartManager,
isOverwrite: !!$scope.overwriteError isOverwrite: !!this.overwriteError
}; }
$scope.$broadcast('saveXmlFile', objParam); this.scope.$broadcast('saveXmlFile', objParam);
}; }
$scope.restart = () => { /**
$scope.$emit('performRestart', {}); * Emit the event to restart
}; */
restart() {
$scope.$on('showRestart', () => { this.scope.$emit('performRestart', {});
$scope.restartBtn = true; }
$scope.$applyAsync();
});
$scope.$on('showSaveAndOverwrite', () => {
$scope.overwriteError = true;
$scope.$applyAsync();
});
$scope.$on('applyFilter', (event, parameters) => {
$scope.search(parameters.filter, true);
});
$scope.$on('viewFileOnlyTable', (event, parameters) => {
parameters.viewingDetail = $scope.viewingDetail;
$scope.mctrl.switchFilesSubTab('rules', { parameters });
});
} }

View File

@ -1205,4 +1205,4 @@ function discoverController(
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
init(); init();
} }

View File

@ -16,6 +16,10 @@
/* Custom healthcheck and blank screen styles */ /* Custom healthcheck and blank screen styles */
.kbnGlobalBannerList{
display: none;
}
.error-notify { .error-notify {
font-size: 20px; font-size: 20px;
color: black; color: black;
@ -164,7 +168,7 @@
} }
.euiFlexGroup .euiFlexGroup:hover { .euiFlexGroup .euiFlexGroup:hover {
background: #fafcfe; //background: #fafcfe;
} }
/* Custom Manager/Status styles */ /* Custom Manager/Status styles */
@ -1011,7 +1015,7 @@ discover-app-w .container-fluid {
} }
.application{ .application{
//background: #fafbfd; background: #fafbfd;
} }
.application.tab-health-check wz-menu{ .application.tab-health-check wz-menu{
@ -1259,4 +1263,50 @@ md-chips.md-default-theme .md-chips, md-chips .md-chips{
.table-vis-container{ .table-vis-container{
overflow: auto !important; overflow: auto !important;
}
.subdued-background {
background: #d3dae6;
}
.subdued-color {
color: #808184;
}
.react-code-mirror {
height: 73vh;
border: solid 1px #d9d9d9;
}
.react-code-mirror > .CodeMirror.CodeMirror-wrap.cm-s-default{
height: 100% !important;
}
.wz-form-row {
width: 100% !important;
max-width: none !important;
}
.wz-form-row .euiComboBox {
width: 100% !important;
max-width: none !important;
}
.wz-form-row .euiFormControlLayout {
width: 100% !important;
max-width: none !important;
}
.wz-form-row .euiComboBox__inputWrap.euiComboBox__inputWrap-isClearable {
width: 100% !important;
max-width: none !important;
}
.sideMenuButton .euiButtonEmpty__content{
justify-content: left!important;
}
.sideBarContent{
float: right;
width: calc(~'100vw - 240px');
} }

View File

@ -20,9 +20,7 @@ body,
button:not(.fa):not(.fa-times), button:not(.fa):not(.fa-times),
textarea, textarea,
input, input,
select, select{
.wz-chip {
font-family: 'Open Sans', Helvetica, Arial, sans-serif !important;
font-size: 14px; font-size: 14px;
} }

33
public/react-services/wz-csv.js vendored Normal file
View File

@ -0,0 +1,33 @@
/*
* Wazuh app - API request service
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import { WzRequest } from './wz-request';
import * as FileSaver from '../services/file-saver';
/**
* Generates a CSV through the given data
* @param {String} path
* @param {Array} filters
* @param {String} exportName
*/
const exportCsv = async (path, filters = [], exportName = 'data') => {
try {
const data = await WzRequest.csvReq(path, filters);
const output = data.data ? [data.data] : [];
const blob = new Blob(output, { type: 'text/csv' });
FileSaver.saveAs(blob, `${exportName}.csv`);
} catch (error) {
return Promise.reject(error);
}
}
export default exportCsv;

113
public/react-services/wz-request.js vendored Normal file
View File

@ -0,0 +1,113 @@
/*
* Wazuh app - API request service
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import axios from 'axios';
import chrome from 'ui/chrome';
export class WzRequest {
/**
* Permorn a generic request
* @param {String} method
* @param {String} path
* @param {Object} payload
*/
static async genericReq(method, path, payload = null) {
try {
if (!method || !path) {
throw new Error('Missing parameters');
}
const url = chrome.addBasePath(path);
const options = {
method: method,
headers: { 'Content-Type': 'application/json', 'kbn-xsrf': 'kibana' },
url: url,
data: payload,
timeout: 20000
};
const data = await axios(options);
if (data.error) {
throw new Error(data.error);
}
return Promise.resolve(data);
} catch (error) {
return (((error || {}).response || {}).data || {}).message || false
? Promise.reject(error.response.data.message)
: Promise.reject(error.response.message || error.response || error);
}
}
/**
* Perform a request to the Wazuh API
* @param {String} method Eg. GET, PUT, POST, DELETE
* @param {String} path API route
* @param {Object} body Request body
*/
static async apiReq(method, path, body) {
try {
if (!method || !path || !body) {
throw new Error('Missing parameters');
}
const id = this.getCookie('API').id;
const requestData = { method, path, body, id };
const data = await this.genericReq('POST', '/api/request', requestData);
return Promise.resolve(data);
} catch (error) {
return ((error || {}).data || {}).message || false
? Promise.reject(error.data.message)
: Promise.reject(error.message || error);
}
}
/**
* Perform a request to generate a CSV
* @param {String} path
* @param {Object} filters
*/
static async csvReq(path, filters){
try {
if (!path || !filters) {
throw new Error('Missing parameters');
}
const id = this.getCookie('API').id;
const requestData = { path, id, filters };
const data = await this.genericReq('POST', '/api/csv', requestData);
return Promise.resolve(data);
} catch (error) {
return ((error || {}).data || {}).message || false
? Promise.reject(error.data.message)
: Promise.reject(error.message || error);
}
}
/**
* Gets the value from a cookie
* @param {String} cookieName
*/
static getCookie(cookieName) {
try {
const value = "; " + document.cookie;
const parts = value.split("; " + cookieName + "=");
const cookie = parts.length === 2 ? parts.pop().split(';').shift() : false;
if (cookie && decodeURIComponent(cookie)) {
const decode = decodeURIComponent(cookie);
try {
return JSON.parse(JSON.parse(decode));
} catch (error) {
return decode;
}
} else {
throw `Cannot get ${cookieName}`;
}
} catch (error) {
throw new Error(error);
}
}
}

View File

@ -0,0 +1,7 @@
export const updateManagementSection = (section) => {
return {
type: 'UPDATE_MANAGEMENT_SECTION',
section
}
}

View File

@ -0,0 +1,212 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
/**
* Update the ruleset section
* @param {String} section
*/
export const updateRulesetSection = (section) => {
return {
type: 'UPDATE_RULESET_SECTION',
section
}
}
/**
* Update the files content
* @param {String} content
*/
export const updateFileContent = (content) => {
return {
type: 'UPDATE_FILE_CONTENT',
content: content
}
}
/**
* Toggle the modal confirm of the ruleset table
* @param {Boolean} showModal
*/
export const updateShowModal = (showModal) => {
return {
type: 'UPDATE_SHOW_MODAL',
showModal: showModal
}
}
/**
* Update the list of items to remove
* @param {Array} itemList
*/
export const updateListItemsForRemove = (itemList) => {
return {
type: 'UPDATE_LIST_ITEMS_FOR_REMOVE',
itemList: itemList
}
}
export const updateSortField = (sortField) => {
return {
type: 'UPDATE_SORT_FIELD',
sortField: sortField
}
}
export const updateSortDirection = (sortDirection) => {
return {
type: 'UPDATE_SORT_DIRECTION',
sortDirection: sortDirection
}
}
export const updateDefaultItems = (defaultItems) => {
return {
type: 'UPDATE_DEFAULT_ITEMS',
defaultItems: defaultItems
}
}
/**
* Update the lists content
* @param {String} content
*/
export const updateListContent = (content) => {
return {
type: 'UPDATE_LIST_CONTENT',
content: content
}
}
/**
* Update the loading status
* @param {Boolean} loading
*/
export const updateLoadingStatus = (loading) => {
return {
type: 'UPDATE_LOADING_STATUS',
status: loading
}
}
/**
* Reset the ruleset store
*/
export const resetRuleset = () => {
return {
type: 'RESET'
}
}
/**
* Toggle show files
* @param {Boolean} status
*/
export const toggleShowFiles = (status) => {
return {
type: 'TOGGLE_SHOW_FILES',
status: status
}
}
/**
* Update the rule info
* @param {String} info
*/
export const updateRuleInfo = (info) => {
return {
type: 'UPDATE_RULE_INFO',
info: info
}
}
/**
* Update the decoder info
* @param {String} info
*/
export const updateDecoderInfo = (info) => {
return {
type: 'UPDATE_DECODER_INFO',
info: info
}
}
/**
* Toggle the updating of the table
* @param {Boolean} isProcessing
*/
export const updateIsProcessing = (isProcessing) => {
return {
type: 'UPDATE_IS_PROCESSING',
isProcessing: isProcessing
}
}
/**
* Set the page index value of the table
* @param {Number} pageIndex
*/
export const updatePageIndex = (pageIndex) => {
return {
type: 'UPDATE_PAGE_INDEX',
pageIndex: pageIndex
}
}
/**
* Update the filters
* @param {string} filters
*/
export const updateFilters = (filters) => {
return {
type: 'UPDATE_FILTERS',
filters: filters
}
}
export const cleanFilters = () => {
return {
type: 'CLEAN_FILTERS'
}
}
export const cleanInfo = () => {
return {
type: 'CLEAN_INFO'
}
}
export const cleanFileContent = () => {
return {
type: 'CLEAN_CONTENT'
}
}
export const updateAdminMode = status => {
return {
type: 'UPDATE_ADMIN_MODE',
status: status
}
}
export const updteAddingRulesetFile = content => {
return {
type: 'UPDATE_ADDING_RULESET_FILE',
content: content
}
}
export const updateError = error => {
return {
type: 'UPDATE_ERROR',
error: error
}
}

View File

@ -0,0 +1,14 @@
const initialState = { section: '' };
export default (state = initialState, action) => {
if (action.type === 'UPDATE_MANAGEMENT_SECTION') {
return {
...state,
section: action.section
};
}
return state;
};
export const changeManagementSection = state => state.managementReducers.section;

View File

@ -0,0 +1,21 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import { combineReducers } from 'redux';
import rulesetReducers from './rulesetReducers';
import managementReducers from './managementReducers';
export default combineReducers({
rulesetReducers,
managementReducers
})

View File

@ -0,0 +1,87 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
const initialState = {
addingRulesetFile: false,
adminMode: true,
decoderInfo: false,
error: false,
fileContent: false,
filters: {},
isLoading: false,
isProcessing: false,
itemList: [],
items: [],
listInfo: false,
pageIndex: 0,
ruleInfo: false,
section: 'rules',
showingFiles: false,
showModal: false,
sortDirection: 'asc',
sortField: 'id',
defaultItems: [],
}
const rulesetReducers = (state = initialState, action) => {
switch (action.type) {
case 'CLEAN_CONTENT':
return Object.assign({}, state, { fileContent: false, error: false });
case 'CLEAN_FILTERS':
return Object.assign({}, state, { filters: {} });
case 'CLEAN_INFO':
return Object.assign({}, state, { decoderInfo: false, ruleInfo: false, listInfo: false, fileContent: false, addingRulesetFile: false, error: false });
case 'RESET':
return initialState;
case 'TOGGLE_SHOW_FILES':
return Object.assign({}, state, { showingFiles: action.status, error: false });
case 'UPDATE_ADDING_RULESET_FILE':
return Object.assign({}, state, { addingRulesetFile: action.content, error: false });
case 'UPDATE_ADMIN_MODE':
return Object.assign({}, state, { adminMode: action.status });
case 'UPDATE_DECODER_INFO':
return Object.assign({}, state, { decoderInfo: action.info, ruleInfo: false, listInfo: false, error: false });
case 'UPDATE_DEFAULT_ITEMS':
return Object.assign({}, state, { defaultItems: action.defaultItems, error: false });
case 'UPDATE_ERROR':
return Object.assign({}, state, { error: action.error });
case 'UPDATE_FILE_CONTENT':
return Object.assign({}, state, { fileContent: action.content, decoderInfo: false, ruleInfo: false, listInfo: false, error: false });
case 'UPDATE_FILTERS':
return Object.assign({}, state, { filters: action.filters, error: false });
case 'UPDATE_IS_PROCESSING':
return Object.assign({}, state, { isProcessing: action.isProcessing, ruleInfo: false, listInfo: false, error: false });
case 'UPDATE_ITEMS':
return Object.assign({}, state, { items: action.items, error: false });
case 'UPDATE_LIST_CONTENT':
return Object.assign({}, state, { fileContent: false, decoderInfo: false, ruleInfo: false, listInfo: action.content, error: false });
case 'UPDATE_LIST_ITEMS_FOR_REMOVE':
return Object.assign({}, state, { itemList: action.itemList, error: false });
case 'UPDATE_LOADING_STATUS':
return Object.assign({}, state, { isLoading: action.status, error: false });
case 'UPDATE_PAGE_INDEX':
return Object.assign({}, state, { pageIndex: action.pageIndex, ruleInfo: false, listInfo: false, error: false });
case 'UPDATE_RULE_INFO':
return Object.assign({}, state, { ruleInfo: action.info, decoderInfo: false, listInfo: false, error: false });
case 'UPDATE_RULESET_SECTION':
return Object.assign({}, state, { section: action.section, error: false });
case 'UPDATE_SHOW_MODAL':
return Object.assign({}, state, { showModal: action.showModal, error: false });
case 'UPDATE_SORT_DIRECTION':
return Object.assign({}, state, { sortDirection: action.sortDirection, error: false });
case 'UPDATE_SORT_FIELD':
return Object.assign({}, state, { sortField: action.sortField, error: false });
default: return state;
}
}
export default rulesetReducers;

16
public/redux/store.js Normal file
View File

@ -0,0 +1,16 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import { createStore } from 'redux';
import rootReducers from './reducers/rootReducers';
export default createStore(rootReducers);

View File

@ -0,0 +1,25 @@
/*
* Wazuh app - React component for registering agents.
* Copyright (C) 2015-2019 Wazuh, Inc.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* Find more information about this on the LICENSE file.
*/
import React from 'react';
import { Provider } from 'react-redux';
import store from './store';
const WzReduxProvider = (props) => {
return (
<Provider store={store}>
{props.children}
</Provider>
)
}
export default WzReduxProvider;

View File

@ -13,12 +13,31 @@ export class RulesetHandler {
constructor(apiReq) { constructor(apiReq) {
this.apiReq = apiReq; this.apiReq = apiReq;
} }
async getRules(filter = {}) {
try {
const result = await this.apiReq.request('GET', `/rules`, filter);
return ((result || {}).data || {}).data || false;
} catch (error) {
return Promise.reject(error);
}
}
async getDecoders(filter = {}) {
try {
const result = await this.apiReq.request('GET', `/decoders`, filter);
return ((result || {}).data || {}).data || false;
} catch (error) {
return Promise.reject(error);
}
}
async getLocalRules() { async getLocalRules() {
try { try {
const result = await this.apiReq.request('GET', `/rules`, { const result = await this.apiReq.request('GET', `/rules`, {
path: 'etc/rules' path: 'etc/rules'
}); });
return result; return ((result || {}).data || {}).data || false;
} catch (error) { } catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
@ -29,7 +48,7 @@ export class RulesetHandler {
const result = await this.apiReq.request('GET', `/decoders`, { const result = await this.apiReq.request('GET', `/decoders`, {
path: 'etc/decoders' path: 'etc/decoders'
}); });
return result; return ((result || {}).data || {}).data || false;
} catch (error) { } catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }
@ -64,7 +83,7 @@ export class RulesetHandler {
const result = await this.apiReq.request('GET', `/manager/files`, { const result = await this.apiReq.request('GET', `/manager/files`, {
path: path path: path
}); });
return result; return ((result || {}).data || {}).data || false;
} catch (error) { } catch (error) {
return Promise.reject(error); return Promise.reject(error);
} }

View File

@ -1,2 +1,2 @@
<div flex="auto" layout="column" ng-controller="managementConfigurationController as mconfigctrl" ng-if="mctrl.tab === 'configuration'" <div flex="auto" layout="column" ng-controller="managementConfigurationController as mconfigctrl" ng-if="mctrl.tab === 'configuration'"
ng-init="mconfigctrl.switchConfigurationTab('welcome', false)"> ng-init="mconfigctrl.switchConfigurationTab('welcome', false)" class="sideBarContent">

View File

@ -1,233 +1,98 @@
<div flex layout="column" ng-class="{'cursor-wait': multipleSelectorLoading}" ng-controller="groupsPreviewController" <div flex layout="column" ng-class="{'cursor-wait': multipleSelectorLoading}" ng-controller="groupsPreviewController as gp"
ng-if="mctrl.tab == 'groups'"> ng-if="mctrl.tab == 'groups'" class="sideBarContent">
<md-content flex layout="column" class=" overflow-hidden" ng-init="lookingGroup=false">
<!-- Main content -->
<md-content flex layout="column" class=" overflow-hidden" ng-init="gp.lookingGroup=false">
<!-- Loading ring --> <!-- Loading ring -->
<div class="md-padding md-padding-top-16" ng-show="load"> <div class="md-padding md-padding-top-16" ng-show="load">
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" /> <react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />
</div> </div>
<!-- Headline --> <!-- Groups table -->
<div ng-show="!load" layout="column" layout-padding ng-if="!currentGroup || !currentGroup.name"> <div ng-show="!gp.load" layout="column" ng-if="(!gp.currentGroup || !gp.currentGroup.name) && gp.groupsTableProps && !gp.lookingGroup">
<div layout="row"> <react-component name="GroupsTable" props="gp.groupsTableProps" />
<span class="font-size-18">
<i class="fa fa-fw fa-object-group" aria-hidden="true"></i> Groups </span>
<span ng-if='adminMode' class="font-size-18 wz-text-link wz-margin-left-8" ng-click="switchAddingGroup()">
<react-component ng-if="!addingGroup" name="EuiIcon" props="{type:'plusInCircle', color:'primary'}" />
<svg ng-if="addingGroup" class="euiIcon euiIcon--medium" focusable="false" xmlns="http://www.w3.org/2000/svg"
width="16" height="16" viewBox="0 0 16 16">
<path fill-rule="evenodd" d="M7.5 0C11.636 0 15 3.364 15 7.5S11.636 15 7.5 15 0 11.636 0 7.5 3.364 0 7.5 0zm0 .882a6.618 6.618 0 1 0 0 13.236A6.618 6.618 0 0 0 7.5.882zM3.5 7h8a.5.5 0 1 1 0 1h-8a.5.5 0 0 1 0-1z">
</path>
</svg>
</span>
</div>
<div layout="row" ng-if="addingGroup" ng-if='adminMode' class="wz-padding-bottom-0">
<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> </div>
<!-- End headline -->
<!-- Inside the group -->
<div flex layout="column" layout-align="start stretch" ng-show="!load" ng-init="groupsSelectedTab='agents'"> <div flex layout="column" layout-align="start stretch" ng-show="!gp.load && gp.lookingGroup " ng-init="gp.groupsSelectedTab='agents'">
<!-- MD5 Sums and Details cards -->
<div layout="row" class="md-padding" ng-if="lookingGroup && currentGroup"> <!-- Tabs for the groups Agents / Files -->
<span class="wz-headline-title font-size-18"><i class="fa fa-fw fa-object-group" aria-hidden="true"></i> <div ng-if="gp.currentGroup && !gp.addingAgents && !gp.editingFile && gp.groupsTabsProps" class="md-margin-h">
{{currentGroup.name}}</span> <react-component name="Tabs" props="gp.groupsTabsProps" />
</div>
<!-- End group actions -->
<div ng-if="lookingGroup && currentGroup && !addingAgents && !editingFile && groupsTabsProps" class="md-margin-h">
<react-component name="Tabs" props="groupsTabsProps" />
</div> </div>
<!-- XML editor for group agents --> <!-- XML editor for group agents -->
<div ng-show="editingFile"> <div ng-show="gp.editingFile">
<div layout="row" class="md-padding-h wz-margin-top-10"> <div layout="row" class="md-padding-h wz-margin-top-10">
<span ng-click='closeEditingFile()' class='wz-margin-top-3 kuiButton kuiButton--hollow'>Cancel</span> <span ng-click='gp.closeEditingFile()' class='wz-margin-top-3 kuiButton kuiButton--hollow'>Cancel</span>
<button ng-disabled='xmlHasErrors' ng-click='doSaveGroupAgentConfig()' class='height-35 kuiButton kuiButton--secondary wz-margin-left-8'> <button ng-disabled='gp.xmlHasErrors' ng-click='gp.doSaveGroupAgentConfig()' class='height-35 kuiButton kuiButton--secondary wz-margin-left-8'>
<span ng-show='!xmlHasErrors'> <span ng-show='!gp.xmlHasErrors'>
<react-component name="EuiIcon" props="{type: 'save'}" /> Save file <react-component name="EuiIcon" props="{type: 'save'}" /> Save file
</span> </span>
<span ng-show='xmlHasErrors' class='btn-danger'><i aria-hidden='true' class='fa fa-fw fa-exclamation-triangle'></i> <span ng-show='gp.xmlHasErrors' class='btn-danger'><i aria-hidden='true' class='fa fa-fw fa-exclamation-triangle'></i>
XML format error</span> XML format error</span>
</button> </button>
</div> </div>
<div class="md-padding md-padding-top-10" ng-if="fetchedXML && currentGroup"> <div class="md-padding md-padding-top-10" ng-if="gp.fetchedXML && gp.currentGroup">
<wz-xml-file-editor file-name='agent.conf' data="fetchedXML" target-name="currentGroup.name + ' group'" <wz-xml-file-editor file-name='agent.conf' data="gp.fetchedXML" target-name="gp.currentGroup.name + ' group'"
valid-fn='xmlIsValid(valid)' close-fn='closeEditingFile(reload)'> valid-fn='gp.xmlIsValid(valid)' close-fn='gp.closeEditingFile(reload)'>
</wz-xml-file-editor> </wz-xml-file-editor>
</div> </div>
</div> </div>
<!-- XML editor for group agents --> <!-- End XML editor for group agents -->
<div ng-if="!editingFile"> <div ng-if="!gp.editingFile">
<div layout="row" class="md-padding" ng-if="lookingGroup && currentGroup && addingAgents"> <div layout="row" class="md-padding" ng-if="gp.currentGroup && gp.addingAgents">
<span ng-click='addMultipleAgents(false)' class='wz-margin-top-3 kuiButton kuiButton--hollow'> <span ng-click='gp.addMultipleAgents(false)' class='wz-margin-top-3 kuiButton kuiButton--hollow'>
Cancel</span> Cancel</span>
<span ng-hide='moreThan500' ng-click='saveAddAgents()' class='btn wz-button wz-margin-left-8'><i <span ng-hide='moreThan500' ng-click='gp.saveAddAgents()' class='btn wz-button wz-margin-left-8'><i
aria-hidden='true' class='fa fa-fw fa-save'></i> aria-hidden='true' class='fa fa-fw fa-save'></i>
Apply changes</span> Apply changes</span>
<span class='error-msg' ng-show='moreThan500'><i class="fa fa-exclamation-triangle"></i> It is not <span class='error-msg' ng-show='gp.moreThan500'><i class="fa fa-exclamation-triangle"></i> It is not
possible to apply changes of more than 500 additions or deletions</span> possible to apply changes of more than 500 additions or deletions</span>
</div> </div>
<!-- Search bar --> <div ng-if='!gp.addingAgents'>
<div layout="row" class="wz-margin-top-16 euiFlexGroup euiFlexGroup--alignItemsCenter euiFormControlLayout__childrenWrapper md-padding-h ng-scope"
ng-if="!addingAgents && !file">
<input placeholder="{{groupsSelectedTab==='files' ? 'Filter files...' : lookingGroup ? 'Filter agents...' : 'Filter groups...'}}"
ng-model="custom_search" type="text" class="euiFieldSearch euiFieldSearch--fullWidth euiFlexItem height-35 ng-empty ng-pristine ng-touched ng-valid"
aria-invalid="false" wz-enter="search(custom_search)">
<div class="euiFormControlLayoutIcons wz-margin-left-16">
<span class="euiFormControlLayoutCustomIcon">
<react-component name="EuiIcon" props="{type:'search', className:'euiFormControlLayoutCustomIcon__icon'}" />
</span>
</div>
<button type="submit" aria-label="Search" class="euiFlexItem euiFlexItem--flexGrowZero height-35 kuiButton kuiButton--secondary wz-margin-left-8"
ng-click="search(custom_search)">
Search
</button>
<div ng-if="lookingGroup && currentGroup && !addingAgents && !editingFile && !file && adminMode"
class="euiFlexItem euiFlexItem--flexGrowZero height-35 wz-margin-left-8">
<div class="euiFilterGroup"></div>
</div>
<button ng-if="lookingGroup && currentGroup && !addingAgents && !editingFile && !file && adminMode && groupsSelectedTab==='files'"
class="euiFlexItem euiFlexItem--flexGrowZero height-35 kuiButton kuiButton--secondary ng-scope wz-margin-left-8"
ng-click="editGroupAgentConfig(currentGroup)" aria-label="Edit group configuration">
<react-component name="EuiIcon" props="{type:'pencil', color:'primary'}" />
Edit group configuration
</button>
<button ng-if="lookingGroup && currentGroup && !addingAgents && !editingFile && !file && adminMode && groupsSelectedTab==='agents'"
class="euiFlexItem euiFlexItem--flexGrowZero height-35 kuiButton kuiButton--secondary ng-scope wz-margin-left-8"
ng-click="addMultipleAgents(true)" aria-label="Add or remove agents">
<react-component name="EuiIcon" props="{type:'plusInCircle', color:'primary'}" />
Add or remove agents
</button>
</div>
<!-- End search bar -->
<!-- Groups table -->
<md-card flex class="wz-md-card _md flex md-margin-h wz-margin-top-16" ng-if="!lookingGroup">
<md-card-content>
<div layout="row">
<wz-table custom-columns="true" flex path="'/agents/groups'" keys="['name','count','mergedSum']"
allow-click="true" row-sizes="[14,12,10]">
</wz-table>
</div>
<div layout="row" layout-align="end center">
<button type="button" ng-click="downloadCsv('/agents/groups')" class="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small">
<span class="euiButtonEmpty__content">
<react-component name="EuiIcon" props="{type:'importAction'}" />
<span class="euiButtonEmpty__text">Formatted</span>
</span>
</button>
</div>
</md-card-content>
</md-card>
<!-- End groups table -->
<div ng-if='!addingAgents'>
<!-- Group agents table --> <!-- Group agents table -->
<md-card flex class="wz-md-card _md flex md-margin-h wz-margin-top-16" ng-if="lookingGroup && groupsSelectedTab==='agents' && currentGroup"> <div flex ng-if="gp.groupsSelectedTab==='agents' && gp.currentGroup">
<md-card-actions layout="row" class="wz-card-actions wz-card-actions-top layout-align-end-center"> <react-component name="AgentsInGroupTable" props="gp.agentsInGroupTableProps"/>
<react-component name="ExportConfiguration" ng-hide="reportBusy && reportStatus" props="{exportConfiguration, type: 'group'}" /> </div>
<react-component ng-show="reportBusy && reportStatus" name="EuiLoadingSpinner" props="{size:'m'}" />
</md-card-actions>
<div layout="row" ng-show="failedErrors" class="extraHeader">
<md-list>
<md-list-item class="error-enum-configuration" ng-repeat="group in failedErrors">
<span class="wz-agent-status-indicator small red-text padding-left-0"><span
ng-repeat="error in group">{{error.id}}{{$last
? '' : ',&nbsp;'}}</span>: {{group[0].message}}</span>
</md-list-item>
</md-list>
<span flex></span>
<a class='md-padding md-padding-top-10' ng-click='clearFailedErrors()'><i class="fa fa-times"
aria-hidden="true"></i></a>
</div>
<md-card-content>
<div layout="row">
<wz-table flex custom-columns="true" path="'/agents/groups/' + currentGroup.name" keys="['id','name','ip','status','os.name','os.version','version']"
allow-click="true" row-sizes="[14,12,10]" empty-results="'No agents were added to this group.'" />
</div>
<div layout="row" ng-if="lookingGroup && groupsSelectedTab==='agents'">
<span flex></span>
<button type="button" ng-click="downloadCsv('/agents/groups/' + currentGroup.name)"
class="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small">
<span class="euiButtonEmpty__content">
<react-component name="EuiIcon" props="{type:'importAction'}" />
<span class="euiButtonEmpty__text">Formatted</span>
</span>
</button>
</div>
</md-card-content>
</md-card>
<!-- Group files table --> <!-- Group files table -->
<md-card flex class="wz-md-card _md flex md-margin-h wz-margin-top-16" ng-if="lookingGroup && groupsSelectedTab==='files' && !fileViewer && currentGroup"> <react-component ng-if="gp.groupsSelectedTab==='files' && !gp.fileViewer && gp.currentGroup" name="FilesInGroupTable" props="gp.filesInGroupTableProps"/>
<md-card-actions layout="row" class="wz-card-actions wz-card-actions-top layout-align-end-center">
<react-component name="ExportConfiguration" ng-hide="reportBusy && reportStatus" props="{exportConfiguration, type: 'group'}" />
<react-component ng-show="reportBusy && reportStatus" name="EuiLoadingSpinner" props="{size:'m'}" />
</md-card-actions>
<md-card-content>
<div layout="row">
<wz-table custom-columns="true" flex path="'/agents/groups/' + currentGroup.name + '/files'"
keys="[{value:'filename',width:'300px'},'hash']" allow-click="true" row-sizes="[16,14,12]">
</wz-table>
</div>
<div layout="row" ng-if="lookingGroup && groupsSelectedTab==='files' && !file">
<span flex></span>
<button type="button" ng-click="downloadCsv('/agents/groups/' + currentGroup.name + '/files')"
class="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small">
<span class="euiButtonEmpty__content">
<react-component name="EuiIcon" props="{type:'importAction'}" />
<span class="euiButtonEmpty__text">Formatted</span>
</span>
</button>
</div>
</md-card-content>
</md-card>
<!-- End Group files table -->
<!-- CSV Download button section for group files-->
<!-- End CSV Download button section for group files -->
<!-- File JSON viewer section --> <!-- File JSON viewer section -->
<div flex layout="column" class="md-padding" ng-if="lookingGroup && groupsSelectedTab==='files' && file"> <div flex layout="column" class="md-padding" ng-if="gp.groupsSelectedTab==='files' && gp.file">
<div flex layout="column"> <div flex layout="column">
<div layout="row" class="wz-padding-bottom-14"> <div layout="row" class="wz-padding-bottom-14">
<span flex class="wz-headline-title">{{ filename }}</span> <span flex class="wz-headline-title">{{ gp.filename }}</span>
<button class="md-icon-button" ng-if="lookingGroup" aria-label="Back" tooltip="Close file" <!-- Go back button -->
tooltip-placement="left" ng-click="goBackFiles()"><i class="fa fa-fw fa-close" <button class="md-icon-button" ng-if="gp.lookingGroup" aria-label="Back" tooltip="Close file"
tooltip-placement="left" ng-click="gp.goBackFiles()"><i class="fa fa-fw fa-close"
aria-hidden="true"></i></button> aria-hidden="true"></i></button>
<!--<span flex class="wz-text-right cursor-pointer color-grey" ng-click="goBackFiles()">close</span>--> <!--<span flex class="wz-text-right cursor-pointer color-grey" ng-click="goBackFiles()">close</span>-->
</div> </div>
<div flex layout="column"> <div flex layout="column">
<pre flex class="wz-pre groupContentViewer wzXmlEditor wz-overflow-y-auto"><code wz-dynamic="file"></code></pre> <pre flex class="wz-pre groupContentViewer wzXmlEditor wz-overflow-y-auto"><code wz-dynamic="gp.file"></code></pre>
</div> </div>
</div> </div>
</div> </div>
<!-- End File JSON viewer section --> <!-- End File JSON viewer section -->
</div> </div>
<div layout="row" class="md-padding" ng-if="addingAgents"> <div layout="row" class="md-padding" ng-if="gp.addingAgents">
<span ng-show='!multipleSelectorLoading' class="wzMultipleSelectorCounter"><span style='color:green'>+{{currentAdding}}</span>&nbsp;<span <span ng-show='!gp.multipleSelectorLoading' class="wzMultipleSelectorCounter"><span style='color:green'>+{{gp.currentAdding}}</span>&nbsp;
style='color:red'>-{{currentDeleting}}</span></span> <span style='color:red'>-{{gp.currentDeleting}}</span></span>
<wz-multiple-selector class='wzMultipleSelector' available-items="availableAgents.data" <wz-multiple-selector class='wzMultipleSelector' available-items="gp.availableAgents.data"
selected-items="selectedAgents.data" title-available-items="Available agents" selected-items="gp.selectedAgents.data" title-available-items="Available agents"
title-selected-items="Current agents in the group" total-selected-items="totalSelectedAgents" title-selected-items="Current agents in the group" total-selected-items="gp.totalSelectedAgents"
reload-scroll='reload(element, searchTerm, 499, start)' limit="checkLimit()"> reload-scroll='gp.reload(element, searchTerm, 499, start)' limit="gp.checkLimit()">
</wz-multiple-selector> </wz-multiple-selector>
</div> </div>
</div> </div>
</div> </div>
<!-- End inside the group -->
</md-content> </md-content>
</div> </div>

View File

@ -1,4 +1,4 @@
<div layout="column" ng-controller="managerLogController as ctrl" ng-if="!ctrl.loading && mctrl.tab == 'logs'"> <div layout="column" class="sideBarContent" ng-controller="managerLogController as ctrl" ng-if="!ctrl.loading && mctrl.tab == 'logs'">
<!-- Filters and Realtime button section --> <!-- Filters and Realtime button section -->
<div ng-show="!ctrl.loading" layout="row" layout-align="start center" class="md-padding-h wz-margin-top-16"> <div ng-show="!ctrl.loading" layout="row" layout-align="start center" class="md-padding-h wz-margin-top-16">

View File

@ -1,51 +1,17 @@
<div flex="auto" layout="column" ng-cloak ng-controller="managementController as mctrl"> <div ng-cloak ng-controller="managementController as mctrl" style="padding-top: 16px">
<!-- Navigation section --> <!-- Navigation section -->
<div layout="row" layout-align="start center" ng-if="mctrl.tab !== 'monitoring' && mctrl.tab !== 'configuration'"> <div layout="row" layout-align="start center" ng-if="mctrl.tab !== 'monitoring' && mctrl.tab !== 'configuration'" class="sideBarContent">
<!-- Breadcrumbs --> <!-- Breadcrumbs -->
<div layout="row" layout-padding> <div layout="row" layout-padding ng-if="mctrl.tab === 'groups' && mctrl.currentGroup && mctrl.currentGroup.name">
<!-- If you're not on the Welcome tab, show a functional breadcrumb --> <span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('groups', true)">{{ mctrl.tabNames[mctrl.tab] }}</span>
<div
ng-if="mctrl.tab !== 'welcome' && !mctrl.currentGroup && !mctrl.currentRule && !mctrl.currentDecoder && !mctrl.currentList">
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome', true)">Management</span>
<span> / {{ mctrl.tabNames[mctrl.tab] }}</span>
</div>
<div
ng-if="mctrl.tab !== 'welcome' && !mctrl.currentGroup && (mctrl.currentRule || mctrl.currentDecoder || mctrl.currentList)">
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome', true)">Management</span>
<span class="wz-text-link cursor-pointer" ng-click="mctrl.breadCrumbBack(true);"> /
{{ mctrl.tabNames[mctrl.tab] }}</span>
<span ng-show="mctrl.currentRule"><span class="wz-text-link cursor-pointer"
ng-click="mctrl.breadCrumbBack()"> / rules</span> / {{mctrl.currentRule.id}}</span>
<span ng-show="mctrl.currentDecoder"><span class="wz-text-link cursor-pointer"
ng-click="mctrl.breadCrumbBack()"> / decoders</span> / {{mctrl.currentDecoder.name}}</span>
<span ng-show="mctrl.currentList"><span class="wz-text-link cursor-pointer"
ng-click="mctrl.breadCrumbBack()"> / lists</span> / {{mctrl.currentList.name}}</span>
</div>
<div ng-if="mctrl.tab === 'groups' && mctrl.currentGroup && mctrl.currentGroup.name">
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome', true)">Management</span>
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('groups', true)"> /
{{ mctrl.tabNames[mctrl.tab] }}</span>
<span> / {{ mctrl.currentGroup.name }} </span> <span> / {{ mctrl.currentGroup.name }} </span>
</div>
</div> </div>
<!-- End breadcrumbs --> <!-- End breadcrumbs -->
</div> </div>
<div layout="row" layout-align="start center" ng-if="mctrl.tab !== 'monitoring' && mctrl.tab === 'configuration'"> <div layout="row" layout-align="start center" ng-if="mctrl.tab !== 'monitoring' && mctrl.tab === 'configuration'" class="sideBarContent">
<div layout-padding class="euiFlexGroup"> <div layout-padding class="euiFlexGroup">
<div layout="row" ng-if="!mctrl.currentConfiguration && mctrl.editionTab !== 'editconfiguration'"
class="euiFlexItem">
<span class="wz-text-link cursor-pointer euiBreadcrumb"
ng-click="mctrl.switchTab('welcome', true)">Management</span>
<span class="euiBreadcrumb"> / </span>
<span>{{ mctrl.tabNames[mctrl.tab] }}</span>
</div>
<div layout="row" ng-if="mctrl.currentConfiguration || mctrl.editionTab === 'editconfiguration'" <div layout="row" ng-if="mctrl.currentConfiguration || mctrl.editionTab === 'editconfiguration'"
class="euiFlexItem"> class="euiFlexItem">
<span class="wz-text-link cursor-pointer euiBreadcrumb"
ng-click="mctrl.switchTab('welcome', true)">Management</span>
<span class="euiBreadcrumb"> / </span>
<span class="wz-text-link cursor-pointer euiBreadcrumb" <span class="wz-text-link cursor-pointer euiBreadcrumb"
ng-click="mctrl.switchTab('configuration', true)">{{ mctrl.tabNames[mctrl.tab] }}</span> ng-click="mctrl.switchTab('configuration', true)">{{ mctrl.tabNames[mctrl.tab] }}</span>
<span ng-if="mctrl.editionTab !== 'editconfiguration'"> / <span ng-if="mctrl.editionTab !== 'editconfiguration'"> /
@ -61,8 +27,3 @@
</div> </div>
</div> </div>
</div> </div>
<div ng-if="!mctrl.load && mctrl.inArray(mctrl.tab, mctrl.statusReportsTabs) && mctrl.tab !== 'monitoring' && mctrl.tab !== 'welcome'"
class="md-padding-h" ng-show="mctrl.tab !== 'welcome'" >
<react-component name="Tabs" props="mctrl.managementTabsProps" />
</div>

View File

@ -0,0 +1,3 @@
<div ng-if="!rctrl.loading && mctrl.managementProps && mctrl.tab !== 'welcome'" layout="column">
<react-component name="WzManagement" props="mctrl.managementProps"></react-component>
</div>

View File

@ -5,6 +5,10 @@ include ./configuration/configuration.pug
include ./monitoring/monitoring.pug include ./monitoring/monitoring.pug
include ./logs.html include ./logs.html
include ./reporting.html include ./reporting.html
include ./management.html
include ./groups/groups.html include ./groups/groups.html
include ./ruleset/ruleset.pug include ./ruleset/ruleset.pug
include ../footer.foot include ../footer.foot

View File

@ -1,24 +1,6 @@
<div layout="column" ng-controller="clusterController" ng-if="mctrl.tab === 'monitoring'"> <div layout="column" ng-controller="clusterController" ng-if="mctrl.tab === 'monitoring'" class="sideBarContent">
<div flex layout="column" ng-show="!isClusterEnabled || !isClusterRunning"> <div flex layout="column" ng-show="!isClusterEnabled || !isClusterRunning">
<!-- Cluster disabled breadcrumbs -->
<div layout="row" layout-align="start center">
<div layout="row" layout-padding>
<div>
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome')">Management</span>
<span> / </span>
<span>{{ mctrl.tabNames[tab] }}</span>
</div>
</div>
</div>
<!-- End cluster disabled breadcrumbs -->
<!-- Status and reports navigation bar -->
<div ng-show="mctrl.tab !== 'welcome'" class="md-padding-h">
<react-component name="Tabs" props="mctrl.managementTabsProps" />
</div>
<!-- End status and reports navigation bar -->
<!-- Cluster disabled section --> <!-- Cluster disabled section -->
<div flex layout="row" layout-align="start start" ng-if="!isClusterEnabled"> <div flex layout="row" layout-align="start start" ng-if="!isClusterEnabled">
<md-card flex class="wz-md-card" flex> <md-card flex class="wz-md-card" flex>
@ -61,27 +43,17 @@
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" /> <react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />
</div> </div>
<!-- Navigation section --> <!-- Navigation section -->
<div layout="row" layout-align="start center" ng-if="!loading"> <div layout="row" layout-align="start center" ng-if="!loading">
<div layout="row" layout-padding> <div layout="row" layout-padding>
<div ng-if="!showConfig && !showNodes">
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome')">Management</span>
<span> / </span>
<span>{{ mctrl.tabNames[mctrl.tab] }}</span>
<span> / </span>
<span>{{ currentAPI }}</span>
</div>
<div ng-if="showConfig"> <div ng-if="showConfig">
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome')">Management</span>
<span> / </span>
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ mctrl.tabNames[mctrl.tab] }}</span> <span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ mctrl.tabNames[mctrl.tab] }}</span>
<span> / </span> <span> / </span>
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span> <span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span>
<span> / Overview</span> <span> / Overview</span>
</div> </div>
<div ng-if="showNodes && !currentNode"> <div ng-if="showNodes && !currentNode">
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome')">Management</span>
<span> / </span>
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ mctrl.tabNames[mctrl.tab] }}</span> <span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ mctrl.tabNames[mctrl.tab] }}</span>
<span> / </span> <span> / </span>
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span> <span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span>
@ -89,8 +61,6 @@
<span>Nodes</span> <span>Nodes</span>
</div> </div>
<div ng-if="currentNode"> <div ng-if="currentNode">
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('welcome')">Management</span>
<span> / </span>
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ mctrl.tabNames[mctrl.tab] }}</span> <span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ mctrl.tabNames[mctrl.tab] }}</span>
<span> / </span> <span> / </span>
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span> <span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span>
@ -102,12 +72,7 @@
</div> </div>
</div> </div>
<!-- End navigation section --> <!-- End navigation section -->
<!-- Status and reports navigation bar -->
<div ng-if="!loading" ng-show="mctrl.tab !== 'welcome'" class="md-padding-h">
<react-component name="Tabs" props="mctrl.managementTabsProps" />
</div>
<!-- End status and reports navigation bar -->
<!-- Discover search bar section --> <!-- Discover search bar section -->
<kbn-dis ng-show="!loading && (!showNodes || currentNode)" class="wz-margin-top-10 monitoring-discover"></kbn-dis> <kbn-dis ng-show="!loading && (!showNodes || currentNode)" class="wz-margin-top-10 monitoring-discover"></kbn-dis>

View File

@ -1,4 +1,4 @@
<div layout="column" layout-align="start stretch" ng-controller="reportingController as ctrl" ng-if="mctrl.tab === 'reporting'"> <div layout="column" class="sideBarContent" layout-align="start stretch" ng-controller="reportingController as ctrl" ng-if="mctrl.tab === 'reporting'">
<div ng-if="ctrl.loading" class="md-padding wz-margin-top-16"> <div ng-if="ctrl.loading" class="md-padding wz-margin-top-16">
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" /> <react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />
</div> </div>

View File

@ -1,5 +0,0 @@
<div flex ng-if="!loading && mctrl.globalRulesetTab == 'decoders' && !mctrl.managingFiles" class="" ng-controller="decodersController as dctrl" layout="column" id="rulesContainer" layout-align="start space-around">
<div ng-if="loading" class="md-padding wz-margin-top-16">
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />
</div>

View File

@ -1,4 +1,3 @@
include ./decoders.head
include ./decoders-list.html include ./decoders-list.html
include ./decoders-detail.html include ./decoders-detail.html
include ../../../footer.foot include ../../../footer.foot

View File

@ -1,201 +1,213 @@
<div ng-if="!loading && viewingDetail" layout="column" ng-class="{ 'md-padding' : editingFile }"> <div ng-if="!rctrl.loading && rctrl.viewingDetail" layout="column" ng-class="{ 'md-padding' : editingFile }">
<!-- Back button and title --> <!-- Back button and title -->
<div layout="row" layout-align="start center" ng-show="!editingFile"> <div layout="row" layout-align="start center" ng-show="!rctrl.editingFile">
<!-- Back button --> <!-- Back button -->
<md-button class="md-icon-button" style="margin: 5px!important;" aria-label="Back to rules list" tooltip="Back" <md-button class="md-icon-button" style="margin: 5px!important;" aria-label="Back to rules list" tooltip="Back"
tooltip-placement="bottom" ng-click="closeDetailView(true)"><i class="fa fa-fw fa-arrow-left" aria-hidden="true"></i></md-button> tooltip-placement="bottom" ng-click="rctrl.closeDetailView(true)"><i class="fa fa-fw fa-arrow-left"
<!-- Rule title --> aria-hidden="true"></i></md-button>
<div> <!-- Rule title -->
<h1 class="font-size-18" ng-bind-html="colorRuleArg(currentRule.description)"></h1> <div>
</div> <h1 class="font-size-18" ng-bind-html="rctrl.colorRuleArg(rctrl.currentRule.description)"></h1>
</div> </div>
<!-- End back button, title and status indicator --> </div>
<!-- End back button, title and status indicator -->
<!-- Rule information ribbon --> <!-- Rule information ribbon -->
<div layout="row" ng-show="!editingFile" class="wz-padding-left-8 wz-padding-right-8"> <div layout="row" ng-show="!rctrl.editingFile" class="wz-padding-left-8 wz-padding-right-8">
<md-card flex class="wz-metric-color wz-md-card"> <md-card flex class="wz-metric-color wz-md-card">
<md-card-content layout="row" class="wz-padding-metric"> <md-card-content layout="row" class="wz-padding-metric">
<div flex="15" ng-if="currentRule.id" class="wz-text-truncatable">ID: <span class="wz-text-bold">{{currentRule.id}}</span></div> <div flex="15" ng-if= "rctrl.currentRule.id" class="wz-text-truncatable">ID: <span
<div flex="15" ng-if="currentRule.level || currentRule.level == 0" class="wz-text-truncatable">Level: class="wz-text-bold">{{rctrl.currentRule.id}}</span></div>
<span class="wz-text-bold wz-text-link" ng-click="addDetailFilter('level', currentRule.level)" <div flex="15" ng-if="rctrl.currentRule.level || rctrl.currentRule.level == 0" class="wz-text-truncatable">
tooltip="Filter by this level" tooltip-placement="bottom">{{currentRule.level}}</span></div> Level:
<div flex="35" ng-if="currentRule.file" class="wz-text-truncatable">File: <span <span class="wz-text-bold wz-text-link" ng-click="rctrl.addDetailFilter('level', rctrl.currentRule.level)"
class="wz-text-bold wz-text-link" ng-click="openFile(currentRule.file,currentRule.path)" tooltip="Filter by this level" tooltip-placement="bottom">{{rctrl.currentRule.level}}</span></div>
tooltip="Filter by this file" tooltip-placement="bottom">{{currentRule.file}}</span></div> <div flex="35" ng-if="rctrl.currentRule.file" class="wz-text-truncatable">File: <span
<div flex="35" ng-if="currentRule.path" class="wz-text-truncatable">Path: <span class="wz-text-bold wz-text-link" ng-click="rctrl.openFile(rctrl.currentRule.file,rctrl.currentRule.path)"
class="wz-text-bold wz-text-link" ng-click="addDetailFilter('path', currentRule.path)" tooltip="Filter by this file" tooltip-placement="bottom">{{rctrl.currentRule.file}}</span></div>
tooltip="Filter by this path" tooltip-placement="bottom">{{currentRule.path}}</span></div> <div flex="35" ng-if="rctrl.currentRule.path" class="wz-text-truncatable">Path: <span
class="wz-text-bold wz-text-link" ng-click="rctrl.addDetailFilter('path', rctrl.currentRule.path)"
tooltip="Filter by this path" tooltip-placement="bottom">{{rctrl.currentRule.path}}</span></div>
</md-card-content> </md-card-content>
</md-card>
</div>
<!-- End Rule information ribbon -->
<div layout="row" class="md-padding"
ng-if="!rctrl.editingFile && rctrl.currentRule.file && rctrl.currentRule.path === 'etc/rules' && rctrl.adminMode">
<button ng-click='editRulesConfig(rctrl.currentRule)' class="height-35 kuiButton kuiButton--secondary">
<react-component name="EuiIcon" props="{type:'pencil'}" />
Edit {{ rctrl.currentRule.file }}
</button>
</div>
<!-- Rest of rule information -->
<div layout="column" layout-align="start" ng-show="!rctrl.editingFile">
<div layout="row" class="wz-padding-left-8 wz-padding-right-8">
<md-card flex class="wz-md-card" ng-if="rctrl.currentRule.details">
<md-card-content>
<span class="wz-headline-title">
<react-component name="EuiIcon" props="{type:'editorUnorderedList'}" /> Details
</span>
<md-divider class="wz-margin-top-10"></md-divider>
<div layout="row" class="wz-margin-top-10">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row"
ng-repeat="(key, value) in rctrl.currentRule.details">
<dt class="euiDescriptionList__title">{{key}}</dt>
<dd class="euiDescriptionList__description">
<div ng-if='!rctrl.isArray(value)' class="wz-word-break">
<span ng-if="!rctrl.isObject(value)" class="wz-text-right color-grey">{{value}}</span>
<span ng-if="rctrl.isObject(value)" class="color-grey">
<span ng-repeat="(key, value) in value">{{key}}: {{value}} <span ng-if="!$last">|</span>
</span>
</span>
</div>
<span ng-if="rctrl.isArray(value)" ng-repeat="v in value track by $index"
class="wz-text-right color-grey">
{{v}}{{$last ? '' : ',&nbsp;'}}
</span>
</dd>
</dl>
</div>
</div>
</md-card-content>
</md-card>
<md-card flex class="wz-md-card" ng-if="rctrl.currentRule.groups.length">
<md-card-content>
<span class="wz-margin-top-10 wz-headline-title">
<react-component name="EuiIcon" props="{type:'kqlSelector'}" /> Groups
</span>
<md-divider class="wz-margin-top-10"></md-divider>
<div layout="row" class="wz-margin-top-10">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dd class="euiDescriptionList__description" ng-repeat="item in rctrl.currentRule.groups">
<span class="wz-text-link" tooltip="Filter by this group" tooltip-placement="bottom"
ng-click="rctrl.addDetailFilter('group', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
</md-card-content>
</md-card>
<md-card flex class="wz-md-card"
ng-if="rctrl.currentRule.pci.length || rctrl.currentRule.gdpr.length || rctrl.currentRule.hipaa.length ||rctrl.currentRule.nist.length">
<md-card-content>
<span class="wz-margin-top-10 wz-headline-title">
<react-component name="EuiIcon" props="{type:'controlsHorizontal'}" /> Compliance
</span>
<md-divider class="wz-margin-top-10"></md-divider>
<div layout="row" class="wz-margin-top-10" ng-if="rctrl.currentRule.pci.length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">PCI DSS</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in rctrl.currentRule.pci">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="rctrl.addDetailFilter('pci', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
<div layout="row" class="wz-margin-top-10" ng-if="rctrl.currentRule.gdpr.length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">GDPR</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in rctrl.currentRule.gdpr">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="rctrl.addDetailFilter('gdpr', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
<div layout="row" class="wz-margin-top-10" ng-if="rctrl.currentRule.hipaa.length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">HIPAA</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in rctrl.currentRule.hipaa">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="rctrl.addDetailFilter('hipaa', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
<div layout="row" class="wz-margin-top-10" ng-if="rctrl.currentRule['nist-800-53'].length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">NIST-800-53</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in rctrl.currentRule['nist-800-53']">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="rctrl.addDetailFilter('nist-800-53', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
</md-card-content>
</md-card>
</div>
<!-- Related rules section -->
<div ng-if="!editingFile">
<div layout="row" class="md-padding wz-padding-top-10" ng-if="rctrl.currentRule">
<h1 class="md-headline wz-headline"><i class="fa fa-fw fa-link" aria-hidden="true"></i> Related rules
</h1>
</div>
<div layout="row" ng-if="rctrl.currentRule">
<md-card flex class="wz-md-card _md flex md-margin-h">
<md-card-content>
<wz-table custom-columns="true" flex path="'/rules'"
implicit-filter="[{ name:'file',value: rctrl.currentRule.file}]"
keys="[{value: 'id', width: '75px'},{value:'file',size:2},{value:'description',size:2},{value:'groups',nosortable:true,size:2},{value:'pci',nosortable:true,size:2},{value:'gdpr',nosortable:true},{value:'hipaa',nosortable:true},{value:'nist-800-53',nosortable:true},'level']"
allow-click="true">
</wz-table>
</md-card-content>
</md-card> </md-card>
</div>
<!-- End related rules section -->
</div> </div>
<!-- End Rule information ribbon --> <br>
</div>
<!-- End rest of rule information -->
<div layout="row" class="md-padding" ng-if="!editingFile && currentRule.file && currentRule.path === 'etc/rules' && adminMode">
<button ng-click='editRulesConfig(currentRule)' class="height-35 kuiButton kuiButton--secondary"> <!-- XML editor for rules -->
<react-component name="EuiIcon" props="{type:'pencil'}" /> <div layout="column" layout-align="start" ng-show="rctrl.editingFile">
Edit {{ currentRule.file }} <div layout="row">
</button> <span ng-click='rctrl.closeEditingFile()' class='wz-margin-top-3 kuiButton kuiButton--hollow'>Close</span>
<button ng-disabled='rctrl.xmlHasErrors || rctrl.doingSaving' ng-click='rctrl.doSaveConfig()'
class='btn wz-button pull-right wz-margin-left-8'>
<span ng-show='!rctrl.xmlHasErrors'><i aria-hidden='true' class='fa fa-fw fa-save'
ng-class="rctrl.doingSaving ? 'fa-spin fa-spinner' : ''"></i>Save
file</span>
<span ng-show='rctrl.xmlHasErrors' class='btn-danger'><i aria-hidden='true'
class='fa fa-fw fa-exclamation-triangle'></i>
XML format error</span>
</button>
<button ng-show="rctrl.restartBtn" class="btn wz-button" type="button" ng-disabled="doingSaving"
ng-click="rctrl.restart(); rctrl.toggleRestartMsg()">Restart
now</button>
</div> </div>
<div class="wz-padding-top-14" ng-if="rctrl.fetchedXML">
<!-- Rest of rule information --> <wz-xml-file-editor file-name='rules' data="rctrl.fetchedXML" target-name="rctrl.currentRule.file"
<div layout="column" layout-align="start" ng-show="!editingFile"> valid-fn='xmlIsValid(valid)' saving-param='rctrl.toggleSaveConfig()' close-fn='rctrl.closeEditingFile(reload)'>
</wz-xml-file-editor>
<div layout="row" class="wz-padding-left-8 wz-padding-right-8">
<md-card flex class="wz-md-card" ng-if="currentRule.details">
<md-card-content>
<span class="wz-headline-title">
<react-component name="EuiIcon" props="{type:'editorUnorderedList'}" /> Details
</span>
<md-divider class="wz-margin-top-10"></md-divider>
<div layout="row" class="wz-margin-top-10">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row" ng-repeat="(key, value) in currentRule.details">
<dt class="euiDescriptionList__title">{{key}}</dt>
<dd class="euiDescriptionList__description">
<div ng-if='!isArray(value)' class="wz-word-break">
<span ng-if="!isObject(value)" class="wz-text-right color-grey">{{value}}</span>
<span ng-if="isObject(value)" class="color-grey">
<span ng-repeat="(key, value) in value">{{key}}: {{value}} <span ng-if="!$last">|</span>
</span>
</span>
</div>
<span ng-if="isArray(value)" ng-repeat="v in value track by $index" class="wz-text-right color-grey">
{{v}}{{$last ? '' : ',&nbsp;'}}
</span>
</dd>
</dl>
</div>
</div>
</md-card-content>
</md-card>
<md-card flex class="wz-md-card" ng-if="currentRule.groups.length">
<md-card-content>
<span class="wz-margin-top-10 wz-headline-title">
<react-component name="EuiIcon" props="{type:'kqlSelector'}" /> Groups
</span>
<md-divider class="wz-margin-top-10"></md-divider>
<div layout="row" class="wz-margin-top-10">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dd class="euiDescriptionList__description" ng-repeat="item in currentRule.groups">
<span class="wz-text-link" tooltip="Filter by this group" tooltip-placement="bottom"
ng-click="addDetailFilter('group', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
</md-card-content>
</md-card>
<md-card flex class="wz-md-card" ng-if="currentRule.pci.length || currentRule.gdpr.length || currentRule.hipaa.length ||currentRule.nist.length">
<md-card-content>
<span class="wz-margin-top-10 wz-headline-title">
<react-component name="EuiIcon" props="{type:'controlsHorizontal'}" /> Compliance
</span>
<md-divider class="wz-margin-top-10"></md-divider>
<div layout="row" class="wz-margin-top-10" ng-if="currentRule.pci.length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">PCI DSS</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in currentRule.pci">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="addDetailFilter('pci', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
<div layout="row" class="wz-margin-top-10" ng-if="currentRule.gdpr.length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">GDPR</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in currentRule.gdpr">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="addDetailFilter('gdpr', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
<div layout="row" class="wz-margin-top-10" ng-if="currentRule.hipaa.length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">HIPAA</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in currentRule.hipaa">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="addDetailFilter('hipaa', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
<div layout="row" class="wz-margin-top-10" ng-if="currentRule['nist-800-53'].length">
<div class="euiFlexItem">
<dl class="euiDescriptionList euiDescriptionList--row">
<dt class="euiDescriptionList__title">NIST-800-53</dt>
<dd class="euiDescriptionList__description" ng-repeat="item in currentRule['nist-800-53']">
<span class="wz-text-link" tooltip="Filter by this requirement" tooltip-placement="bottom"
ng-click="addDetailFilter('nist-800-53', item)">
{{item}}
</span>
</dd>
</dl>
</div>
</div>
</md-card-content>
</md-card>
</div>
<!-- Related rules section -->
<div ng-if="!editingFile">
<div layout="row" class="md-padding wz-padding-top-10" ng-if="currentRule">
<h1 class="md-headline wz-headline"><i class="fa fa-fw fa-link" aria-hidden="true"></i> Related rules
</h1>
</div>
<div layout="row" ng-if="currentRule">
<md-card flex class="wz-md-card _md flex md-margin-h">
<md-card-content>
<wz-table custom-columns="true" flex path="'/rules'" implicit-filter="[{ name:'file',value: currentRule.file}]"
keys="[{value: 'id', width: '75px'},{value:'file',size:2},{value:'description',size:2},{value:'groups',nosortable:true,size:2},{value:'pci',nosortable:true,size:2},{value:'gdpr',nosortable:true},{value:'hipaa',nosortable:true},{value:'nist-800-53',nosortable:true},'level']"
allow-click="true">
</wz-table>
</md-card-content>
</md-card>
</div>
<!-- End related rules section -->
</div>
<br>
</div> </div>
<!-- End rest of rule information --> </div>
<!-- XML editor for rules -->
<!-- XML editor for rules -->
<div layout="column" layout-align="start" ng-show="editingFile">
<div layout="row">
<span ng-click='closeEditingFile()' class='wz-margin-top-3 kuiButton kuiButton--hollow'>Close</span>
<button ng-disabled='xmlHasErrors || doingSaving' ng-click='doSaveConfig()' class='btn wz-button pull-right wz-margin-left-8'>
<span ng-show='!xmlHasErrors'><i aria-hidden='true' class='fa fa-fw fa-save' ng-class="doingSaving ? 'fa-spin fa-spinner' : ''"></i>Save
file</span>
<span ng-show='xmlHasErrors' class='btn-danger'><i aria-hidden='true' class='fa fa-fw fa-exclamation-triangle'></i>
XML format error</span>
</button>
<button ng-show="restartBtn" class="btn wz-button" type="button" ng-disabled="doingSaving" ng-click="restart(); toggleRestartMsg()">Restart
now</button>
</div>
<div class="wz-padding-top-14" ng-if="fetchedXML">
<wz-xml-file-editor file-name='rules' data="fetchedXML" target-name="currentRule.file" valid-fn='xmlIsValid(valid)'
saving-param='toggleSaveConfig()' close-fn='closeEditingFile(reload)'>
</wz-xml-file-editor>
</div>
</div>
<!-- XML editor for rules -->
</div> </div>

View File

@ -1,113 +0,0 @@
<div ng-if="!loading && !viewingDetail" layout="column">
<div layout="row" ng-if="!editingFile" class="wz-margin-top-16">
<div flex class="euiPanel euiPanel--paddingLarge md-margin-h">
<div layout="row" layout-align="end center">
<h1 class="euiTitle euiTitle--medium">Rules</h1>
<wz-kbn-switch class="md-primary wz-no-top-bottom-margin md-padding-h wz-margin-top-3" switch-model="mctrl.showingLocalRules"
switch-text="Custom rules" switch-change="switchLocalRules()"></wz-kbn-switch>
<span flex></span>
<button ng-if="adminMode" type="button" ng-click="mctrl.switchFilesSubTab('rules')" class="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small wz-margin-left-10">
<span class="euiButtonEmpty__content">
<react-component name="EuiIcon" props="{type:'folderOpen'}" />
<span class="euiButtonEmpty__text">Manage rules files</span>
</span>
</button>
<button ng-if="adminMode" type="button" ng-click="mctrl.newFile()" class="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small wz-margin-left-10">
<span class="euiButtonEmpty__content">
<react-component name="EuiIcon" props="{type:'plusInCircle'}" />
<span class="euiButtonEmpty__text">Add new rule</span>
</span>
</button>
<button type="button" ng-click="downloadCsv()" class="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small wz-margin-left-10">
<span class="euiButtonEmpty__content">
<react-component name="EuiIcon" props="{type:'exportAction'}" />
<span class="euiButtonEmpty__text">Export formatted</span>
</span>
</button>
<!--
<button type="button" ng-click="mctrl.openLogtest()" class="euiButtonEmpty euiButtonEmpty--primary euiButtonEmpty--small wz-margin-left-10">
<span class="euiButtonEmpty__content">
<react-component name="EuiIcon" props="{type:'play'}" />
<span class="euiButtonEmpty__text">Test your logs</span>
</span>
</button>
-->
</div>
<div class="wz-padding-bottom-0">
<div layout="row" style="margin-left: -16px;margin-bottom: 20px;padding-right:0" class="euiFlexGroup euiFlexGroup--alignItemsCenter euiFormControlLayout__childrenWrapper md-padding-h ng-scope wz-margin-top-16"
ng-if="!mctrl.filesSubTab">
<input placeholder="Filter rules..." ng-model="custom_search" type="text" class="euiFieldSearch euiFieldSearch--fullWidth euiFlexItem"
aria-invalid="false" wz-enter="search(custom_search)" id="search-input-rules">
<wz-add-filter-chip id-input="search-input-rules"
options="[{label: 'File', value: 'file'}, {label: 'Path', value: 'path', hide: mctrl.showingLocalRules}, {label: 'Level', value: 'level'},
{label: 'Group', value: 'group'}, {label: 'PCI control', value: 'pci'}, {label: 'GDPR', value: 'gdpr'}, {label: 'HIPAA', value: 'hipaa'}, {label: 'NIST-800-53', value: 'nist-800-53'}]" />
<div class="euiFormControlLayoutIcons wz-margin-left-16">
<span class="euiFormControlLayoutCustomIcon">
<react-component name="EuiIcon"
props="{type:'search', className:'euiFormControlLayoutCustomIcon__icon'}" />
</span>
</div>
<button type="submit" aria-label="Search" class="euiButton euiButton--primary euiButton--fill wz-margin-left-8"
ng-click="search(custom_search)">
<react-component name="EuiIcon" props="{type:'search'}"></react-component> Search
</button>
</div>
<md-chips readonly="true" ng-hide="!appliedFilters.length ||
(appliedFilters.length === 1 && includesFilter('path') && mctrl.showingLocalRules)"
ng-if="!editingFile && !mctrl.filesSubTab">
<md-chip class="wz-chip" ng-show="includesFilter('file')">
<span>File: {{getFilter('file')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true" ng-click="removeFilter('file')"></i>
</span>
</md-chip>
<md-chip class="wz-chip" ng-show="includesFilter('path') && !mctrl.showingLocalRules">
<span>Path: {{getFilter('path')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true" ng-click="removeFilter('path')"></i>
</span>
</md-chip>
<md-chip class="wz-chip" ng-show="includesFilter('level')">
<span>Level: {{getFilter('level')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true" ng-click="removeFilter('level')"></i>
</span>
</md-chip>
<md-chip class="wz-chip" ng-show="includesFilter('group')">
<span>Group: {{getFilter('group')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true" ng-click="removeFilter('group')"></i>
</span>
</md-chip>
<md-chip class="wz-chip" ng-show="includesFilter('pci')">
<span>PCI control: {{getFilter('pci')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true" ng-click="removeFilter('pci')"></i>
</span>
</md-chip>
<md-chip class="wz-chip" ng-show="includesFilter('gdpr')">
<span>GDPR: {{getFilter('gdpr')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true" ng-click="removeFilter('gdpr')"></i>
</span>
</md-chip>
<md-chip class="wz-chip" ng-show="includesFilter('hipaa')">
<span>HIPAA: {{getFilter('hipaa')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true" ng-click="removeFilter('hipaa')"></i>
</span>
</md-chip>
<md-chip class="wz-chip" ng-show="includesFilter('nist-800-53')">
<span>NIST-800-53: {{getFilter('nist-800-53')}}
<i class="fa fa-fw fa-times cursor-pointer" aria-hidden="true"
ng-click="removeFilter('nist-800-53')"></i>
</span>
</md-chip>
</md-chips>
<div class="wz-margin-top-16">
<wz-table lens="mctrl.showingLocalRules" custom-columns="true" flex ng-if="mctrl.showingLocalRules" path="'/rules'" keys="[{value: 'id', width: '85px'},{value:'description', width: 'auto'},{value:'groups',nosortable:true, width: '250px'},{value:'pci',nosortable:true, width: '170px'},{value:'gdpr',nosortable:true,width: '170px'}, {value:'hipaa',nosortable:true,width: '170px'}, {value:'nist-800-53',nosortable:true,width: '170px'}, {value: 'level', width: '100px'}, {value:'file',width: '200px'}, {value:'path', width: 'auto'}]"
implicit-filter="appliedFilters" allow-click="true" row-sizes="[16,14,12]" implicit-sort="'id'">
</wz-table>
<wz-table lens="mctrl.showingLocalRules" custom-columns="true" ng-if="!mctrl.showingLocalRules" implicit-filter="appliedFilters" flex
path="'/rules'" keys="[{value: 'id', width: '85px'},{value:'description', width: 'auto'},{value:'groups',nosortable:true, width: '250px'},{value:'pci',nosortable:true, width: '170px'},{value:'gdpr',nosortable:true,width: '170px'}, {value:'hipaa',nosortable:true,width: '170px'}, {value:'nist-800-53',nosortable:true,width: '170px'}, {value: 'level', width: '100px'}, {value:'file',width: '200px'}, {value:'path', width: 'auto'}]"
allow-click="true" row-sizes="[16,14,12]" implicit-sort="'id'">
</wz-table>
</div>
</div>
</div>
</div>
</div>

View File

@ -1,4 +1,3 @@
include ./rules.head include ./rules.head
include ./rules-list.html
include ./rules-detail.html include ./rules-detail.html
include ../../../footer.foot include ../../../footer.foot

View File

@ -1 +0,0 @@
</div>

View File

@ -1,9 +0,0 @@
<div flex="auto" layout="column" ng-if="mctrl.globalRuleSet == 'ruleset'">
<div ng-if="!loading && !viewingDetail" flex layout="row">
<div ng-class="mctrl.logtestOpened ? 'flex-70' : 'flex-100'">
<div class="md-padding-h">
<react-component name="Tabs" props="mctrl.rulesetTabsProps" />
</div>
<div layout="row" class="md-padding-h">
<span flex></span>
</div>

View File

@ -1,8 +1,3 @@
include ./ruleset.head include ./ruleset.html
include ./rules/rules.pug
include ./decoders/decoders.pug
include ./cdblists/cdblists.pug
include ./files/files.pug
include ./logtest.html include ./logtest.html
include ./ruleset.foot
include ../../footer.foot include ../../footer.foot

View File

@ -1,5 +1,5 @@
<div layout="column" layout-align="start stretch" ng-controller="managerStatusController as ctrl" ng-if="mctrl.tab === 'status'" <div layout="column" layout-align="start stretch" ng-controller="managerStatusController as ctrl" ng-if="mctrl.tab === 'status'"
class=""> class="sideBarContent">
<div class="md-padding md-padding-top-16" ng-show="ctrl.load"> <div class="md-padding md-padding-top-16" ng-show="ctrl.load">
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" /> <react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />

View File

@ -1,5 +1,5 @@
<div layout="column" layout-align="start stretch" ng-if="mctrl.tab === 'welcome'"> <div layout="column" layout-align="start stretch" ng-if="mctrl.tab === 'welcome'">
<div layout="row" layout-padding> <div layout="row" layout-padding>
<react-component flex name="WelcomeScreenManagement" props="mctrl.welcomeCardsProps" /> <react-component flex name="WelcomeWrapper" props="mctrl.welcomeCardsProps" />
</div> </div>
</div> </div>