mirror of
https://github.com/valitydev/wazuh-kibana-app.git
synced 2024-11-06 01:45: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
4
.gitignore
vendored
4
.gitignore
vendored
@ -64,4 +64,6 @@ package-lock.json
|
||||
|
||||
build/
|
||||
|
||||
yarn.lock
|
||||
yarn.lock
|
||||
|
||||
server/wazuh-registry.json
|
@ -38,6 +38,7 @@
|
||||
"angular-chart.js": "1.1.1",
|
||||
"angular-cookies": "1.6.5",
|
||||
"angular-material": "1.1.18",
|
||||
"axios": "^0.19.0",
|
||||
"babel-polyfill": "^6.13.0",
|
||||
"dom-to-image": "^2.6.0",
|
||||
"install": "^0.10.1",
|
||||
@ -48,6 +49,9 @@
|
||||
"pdfmake": "^0.1.37",
|
||||
"pug-loader": "^2.4.0",
|
||||
"querystring-browser": "1.0.4",
|
||||
"react-redux": "^7.1.1",
|
||||
"react-codemirror": "^1.0.0",
|
||||
"redux": "^4.0.4",
|
||||
"simple-tail": "^1.1.0",
|
||||
"timsort": "^0.3.0",
|
||||
"winston": "3.0.0"
|
||||
|
@ -7,4 +7,4 @@
|
||||
|
||||
#wazuh-app > div > div.euiComboBoxOptionsList {
|
||||
width: 25%!important;
|
||||
}
|
||||
}
|
||||
|
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(),
|
||||
needsPassword: () => this.needsPassword()
|
||||
};
|
||||
|
||||
this.hasAgents = true;
|
||||
this.init = false;
|
||||
const instance = new DataFactory(
|
||||
|
@ -115,14 +115,15 @@ export class ExportConfiguration extends Component {
|
||||
|
||||
render() {
|
||||
const button = (
|
||||
<EuiButton
|
||||
<EuiButtonEmpty
|
||||
iconType="importAction"
|
||||
iconSide="left"
|
||||
size="s"
|
||||
style={{ marginTop: '4px' }}
|
||||
onClick={this.exportClick.bind(this)}
|
||||
>
|
||||
PDF
|
||||
</EuiButton>
|
||||
Export PDF
|
||||
</EuiButtonEmpty>
|
||||
);
|
||||
return (
|
||||
<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
|
||||
} 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) {
|
||||
super(props);
|
||||
|
||||
this.state = {};
|
||||
}
|
||||
|
||||
switchSection(section) {
|
||||
this.props.switchTab(section, true);
|
||||
this.props.changeManagementSection(section);
|
||||
}
|
||||
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<WzReduxProvider>
|
||||
<EuiFlexGroup>
|
||||
<EuiFlexItem>
|
||||
<EuiPanel betaBadgeLabel="Administration">
|
||||
@ -41,7 +52,7 @@ export class WelcomeScreen extends Component {
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type="indexRollupApp" />}
|
||||
title="Ruleset"
|
||||
onClick={() => this.props.switchTab('ruleset', true)}
|
||||
onClick={() => this.switchSection('ruleset')}
|
||||
description="Manage your Wazuh cluster ruleset."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
@ -50,7 +61,7 @@ export class WelcomeScreen extends Component {
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type="usersRolesApp" />}
|
||||
title="Groups"
|
||||
onClick={() => this.props.switchTab('groups', true)}
|
||||
onClick={() => this.switchSection('groups')}
|
||||
description="Manage your agent groups."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
@ -61,7 +72,7 @@ export class WelcomeScreen extends Component {
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type="devToolsApp" />}
|
||||
title="Configuration"
|
||||
onClick={() => this.props.switchTab('configuration', true)}
|
||||
onClick={() => this.switchSection('configuration')}
|
||||
description="Manage your Wazuh cluster configuration."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
@ -78,7 +89,7 @@ export class WelcomeScreen extends Component {
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type="uptimeApp" />}
|
||||
title="Status"
|
||||
onClick={() => this.props.switchTab('status', true)}
|
||||
onClick={() => this.switchSection('status')}
|
||||
description="Manage your Wazuh cluster status."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
@ -87,7 +98,7 @@ export class WelcomeScreen extends Component {
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type="indexPatternApp" />}
|
||||
title="Cluster"
|
||||
onClick={() => this.props.switchTab('monitoring', true)}
|
||||
onClick={() => this.switchSection('monitoring')}
|
||||
description="Visualize your Wazuh cluster."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
@ -98,7 +109,7 @@ export class WelcomeScreen extends Component {
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type="filebeatApp" />}
|
||||
title="Logs"
|
||||
onClick={() => this.props.switchTab('logs', true)}
|
||||
onClick={() => this.switchSection('logs')}
|
||||
description="Logs from your Wazuh cluster."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
@ -107,7 +118,7 @@ export class WelcomeScreen extends Component {
|
||||
layout="horizontal"
|
||||
icon={<EuiIcon size="xl" type="reportingApp" />}
|
||||
title="Reporting"
|
||||
onClick={() => this.props.switchTab('reporting', true)}
|
||||
onClick={() => this.switchSection('reporting')}
|
||||
description="Check your stored Wazuh reports."
|
||||
/>
|
||||
</EuiFlexItem>
|
||||
@ -115,7 +126,7 @@ export class WelcomeScreen extends Component {
|
||||
</EuiPanel>
|
||||
</EuiFlexItem>
|
||||
</EuiFlexGroup>
|
||||
</div>
|
||||
</WzReduxProvider>
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -123,3 +134,11 @@ export class WelcomeScreen extends Component {
|
||||
WelcomeScreen.propTypes = {
|
||||
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.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.selectData;
|
||||
|
@ -66,7 +66,6 @@ export class FilesController {
|
||||
this.$scope.closeEditingFile = (flag = false) => {
|
||||
this.$scope.viewingDetail = false;
|
||||
this.$scope.editingFile = false;
|
||||
this.$scope.editingFile = false;
|
||||
this.$scope.editorReadOnly = false;
|
||||
this.$scope.fetchedXML = null;
|
||||
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 { FilesController } from './files';
|
||||
import { WelcomeScreen } from './components/welcome';
|
||||
import { WelcomeWrapper } from './components/welcome-wrapper';
|
||||
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 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', []);
|
||||
|
||||
@ -43,7 +50,15 @@ app
|
||||
.controller('configurationRulesetController', ConfigurationRulesetController)
|
||||
.controller('configurationGroupsController', ConfigurationGroupsController)
|
||||
.controller('editionController', EditionController)
|
||||
.controller('filesController', FilesController)
|
||||
.controller('filesController', FilesController)
|
||||
.value('WelcomeScreenManagement', WelcomeScreen)
|
||||
.value('WelcomeWrapper', WelcomeWrapper)
|
||||
.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);
|
||||
|
@ -156,16 +156,6 @@ export class ManagementController {
|
||||
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 = {
|
||||
clickAction: tab => this.switchTab(tab, true),
|
||||
selectedTab: this.tab,
|
||||
@ -182,6 +172,12 @@ export class ManagementController {
|
||||
close: () => this.openLogtest(),
|
||||
showClose: true
|
||||
};
|
||||
|
||||
this.managementProps = {
|
||||
switchTab: (section) => this.switchTab(section, true),
|
||||
section: "",
|
||||
groupsProps: {},
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@ -324,6 +320,7 @@ export class ManagementController {
|
||||
this.currentList = false;
|
||||
this.managementTabsProps.selectedTab = this.tab;
|
||||
}
|
||||
this.managementProps.section = this.tab === 'ruleset' ? this.rulesetTab : this.tab;
|
||||
this.$location.search('tab', this.tab);
|
||||
this.loadNodeList();
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Wazuh app - Ruleset controllers
|
||||
* @param {Objet} * Wazuh app - Ruleset controllers
|
||||
* Copyright (C) 2015-2019 Wazuh, Inc.
|
||||
*
|
||||
* 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';
|
||||
|
||||
export function RulesController(
|
||||
$scope,
|
||||
$sce,
|
||||
errorHandler,
|
||||
appState,
|
||||
csvReq,
|
||||
wzTableFilter,
|
||||
$location,
|
||||
apiReq,
|
||||
wazuhConfig,
|
||||
rulesetHandler
|
||||
) {
|
||||
$scope.overwriteError = false;
|
||||
$scope.isObject = item => typeof item === 'object';
|
||||
$scope.mctrl = $scope.$parent.$parent.$parent.mctrl;
|
||||
$scope.mctrl.showingLocalRules = false;
|
||||
$scope.mctrl.onlyLocalFiles = false;
|
||||
$scope.appliedFilters = [];
|
||||
|
||||
export class RulesController {
|
||||
/**
|
||||
* Class constructor
|
||||
* @param {Objet} $scope
|
||||
* @param {Objet} $sce
|
||||
* @param {Objet} errorHandler
|
||||
* @param {Objet} appState
|
||||
* @param {Objet} csvReq
|
||||
* @param {Objet} wzTableFilter
|
||||
* @param {Objet} $location
|
||||
* @param {Objet} apiReq
|
||||
* @param {Objet} wazuhConfig
|
||||
* @param {Objet} rulesetHandler
|
||||
*/
|
||||
|
||||
constructor(
|
||||
$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
|
||||
* @param {String} term
|
||||
* @param {Boolean} fromClick
|
||||
*/
|
||||
$scope.search = (term, fromClick = false) => {
|
||||
search(term, fromClick = false) {
|
||||
let clearInput = true;
|
||||
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() };
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'group'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
} else if (
|
||||
term &&
|
||||
term.startsWith('level:') &&
|
||||
term.split('level:')[1].trim()
|
||||
) {
|
||||
$scope.custom_search = '';
|
||||
this.custom_search = '';
|
||||
const filter = { name: 'level', value: term.split('level:')[1].trim() };
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'level'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
} else if (
|
||||
term &&
|
||||
term.startsWith('pci:') &&
|
||||
term.split('pci:')[1].trim()
|
||||
) {
|
||||
$scope.custom_search = '';
|
||||
this.custom_search = '';
|
||||
const filter = { name: 'pci', value: term.split('pci:')[1].trim() };
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'pci'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
} else if (
|
||||
term &&
|
||||
term.startsWith('gdpr:') &&
|
||||
term.split('gdpr:')[1].trim()
|
||||
) {
|
||||
$scope.custom_search = '';
|
||||
this.custom_search = '';
|
||||
const filter = { name: 'gdpr', value: term.split('gdpr:')[1].trim() };
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'gdpr'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
} else if (
|
||||
term &&
|
||||
term.startsWith('hipaa:') &&
|
||||
term.split('hipaa:')[1].trim()
|
||||
) {
|
||||
$scope.custom_search = '';
|
||||
this.custom_search = '';
|
||||
const filter = { name: 'hipaa', value: term.split('hipaa:')[1].trim() };
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'hipaa'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
} else if (
|
||||
term &&
|
||||
term.startsWith('nist-800-53:') &&
|
||||
term.split('nist-800-53:')[1].trim()
|
||||
) {
|
||||
$scope.custom_search = '';
|
||||
this.custom_search = '';
|
||||
const filter = {
|
||||
name: 'nist-800-53',
|
||||
value: term.split('nist-800-53:')[1].trim()
|
||||
};
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'nist-800-53'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
} else if (
|
||||
term &&
|
||||
term.startsWith('file:') &&
|
||||
term.split('file:')[1].trim()
|
||||
) {
|
||||
$scope.custom_search = '';
|
||||
this.custom_search = '';
|
||||
const filter = { name: 'file', value: term.split('file:')[1].trim() };
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'file'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
} else if (
|
||||
term &&
|
||||
term.startsWith('path:') &&
|
||||
term.split('path:')[1].trim()
|
||||
) {
|
||||
$scope.custom_search = '';
|
||||
if (!$scope.mctrl.showingLocalRules) {
|
||||
this.custom_search = '';
|
||||
if (!this.mctrl.showingLocalRules) {
|
||||
const filter = { name: 'path', value: term.split('path:')[1].trim() };
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
item => item.name !== 'path'
|
||||
);
|
||||
$scope.appliedFilters.push(filter);
|
||||
$scope.$broadcast('wazuhFilter', { filter });
|
||||
this.appliedFilters.push(filter);
|
||||
this.scope.$broadcast('wazuhFilter', { filter });
|
||||
}
|
||||
} else {
|
||||
clearInput = false;
|
||||
$scope.$broadcast('wazuhSearch', { term, removeFilters: 0 });
|
||||
this.scope.$broadcast('wazuhSearch', { term, removeFilters: 0 });
|
||||
}
|
||||
if (clearInput && !fromClick) {
|
||||
const searchBar = $('#search-input-rules');
|
||||
searchBar.val('');
|
||||
}
|
||||
$scope.$applyAsync();
|
||||
};
|
||||
this.scope.$applyAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* This show us if new filter is already included in filters
|
||||
* @param {String} filterName
|
||||
*/
|
||||
$scope.includesFilter = filterName =>
|
||||
$scope.appliedFilters.map(item => item.name).includes(filterName);
|
||||
* This show us if new filter is already included in filters
|
||||
* @param {String} filterName
|
||||
*/
|
||||
includesFilter(filterName) {
|
||||
return this.appliedFilters.map(item => item.name).includes(filterName);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a filter given its name
|
||||
* @param {String} filterName
|
||||
*/
|
||||
$scope.getFilter = filterName => {
|
||||
const filtered = $scope.appliedFilters.filter(
|
||||
getFilter(filterName) {
|
||||
const filtered = this.appliedFilters.filter(
|
||||
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) {
|
||||
$scope.appliedFilters.push({ name: 'path', value: 'etc/rules' });
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Swotch between tabs
|
||||
*/
|
||||
switchLocalRules() {
|
||||
this.removeFilter('path');
|
||||
if (!this.mctrl.showingLocalRules) this.appliedFilters.push({ name: 'path', value: 'etc/rules' });
|
||||
}
|
||||
|
||||
/**
|
||||
* This a the filter given its name
|
||||
* @param {String} filterName
|
||||
*/
|
||||
$scope.removeFilter = filterName => {
|
||||
$scope.appliedFilters = $scope.appliedFilters.filter(
|
||||
removeFilter(filterName) {
|
||||
this.appliedFilters = this.appliedFilters.filter(
|
||||
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
|
||||
*/
|
||||
$scope.colorRuleArg = ruleArg => {
|
||||
* This set color to a given rule argument
|
||||
* @param {String} ruleArg
|
||||
*/
|
||||
colorRuleArg(ruleArg) {
|
||||
ruleArg = ruleArg.toString();
|
||||
let valuesArray = ruleArg.match(/\$\(((?!<\/span>).)*?\)(?!<\/span>)/gim);
|
||||
let coloredString = ruleArg;
|
||||
@ -204,38 +315,28 @@ export function RulesController(
|
||||
coloredString = coloredString.replace(
|
||||
/\$\(((?!<\/span>).)*?\)(?!<\/span>)/im,
|
||||
'<span style="color: ' +
|
||||
colors[i] +
|
||||
' ">' +
|
||||
valuesArray[i] +
|
||||
'</span>'
|
||||
colors[i] +
|
||||
' ">' +
|
||||
valuesArray[i] +
|
||||
'</span>'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $sce.trustAsHtml(coloredString);
|
||||
};
|
||||
|
||||
$scope.$on('closeRuleView', () => {
|
||||
$scope.closeDetailView();
|
||||
});
|
||||
|
||||
// Reloading event listener
|
||||
$scope.$on('rulesetIsReloaded', () => {
|
||||
$scope.viewingDetail = false;
|
||||
$scope.$applyAsync();
|
||||
});
|
||||
return this.sce.trustAsHtml(coloredString);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get full data on CSV format
|
||||
*/
|
||||
$scope.downloadCsv = async () => {
|
||||
async downloadCsv() {
|
||||
try {
|
||||
errorHandler.info('Your download should begin automatically...', 'CSV');
|
||||
const currentApi = JSON.parse(appState.getCurrentAPI()).id;
|
||||
const output = await csvReq.fetch(
|
||||
this.errorHandler.info('Your download should begin automatically...', 'CSV');
|
||||
const currentApi = JSON.parse(this.appState.getCurrentAPI()).id;
|
||||
const output = await this.csvReq.fetch(
|
||||
'/rules',
|
||||
currentApi,
|
||||
wzTableFilter.get()
|
||||
this.wzTableFilter.get()
|
||||
);
|
||||
const blob = new Blob([output], { type: 'text/csv' }); // eslint-disable-line
|
||||
|
||||
@ -243,188 +344,164 @@ export function RulesController(
|
||||
|
||||
return;
|
||||
} catch (error) {
|
||||
errorHandler.handle(error, 'Download CSV');
|
||||
this.errorHandler.handle(error, 'Download CSV');
|
||||
}
|
||||
return;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* This function takes back to the list but adding a filter from the detail view
|
||||
*/
|
||||
$scope.addDetailFilter = (name, value) => {
|
||||
* This function takes back to the list but adding a filter from the detail view
|
||||
* @param {String} name
|
||||
* @param {String} value
|
||||
*/
|
||||
addDetailFilter(name, value) {
|
||||
// Go back to the list
|
||||
$scope.closeDetailView();
|
||||
$scope.search(`${name}:${value}`);
|
||||
};
|
||||
this.closeDetailView();
|
||||
this.search(`${name}:${value}`);
|
||||
}
|
||||
|
||||
$scope.openFile = (file, path) => {
|
||||
/**
|
||||
* Open a file
|
||||
* @param {String} file
|
||||
* @param {String} path
|
||||
*/
|
||||
openFile(file, path) {
|
||||
if (file && path) {
|
||||
$scope.mctrl.switchFilesSubTab('rules', {
|
||||
this.mctrl.switchFilesSubTab('rules', {
|
||||
parameters: {
|
||||
file: { file, path },
|
||||
path,
|
||||
viewingDetail: $scope.viewingDetail
|
||||
viewingDetail: this.viewingDetail
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
//listeners
|
||||
$scope.$on('wazuhShowRule', (event, parameters) => {
|
||||
$scope.currentRule = parameters.rule;
|
||||
$scope.$emit('setCurrentRule', { currentRule: $scope.currentRule });
|
||||
if (!(Object.keys(($scope.currentRule || {}).details || {}) || []).length) {
|
||||
$scope.currentRule.details = false;
|
||||
}
|
||||
$scope.viewingDetail = true;
|
||||
$scope.$applyAsync();
|
||||
});
|
||||
|
||||
$scope.editRulesConfig = async () => {
|
||||
$scope.editingFile = true;
|
||||
/**
|
||||
* Open an edit a rules file
|
||||
*/
|
||||
async editRulesConfig() {
|
||||
this.editingFile = true;
|
||||
try {
|
||||
$scope.fetchedXML = await rulesetHandler.getRuleConfiguration(
|
||||
$scope.currentRule.file
|
||||
this.fetchedXML = await this.rulesetHandler.getRuleConfiguration(
|
||||
this.currentRule.file
|
||||
);
|
||||
$location.search('editingFile', true);
|
||||
appState.setNavigation({ status: true });
|
||||
$scope.$applyAsync();
|
||||
$scope.$broadcast('fetchedFile', { data: $scope.fetchedXML });
|
||||
this.location.search('editingFile', true);
|
||||
this.appState.setNavigation({ status: true });
|
||||
this.scope.$applyAsync();
|
||||
this.scope.$broadcast('fetchedFile', { data: this.scope.fetchedXML });
|
||||
} catch (error) {
|
||||
$scope.fetchedXML = null;
|
||||
errorHandler.handle(error, 'Fetch file error');
|
||||
this.fetchedXML = null;
|
||||
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 {
|
||||
const ruleReloaded = await apiReq.request(
|
||||
const ruleReloaded = await this.apiReq.request(
|
||||
'GET',
|
||||
`/rules/${$scope.currentRule.id}`,
|
||||
`/rules/${this.currentRule.id}`,
|
||||
{}
|
||||
);
|
||||
const response =
|
||||
(((ruleReloaded || {}).data || {}).data || {}).items || [];
|
||||
if (response.length) {
|
||||
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 {
|
||||
$scope.currentRule = false;
|
||||
$scope.closeDetailView(true);
|
||||
this.currentRule = false;
|
||||
this.closeDetailView(true);
|
||||
}
|
||||
$scope.fetchedXML = false;
|
||||
this.fetchedXML = false;
|
||||
} catch (error) {
|
||||
errorHandler.handle(error.message || error);
|
||||
this.errorHandler.handle(error.message || error);
|
||||
}
|
||||
}
|
||||
|
||||
$scope.editingFile = false;
|
||||
$scope.$applyAsync();
|
||||
appState.setNavigation({ status: true });
|
||||
$scope.$broadcast('closeEditXmlFile', {});
|
||||
$scope.$applyAsync();
|
||||
};
|
||||
this.editingFile = false;
|
||||
this.scope.$applyAsync();
|
||||
this.appState.setNavigation({ status: true });
|
||||
this.scope.$broadcast('closeEditXmlFile', {});
|
||||
this.scope.$applyAsync();
|
||||
}
|
||||
|
||||
$scope.xmlIsValid = valid => {
|
||||
$scope.xmlHasErrors = valid;
|
||||
$scope.$applyAsync();
|
||||
};
|
||||
/**
|
||||
* Checks if the XML is false
|
||||
*/
|
||||
xmlIsValid() {
|
||||
this.xmlHasErrors = valid;
|
||||
this.scope.$applyAsync();
|
||||
}
|
||||
|
||||
/**
|
||||
* This function changes to the rules list view
|
||||
*/
|
||||
$scope.closeDetailView = clear => {
|
||||
$scope.mctrl.showingLocalRules = !$scope.mctrl.showingLocalRules;
|
||||
closeDetailView(clear) {
|
||||
this.mctrl.showingLocalRules = !this.mctrl.showingLocalRules;
|
||||
if (clear)
|
||||
$scope.appliedFilters = $scope.appliedFilters.slice(
|
||||
this.appliedFilters = this.appliedFilters.slice(
|
||||
0,
|
||||
$scope.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.appliedFilters.length - 1
|
||||
);
|
||||
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;
|
||||
$scope.$applyAsync();
|
||||
};
|
||||
/**
|
||||
* Enable the save
|
||||
*/
|
||||
toggleSaveConfig() {
|
||||
this.doingSaving = false;
|
||||
this.scope.$applyAsync();
|
||||
}
|
||||
|
||||
$scope.toggleRestartMsg = () => {
|
||||
$scope.restartBtn = false;
|
||||
$scope.$applyAsync();
|
||||
};
|
||||
/**
|
||||
* Enable the restart
|
||||
*/
|
||||
toggleRestartMsg() {
|
||||
this.restartBtn = false;
|
||||
this.scope.$applyAsync();
|
||||
}
|
||||
|
||||
$scope.cancelSaveAndOverwrite = () => {
|
||||
$scope.overwriteError = false;
|
||||
$scope.$applyAsync();
|
||||
};
|
||||
/**
|
||||
* Cancel the save
|
||||
*/
|
||||
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 =
|
||||
clusterInfo.status === 'enabled' ? 'cluster' : 'manager';
|
||||
$scope.doingSaving = true;
|
||||
this.doingSaving = true;
|
||||
const objParam = {
|
||||
rule: $scope.currentRule,
|
||||
rule: this.currentRule,
|
||||
showRestartManager,
|
||||
isOverwrite: !!$scope.overwriteError
|
||||
};
|
||||
$scope.$broadcast('saveXmlFile', objParam);
|
||||
};
|
||||
isOverwrite: !!this.overwriteError
|
||||
}
|
||||
this.scope.$broadcast('saveXmlFile', objParam);
|
||||
}
|
||||
|
||||
$scope.restart = () => {
|
||||
$scope.$emit('performRestart', {});
|
||||
};
|
||||
|
||||
$scope.$on('showRestart', () => {
|
||||
$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 });
|
||||
});
|
||||
/**
|
||||
* Emit the event to restart
|
||||
*/
|
||||
restart() {
|
||||
this.scope.$emit('performRestart', {});
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1205,4 +1205,4 @@ function discoverController(
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
init();
|
||||
}
|
||||
}
|
@ -16,6 +16,10 @@
|
||||
|
||||
/* Custom healthcheck and blank screen styles */
|
||||
|
||||
.kbnGlobalBannerList{
|
||||
display: none;
|
||||
}
|
||||
|
||||
.error-notify {
|
||||
font-size: 20px;
|
||||
color: black;
|
||||
@ -164,7 +168,7 @@
|
||||
}
|
||||
|
||||
.euiFlexGroup .euiFlexGroup:hover {
|
||||
background: #fafcfe;
|
||||
//background: #fafcfe;
|
||||
}
|
||||
|
||||
/* Custom Manager/Status styles */
|
||||
@ -1011,7 +1015,7 @@ discover-app-w .container-fluid {
|
||||
}
|
||||
|
||||
.application{
|
||||
//background: #fafbfd;
|
||||
background: #fafbfd;
|
||||
}
|
||||
|
||||
.application.tab-health-check wz-menu{
|
||||
@ -1259,4 +1263,50 @@ md-chips.md-default-theme .md-chips, md-chips .md-chips{
|
||||
|
||||
.table-vis-container{
|
||||
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),
|
||||
textarea,
|
||||
input,
|
||||
select,
|
||||
.wz-chip {
|
||||
font-family: 'Open Sans', Helvetica, Arial, sans-serif !important;
|
||||
select{
|
||||
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) {
|
||||
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() {
|
||||
try {
|
||||
const result = await this.apiReq.request('GET', `/rules`, {
|
||||
path: 'etc/rules'
|
||||
});
|
||||
return result;
|
||||
return ((result || {}).data || {}).data || false;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@ -29,7 +48,7 @@ export class RulesetHandler {
|
||||
const result = await this.apiReq.request('GET', `/decoders`, {
|
||||
path: 'etc/decoders'
|
||||
});
|
||||
return result;
|
||||
return ((result || {}).data || {}).data || false;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
@ -64,7 +83,7 @@ export class RulesetHandler {
|
||||
const result = await this.apiReq.request('GET', `/manager/files`, {
|
||||
path: path
|
||||
});
|
||||
return result;
|
||||
return ((result || {}).data || {}).data || false;
|
||||
} catch (error) {
|
||||
return Promise.reject(error);
|
||||
}
|
||||
|
@ -1,2 +1,2 @@
|
||||
<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"
|
||||
ng-if="mctrl.tab == 'groups'">
|
||||
<md-content flex layout="column" class=" overflow-hidden" ng-init="lookingGroup=false">
|
||||
<div flex layout="column" ng-class="{'cursor-wait': multipleSelectorLoading}" ng-controller="groupsPreviewController as gp"
|
||||
ng-if="mctrl.tab == 'groups'" class="sideBarContent">
|
||||
|
||||
<!-- Main content -->
|
||||
<md-content flex layout="column" class=" overflow-hidden" ng-init="gp.lookingGroup=false">
|
||||
|
||||
<!-- Loading ring -->
|
||||
<div class="md-padding md-padding-top-16" ng-show="load">
|
||||
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />
|
||||
</div>
|
||||
|
||||
<!-- Headline -->
|
||||
<div ng-show="!load" layout="column" layout-padding ng-if="!currentGroup || !currentGroup.name">
|
||||
<div layout="row">
|
||||
<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>
|
||||
<!-- Groups table -->
|
||||
<div ng-show="!gp.load" layout="column" ng-if="(!gp.currentGroup || !gp.currentGroup.name) && gp.groupsTableProps && !gp.lookingGroup">
|
||||
<react-component name="GroupsTable" props="gp.groupsTableProps" />
|
||||
</div>
|
||||
<!-- End headline -->
|
||||
|
||||
<div flex layout="column" layout-align="start stretch" ng-show="!load" ng-init="groupsSelectedTab='agents'">
|
||||
<!-- MD5 Sums and Details cards -->
|
||||
<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 -->
|
||||
<div ng-if="lookingGroup && currentGroup && !addingAgents && !editingFile && groupsTabsProps" class="md-margin-h">
|
||||
<react-component name="Tabs" props="groupsTabsProps" />
|
||||
|
||||
<!-- Inside the group -->
|
||||
<div flex layout="column" layout-align="start stretch" ng-show="!gp.load && gp.lookingGroup " ng-init="gp.groupsSelectedTab='agents'">
|
||||
|
||||
<!-- Tabs for the groups Agents / Files -->
|
||||
<div ng-if="gp.currentGroup && !gp.addingAgents && !gp.editingFile && gp.groupsTabsProps" class="md-margin-h">
|
||||
<react-component name="Tabs" props="gp.groupsTabsProps" />
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<span ng-click='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'>
|
||||
<span ng-show='!xmlHasErrors'>
|
||||
<span ng-click='gp.closeEditingFile()' class='wz-margin-top-3 kuiButton kuiButton--hollow'>Cancel</span>
|
||||
<button ng-disabled='gp.xmlHasErrors' ng-click='gp.doSaveGroupAgentConfig()' class='height-35 kuiButton kuiButton--secondary wz-margin-left-8'>
|
||||
<span ng-show='!gp.xmlHasErrors'>
|
||||
<react-component name="EuiIcon" props="{type: 'save'}" /> Save file
|
||||
</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>
|
||||
</button>
|
||||
</div>
|
||||
<div class="md-padding md-padding-top-10" ng-if="fetchedXML && currentGroup">
|
||||
<wz-xml-file-editor file-name='agent.conf' data="fetchedXML" target-name="currentGroup.name + ' group'"
|
||||
valid-fn='xmlIsValid(valid)' close-fn='closeEditingFile(reload)'>
|
||||
<div class="md-padding md-padding-top-10" ng-if="gp.fetchedXML && gp.currentGroup">
|
||||
<wz-xml-file-editor file-name='agent.conf' data="gp.fetchedXML" target-name="gp.currentGroup.name + ' group'"
|
||||
valid-fn='gp.xmlIsValid(valid)' close-fn='gp.closeEditingFile(reload)'>
|
||||
</wz-xml-file-editor>
|
||||
</div>
|
||||
</div>
|
||||
<!-- XML editor for group agents -->
|
||||
<!-- End XML editor for group agents -->
|
||||
|
||||
<div ng-if="!editingFile">
|
||||
<div layout="row" class="md-padding" ng-if="lookingGroup && currentGroup && addingAgents">
|
||||
<span ng-click='addMultipleAgents(false)' class='wz-margin-top-3 kuiButton kuiButton--hollow'>
|
||||
<div ng-if="!gp.editingFile">
|
||||
<div layout="row" class="md-padding" ng-if="gp.currentGroup && gp.addingAgents">
|
||||
<span ng-click='gp.addMultipleAgents(false)' class='wz-margin-top-3 kuiButton kuiButton--hollow'>
|
||||
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>
|
||||
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>
|
||||
</div>
|
||||
|
||||
<!-- Search bar -->
|
||||
<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>
|
||||
<div ng-if='!gp.addingAgents'>
|
||||
|
||||
<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 -->
|
||||
<md-card flex class="wz-md-card _md flex md-margin-h wz-margin-top-16" ng-if="lookingGroup && groupsSelectedTab==='agents' && currentGroup">
|
||||
<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>
|
||||
<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>
|
||||
<div flex ng-if="gp.groupsSelectedTab==='agents' && gp.currentGroup">
|
||||
<react-component name="AgentsInGroupTable" props="gp.agentsInGroupTableProps"/>
|
||||
</div>
|
||||
|
||||
<!-- 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">
|
||||
<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 -->
|
||||
<react-component ng-if="gp.groupsSelectedTab==='files' && !gp.fileViewer && gp.currentGroup" name="FilesInGroupTable" props="gp.filesInGroupTableProps"/>
|
||||
|
||||
<!-- 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 layout="row" class="wz-padding-bottom-14">
|
||||
<span flex class="wz-headline-title">{{ filename }}</span>
|
||||
<button class="md-icon-button" ng-if="lookingGroup" aria-label="Back" tooltip="Close file"
|
||||
tooltip-placement="left" ng-click="goBackFiles()"><i class="fa fa-fw fa-close"
|
||||
<span flex class="wz-headline-title">{{ gp.filename }}</span>
|
||||
<!-- Go back button -->
|
||||
<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>
|
||||
<!--<span flex class="wz-text-right cursor-pointer color-grey" ng-click="goBackFiles()">close</span>-->
|
||||
</div>
|
||||
<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>
|
||||
<!-- End File JSON viewer section -->
|
||||
</div>
|
||||
|
||||
<div layout="row" class="md-padding" ng-if="addingAgents">
|
||||
<span ng-show='!multipleSelectorLoading' class="wzMultipleSelectorCounter"><span style='color:green'>+{{currentAdding}}</span> <span
|
||||
style='color:red'>-{{currentDeleting}}</span></span>
|
||||
<wz-multiple-selector class='wzMultipleSelector' available-items="availableAgents.data"
|
||||
selected-items="selectedAgents.data" title-available-items="Available agents"
|
||||
title-selected-items="Current agents in the group" total-selected-items="totalSelectedAgents"
|
||||
reload-scroll='reload(element, searchTerm, 499, start)' limit="checkLimit()">
|
||||
<div layout="row" class="md-padding" ng-if="gp.addingAgents">
|
||||
<span ng-show='!gp.multipleSelectorLoading' class="wzMultipleSelectorCounter"><span style='color:green'>+{{gp.currentAdding}}</span>
|
||||
<span style='color:red'>-{{gp.currentDeleting}}</span></span>
|
||||
<wz-multiple-selector class='wzMultipleSelector' available-items="gp.availableAgents.data"
|
||||
selected-items="gp.selectedAgents.data" title-available-items="Available agents"
|
||||
title-selected-items="Current agents in the group" total-selected-items="gp.totalSelectedAgents"
|
||||
reload-scroll='gp.reload(element, searchTerm, 499, start)' limit="gp.checkLimit()">
|
||||
</wz-multiple-selector>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End inside the group -->
|
||||
</md-content>
|
||||
</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 -->
|
||||
<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 -->
|
||||
<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 -->
|
||||
<div layout="row" layout-padding>
|
||||
<!-- If you're not on the Welcome tab, show a functional breadcrumb -->
|
||||
<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>
|
||||
<div layout="row" layout-padding ng-if="mctrl.tab === 'groups' && mctrl.currentGroup && mctrl.currentGroup.name">
|
||||
<span class="wz-text-link cursor-pointer" ng-click="mctrl.switchTab('groups', true)">{{ mctrl.tabNames[mctrl.tab] }}</span>
|
||||
<span> / {{ mctrl.currentGroup.name }} </span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- End breadcrumbs -->
|
||||
</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="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'"
|
||||
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"
|
||||
ng-click="mctrl.switchTab('configuration', true)">{{ mctrl.tabNames[mctrl.tab] }}</span>
|
||||
<span ng-if="mctrl.editionTab !== 'editconfiguration'"> /
|
||||
@ -61,8 +27,3 @@
|
||||
</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 ./logs.html
|
||||
include ./reporting.html
|
||||
|
||||
include ./management.html
|
||||
|
||||
include ./groups/groups.html
|
||||
include ./ruleset/ruleset.pug
|
||||
|
||||
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">
|
||||
|
||||
<!-- 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 -->
|
||||
<div flex layout="row" layout-align="start start" ng-if="!isClusterEnabled">
|
||||
<md-card flex class="wz-md-card" flex>
|
||||
@ -61,27 +43,17 @@
|
||||
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />
|
||||
</div>
|
||||
|
||||
|
||||
<!-- Navigation section -->
|
||||
<div layout="row" layout-align="start center" ng-if="!loading">
|
||||
<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">
|
||||
<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> / </span>
|
||||
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span>
|
||||
<span> / Overview</span>
|
||||
</div>
|
||||
<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> / </span>
|
||||
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span>
|
||||
@ -89,8 +61,6 @@
|
||||
<span>Nodes</span>
|
||||
</div>
|
||||
<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> / </span>
|
||||
<span class="wz-text-link cursor-pointer" ng-click="goBack()">{{ currentAPI }}</span>
|
||||
@ -102,12 +72,7 @@
|
||||
</div>
|
||||
</div>
|
||||
<!-- 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 -->
|
||||
<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">
|
||||
<react-component name="EuiProgress" props="{size: 'xs', color: 'primary'}" />
|
||||
</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-detail.html
|
||||
include ../../../footer.foot
|
||||
|
@ -1,201 +1,213 @@
|
||||
<div ng-if="!loading && viewingDetail" layout="column" ng-class="{ 'md-padding' : editingFile }">
|
||||
<!-- Back button and title -->
|
||||
<div layout="row" layout-align="start center" ng-show="!editingFile">
|
||||
<!-- Back button -->
|
||||
<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>
|
||||
<!-- Rule title -->
|
||||
<div>
|
||||
<h1 class="font-size-18" ng-bind-html="colorRuleArg(currentRule.description)"></h1>
|
||||
</div>
|
||||
<div ng-if="!rctrl.loading && rctrl.viewingDetail" layout="column" ng-class="{ 'md-padding' : editingFile }">
|
||||
<!-- Back button and title -->
|
||||
<div layout="row" layout-align="start center" ng-show="!rctrl.editingFile">
|
||||
<!-- Back button -->
|
||||
<md-button class="md-icon-button" style="margin: 5px!important;" aria-label="Back to rules list" tooltip="Back"
|
||||
tooltip-placement="bottom" ng-click="rctrl.closeDetailView(true)"><i class="fa fa-fw fa-arrow-left"
|
||||
aria-hidden="true"></i></md-button>
|
||||
<!-- Rule title -->
|
||||
<div>
|
||||
<h1 class="font-size-18" ng-bind-html="rctrl.colorRuleArg(rctrl.currentRule.description)"></h1>
|
||||
</div>
|
||||
<!-- End back button, title and status indicator -->
|
||||
</div>
|
||||
<!-- End back button, title and status indicator -->
|
||||
|
||||
<!-- Rule information ribbon -->
|
||||
<div layout="row" ng-show="!editingFile" class="wz-padding-left-8 wz-padding-right-8">
|
||||
<md-card flex class="wz-metric-color wz-md-card">
|
||||
<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="currentRule.level || currentRule.level == 0" class="wz-text-truncatable">Level:
|
||||
<span class="wz-text-bold wz-text-link" ng-click="addDetailFilter('level', currentRule.level)"
|
||||
tooltip="Filter by this level" tooltip-placement="bottom">{{currentRule.level}}</span></div>
|
||||
<div flex="35" ng-if="currentRule.file" class="wz-text-truncatable">File: <span
|
||||
class="wz-text-bold wz-text-link" ng-click="openFile(currentRule.file,currentRule.path)"
|
||||
tooltip="Filter by this file" tooltip-placement="bottom">{{currentRule.file}}</span></div>
|
||||
<div flex="35" ng-if="currentRule.path" class="wz-text-truncatable">Path: <span
|
||||
class="wz-text-bold wz-text-link" ng-click="addDetailFilter('path', currentRule.path)"
|
||||
tooltip="Filter by this path" tooltip-placement="bottom">{{currentRule.path}}</span></div>
|
||||
<!-- Rule information ribbon -->
|
||||
<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-content layout="row" class="wz-padding-metric">
|
||||
<div flex="15" ng-if= "rctrl.currentRule.id" class="wz-text-truncatable">ID: <span
|
||||
class="wz-text-bold">{{rctrl.currentRule.id}}</span></div>
|
||||
<div flex="15" ng-if="rctrl.currentRule.level || rctrl.currentRule.level == 0" class="wz-text-truncatable">
|
||||
Level:
|
||||
<span class="wz-text-bold wz-text-link" ng-click="rctrl.addDetailFilter('level', rctrl.currentRule.level)"
|
||||
tooltip="Filter by this level" tooltip-placement="bottom">{{rctrl.currentRule.level}}</span></div>
|
||||
<div flex="35" ng-if="rctrl.currentRule.file" class="wz-text-truncatable">File: <span
|
||||
class="wz-text-bold wz-text-link" ng-click="rctrl.openFile(rctrl.currentRule.file,rctrl.currentRule.path)"
|
||||
tooltip="Filter by this file" tooltip-placement="bottom">{{rctrl.currentRule.file}}</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>
|
||||
</div>
|
||||
<!-- End related rules section -->
|
||||
</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">
|
||||
<react-component name="EuiIcon" props="{type:'pencil'}" />
|
||||
Edit {{ currentRule.file }}
|
||||
</button>
|
||||
|
||||
<!-- XML editor for rules -->
|
||||
<div layout="column" layout-align="start" ng-show="rctrl.editingFile">
|
||||
<div layout="row">
|
||||
<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>
|
||||
|
||||
<!-- Rest of rule information -->
|
||||
<div layout="column" layout-align="start" ng-show="!editingFile">
|
||||
|
||||
<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 class="wz-padding-top-14" ng-if="rctrl.fetchedXML">
|
||||
<wz-xml-file-editor file-name='rules' data="rctrl.fetchedXML" target-name="rctrl.currentRule.file"
|
||||
valid-fn='xmlIsValid(valid)' saving-param='rctrl.toggleSaveConfig()' close-fn='rctrl.closeEditingFile(reload)'>
|
||||
</wz-xml-file-editor>
|
||||
</div>
|
||||
<!-- End rest of rule information -->
|
||||
|
||||
|
||||
<!-- 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>
|
||||
<!-- XML editor for rules -->
|
||||
</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-list.html
|
||||
include ./rules-detail.html
|
||||
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 ./rules/rules.pug
|
||||
include ./decoders/decoders.pug
|
||||
include ./cdblists/cdblists.pug
|
||||
include ./files/files.pug
|
||||
include ./ruleset.html
|
||||
include ./logtest.html
|
||||
include ./ruleset.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'"
|
||||
class="">
|
||||
class="sideBarContent">
|
||||
|
||||
<div class="md-padding md-padding-top-16" ng-show="ctrl.load">
|
||||
<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="row" layout-padding>
|
||||
<react-component flex name="WelcomeScreenManagement" props="mctrl.welcomeCardsProps" />
|
||||
<react-component flex name="WelcomeWrapper" props="mctrl.welcomeCardsProps" />
|
||||
</div>
|
||||
</div>
|
Loading…
Reference in New Issue
Block a user