mirror of
https://github.com/valitydev/wazuh-kibana-app.git
synced 2024-11-06 09:55:18 +00:00
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:
parent
79d029fb9a
commit
bc657bf2b5
2
.gitignore
vendored
2
.gitignore
vendored
@ -65,3 +65,5 @@ package-lock.json
|
|||||||
build/
|
build/
|
||||||
|
|
||||||
yarn.lock
|
yarn.lock
|
||||||
|
|
||||||
|
server/wazuh-registry.json
|
@ -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"
|
||||||
|
120
public/components/wz-search-bar/wz-search-bar.tsx
Normal file
120
public/components/wz-search-bar/wz-search-bar.tsx
Normal 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,
|
||||||
|
}
|
@ -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(
|
||||||
|
@ -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
|
||||||
|
274
public/controllers/management/components/agents-groups-table.js
Normal file
274
public/controllers/management/components/agents-groups-table.js
Normal 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
|
||||||
|
};
|
||||||
|
|
197
public/controllers/management/components/files-group-table.js
Normal file
197
public/controllers/management/components/files-group-table.js
Normal 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
|
||||||
|
};
|
||||||
|
|
@ -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
|
||||||
|
};
|
@ -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);
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
@ -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);
|
@ -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> <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 })}>
|
||||||
|
{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 })}>
|
||||||
|
{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> {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);
|
@ -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);
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -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> {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 })}>
|
||||||
|
{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 })}>
|
||||||
|
{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 })}>
|
||||||
|
{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> {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);
|
@ -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);
|
@ -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);
|
@ -0,0 +1,3 @@
|
|||||||
|
.euiSideNavItemButton__content{
|
||||||
|
justify-content: initial;
|
||||||
|
}
|
@ -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);
|
@ -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);
|
@ -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);
|
@ -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);
|
@ -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;
|
@ -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'
|
||||||
|
];
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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;
|
32
public/controllers/management/components/welcome-wrapper.js
Normal file
32
public/controllers/management/components/welcome-wrapper.js
Normal 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>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -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);
|
@ -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;
|
||||||
|
@ -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
@ -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', []);
|
||||||
|
|
||||||
@ -45,5 +52,13 @@ app
|
|||||||
.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);
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -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 });
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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{
|
||||||
@ -1260,3 +1264,49 @@ 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');
|
||||||
|
}
|
@ -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
33
public/react-services/wz-csv.js
vendored
Normal 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
113
public/react-services/wz-request.js
vendored
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
public/redux/actions/managementActions.js
Normal file
7
public/redux/actions/managementActions.js
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
|
||||||
|
export const updateManagementSection = (section) => {
|
||||||
|
return {
|
||||||
|
type: 'UPDATE_MANAGEMENT_SECTION',
|
||||||
|
section
|
||||||
|
}
|
||||||
|
}
|
212
public/redux/actions/rulesetActions.js
Normal file
212
public/redux/actions/rulesetActions.js
Normal 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
|
||||||
|
}
|
||||||
|
}
|
14
public/redux/reducers/managementReducers.js
Normal file
14
public/redux/reducers/managementReducers.js
Normal 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;
|
21
public/redux/reducers/rootReducers.js
Normal file
21
public/redux/reducers/rootReducers.js
Normal 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
|
||||||
|
})
|
||||||
|
|
87
public/redux/reducers/rulesetReducers.js
Normal file
87
public/redux/reducers/rulesetReducers.js
Normal 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
16
public/redux/store.js
Normal 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);
|
25
public/redux/wz-redux-provider.js
Normal file
25
public/redux/wz-redux-provider.js
Normal 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;
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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">
|
@ -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 -->
|
|
||||||
|
|
||||||
<div flex layout="column" layout-align="start stretch" ng-show="!load" ng-init="groupsSelectedTab='agents'">
|
<!-- Inside the group -->
|
||||||
<!-- MD5 Sums and Details cards -->
|
<div flex layout="column" layout-align="start stretch" ng-show="!gp.load && gp.lookingGroup " ng-init="gp.groupsSelectedTab='agents'">
|
||||||
<div layout="row" class="md-padding" ng-if="lookingGroup && currentGroup">
|
|
||||||
<span class="wz-headline-title font-size-18"><i class="fa fa-fw fa-object-group" aria-hidden="true"></i>
|
|
||||||
{{currentGroup.name}}</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- End group actions -->
|
<!-- Tabs for the groups Agents / Files -->
|
||||||
<div ng-if="lookingGroup && currentGroup && !addingAgents && !editingFile && groupsTabsProps" class="md-margin-h">
|
<div ng-if="gp.currentGroup && !gp.addingAgents && !gp.editingFile && gp.groupsTabsProps" class="md-margin-h">
|
||||||
<react-component name="Tabs" props="groupsTabsProps" />
|
<react-component name="Tabs" props="gp.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
|
|
||||||
? '' : ', '}}</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> <span
|
<span ng-show='!gp.multipleSelectorLoading' class="wzMultipleSelectorCounter"><span style='color:green'>+{{gp.currentAdding}}</span>
|
||||||
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>
|
@ -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">
|
||||||
|
@ -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>
|
|
||||||
|
3
public/templates/management/management.html
Normal file
3
public/templates/management/management.html
Normal 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>
|
@ -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
|
||||||
|
@ -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>
|
||||||
@ -103,11 +73,6 @@
|
|||||||
</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>
|
||||||
|
@ -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>
|
||||||
|
@ -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>
|
|
@ -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
|
||||||
|
@ -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 ? '' : ', '}}
|
||||||
|
</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 ? '' : ', '}}
|
|
||||||
</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>
|
@ -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>
|
|
@ -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
|
||||||
|
@ -1 +0,0 @@
|
|||||||
</div>
|
|
@ -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>
|
|
0
public/templates/management/ruleset/ruleset.html
Normal file
0
public/templates/management/ruleset/ruleset.html
Normal 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
|
||||||
|
@ -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'}" />
|
||||||
|
@ -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>
|
Loading…
Reference in New Issue
Block a user