Skip to content

Commit

Permalink
Implementation of an alerting UI for the configuration server (#840)
Browse files Browse the repository at this point in the history
* Closes #811 - Created UI for alert rule configuration

* Closes #811 - fixed lint errors

* Closes #811 - fixed bug in default prop values

* Closes #811 - created grouping options

* reverted yarn lock upgrade

* refactoring of the alerting tree

* refactored alerting rules toolbars

* Rule view refactoring

* resolved proptypes warning and prevented flickering

* added status to rule view

* Fixed formatting and lint errors

Co-authored-by: Marius Oehler <[email protected]>
  • Loading branch information
AlexanderWert and mariusoe authored Jul 20, 2020
1 parent 22185b4 commit f40e9fa
Show file tree
Hide file tree
Showing 62 changed files with 3,911 additions and 146 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"jwt-decode": "^2.2.0",
"lodash": "^4.17.14",
"next": "^9.3.2",
"primeflex": "^1.0.0-rc.1",
"primeflex": "^1.3.1",
"primeicons": "^1.0.0",
"primereact": "^4.2.1",
"prop-types": "^15.7.2",
Expand All @@ -55,9 +55,9 @@
"eslint": "^6.8.0",
"eslint-config-prettier": "^6.10.1",
"eslint-import-resolver-webpack": "^0.12.1",
"eslint-plugin-import": "^2.20.2",
"eslint-plugin-import": "^2.22.0",
"eslint-plugin-prettier": "^3.1.2",
"eslint-plugin-react": "^7.19.0",
"eslint-plugin-react": "^7.20.1",
"file-loader": "^4.0.0",
"prettier": "^2.0.4",
"url-loader": "^2.0.1"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import React, { useEffect } from 'react';
import PropTypes from 'prop-types';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';

/**
* A generic Dialog for deleting the given element.
*/
const DeleteDialog = ({ name, visible, onHide, text, onSuccess }) => {
const deleteButton = React.createRef();
useEffect(() => {
if (deleteButton && deleteButton.current) {
deleteButton.current.element.focus();
}
});
return (
<Dialog
header={text}
modal={true}
visible={visible}
onHide={onHide}
footer={
<div>
<Button
label="Delete"
ref={deleteButton}
className="p-button-danger"
onClick={() => {
onSuccess(name);
onHide();
}}
/>
<Button label="Cancel" className="p-button-secondary" onClick={onHide} />
</div>
}
>
Are you sure you want to delete <b>&quot;{name}&quot;</b> ? This cannot be undone!
</Dialog>
);
};

DeleteDialog.propTypes = {
/** The name of the element to delete */
name: PropTypes.string,
/** The text to show in the dialog */
text: PropTypes.string,
/** Whether the dialog is visible */
visible: PropTypes.bool,
/** Callback on deletion success */
onSuccess: PropTypes.func,
/** Callback on dialog hide */
onHide: PropTypes.func,
};

DeleteDialog.defaultProps = {
text: 'Delete element',
visible: true,
onSuccess: () => {},
onHide: () => {},
};

export default DeleteDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';
import { InputText } from 'primereact/inputtext';
import { Message } from 'primereact/message';

/**
* A generic Dialog for renaming or copying the given element.
*/
const RenameCopyDialog = ({ name, reservedNames, retrieveReservedNames, visible, onHide, text, onSuccess, intention }) => {
const [newName, setName] = useState('');
const [isValid, setValidState] = useState(false);
const [error, setError] = useState(undefined);
const [invalidNames, setInvalidNames] = useState([]);

useEffect(() => {
if (reservedNames) {
setInvalidNames(reservedNames);
} else if (retrieveReservedNames) {
retrieveReservedNames((names) => setInvalidNames(names));
}
}, [reservedNames, retrieveReservedNames]);

useEffect(() => {
const reservedName = invalidNames.some((n) => n === newName);
if (reservedName) {
setError('An element with the given name already exists!');
} else {
setError(undefined);
}
setValidState(!reservedName && !!name && name !== newName);
}, [newName]);

return (
<Dialog
className="this"
style={{ width: '400px' }}
header={(intention === 'copy' ? 'Copy' : 'Rename') + ' Element'}
modal={true}
visible={visible}
onHide={onHide}
onShow={() => {
setName(name);
setValidState(false);
setError(undefined);
}}
footer={
<div>
<Button label={intention === 'copy' ? 'Copy' : 'Rename'} disabled={!isValid} onClick={() => onSuccess(name, newName)} />
<Button label="Cancel" className="p-button-secondary" onClick={onHide} />
</div>
}
>
<div style={{ width: '100%', paddingBottom: '0.5em' }}>{text}</div>
<div className="p-grid">
<div className="p-inputgroup p-col-12" style={{ width: '100%' }}>
<span className="p-inputgroup-addon">
<i className="pi pi-pencil"></i>
</span>
<InputText
style={{ width: '100%' }}
onKeyPress={(e) => e.key === 'Enter' && isValid && onSuccess(name, newName)}
value={newName}
autoFocus
placeholder="New Name"
onChange={(e) => {
setName(e.target.value);
}}
/>
</div>
</div>
{error && (
<div style={{ width: '100%', paddingTop: '0.5em' }}>
<Message style={{ width: '100%' }} severity="error" text={error}></Message>
</div>
)}
</Dialog>
);
};

RenameCopyDialog.propTypes = {
/** The name of the element to delete */
name: PropTypes.string,
/** A list of reserved name (optional) */
reservedNames: PropTypes.array,
/** A function returning a list of reserved names as alternative to passing the reserved names. */
retrieveReservedNames: PropTypes.func,
/** The text to show in the dialog */
text: PropTypes.string,
/** Whether the dialog is visible */
visible: PropTypes.bool,
/** The intention of the dialog (copy or rename) */
intention: PropTypes.string,
/** Callback on deletion success */
onSuccess: PropTypes.func,
/** Callback on dialog hide */
onHide: PropTypes.func,
};

RenameCopyDialog.defaultProps = {
text: 'Delete element',
visible: true,
onSuccess: () => {},
onHide: () => {},
};

export default RenameCopyDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React, { useState } from 'react';
import PropTypes from 'prop-types';
import { InputSwitch } from 'primereact/inputswitch';

/**
* Editor for Bool type values.
*/
const BoolEditor = ({ value, disabled, updateValue }) => {
const [val, setValue] = useState(value);

return (
<InputSwitch
disabled={disabled}
checked={val}
onChange={(e) => {
setValue(e.target.value);
updateValue(e.target.value);
}}
/>
);
};

BoolEditor.propTypes = {
/** The value to edit */
value: PropTypes.bool.isRequired,
/** Whether the editor is disabled or not */
disabled: PropTypes.bool,
/** Callback on value change */
updateValue: PropTypes.func,
};

BoolEditor.defaultProps = {
disabled: false,
updateValue: () => {},
};

export default BoolEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { InputNumber } from 'primereact/inputnumber';

/**
* Editor for number type values.
*/
const NumberEditor = ({ value, type, disabled, updateValue }) => {
const [val, setValue] = useState(value);

const ref = React.createRef();
useEffect(() => {
ref.current.inputEl.focus();
});

const minFractionDigits = type === 'int' ? 0 : 1;
const maxFractionDigits = type === 'int' ? 0 : 10;
return (
<InputNumber
ref={ref}
disabled={disabled}
style={{ width: '100%' }}
value={val}
onChange={(e) => setValue(e.value)}
mode="decimal"
autoFocus
minFractionDigits={minFractionDigits}
maxFractionDigits={maxFractionDigits}
useGrouping={false}
onKeyPress={(e) => e.key === 'Enter' && updateValue(val)}
onBlur={() => updateValue(val)}
/>
);
};

NumberEditor.propTypes = {
/** The value to edit */
value: PropTypes.number.isRequired,
/** The type of the value */
type: PropTypes.string,
/** Whether the editor is disabled or not */
disabled: PropTypes.bool,
/** Callback on value change */
updateValue: PropTypes.func,
};

NumberEditor.defaultProps = {
disabled: false,
type: 'float',
updateValue: () => {},
};

export default NumberEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
import React, { useState, useEffect } from 'react';
import PropTypes from 'prop-types';
import { Dropdown } from 'primereact/dropdown';

/**
* Editor for String type values with predefined selection list.
*/
const SelectionEditor = ({ value, options, editable, disabled, updateValue, keyfilter }) => {
const [val, setValue] = useState(value);
const wrapperRef = React.createRef();
const selectItems = !options ? [] : options.map((item) => ({ label: item, value: item }));

// we need this, as onBlur is not working properly with the dropdown box
// (onBlur is triggered when drop down list is opened).
const handleClickOutside = (event) => {
if (wrapperRef && !wrapperRef.current.contains(event.target)) {
updateValue(val);
}
};

useEffect(() => {
document.addEventListener('mousedown', handleClickOutside);
return () => {
document.removeEventListener('mousedown', handleClickOutside);
};
});

return (
<div className="this" ref={wrapperRef}>
<style jsx>{`
.this {
flex-grow: 0;
width: fit-content;
}
.this :global(.p-dropdown) {
min-width: 250px;
}
`}</style>
<Dropdown
disabled={disabled}
value={val}
editable={editable}
options={selectItems}
autoFocus
filter
keyfilter={keyfilter}
onChange={(e) => setValue(e.value)}
onKeyPress={(e) => e.key === 'Enter' && updateValue(val)}
/>
</div>
);
};

SelectionEditor.propTypes = {
/** The value to edit */
value: PropTypes.string.isRequired,
/** List of strings as options for the dropdown */
options: PropTypes.array,
/** Whether the editor is editable or not */
editable: PropTypes.bool,
/** Whether the editor is disabled or not */
disabled: PropTypes.bool,
/** The keyfilter limiting the allowed keys*/
keyfilter: PropTypes.any,
/** Callback on value change */
updateValue: PropTypes.func,
};

SelectionEditor.defaultProps = {
disabled: false,
editable: true,
options: [],
keyfilter: undefined,
updateValue: () => {},
};

export default SelectionEditor;
Loading

0 comments on commit f40e9fa

Please sign in to comment.