Skip to content

Commit

Permalink
Closes inspectIT#811 - Created UI for alert rule configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
Alexander Wert committed Jul 7, 2020
1 parent 592354a commit cb43da4
Show file tree
Hide file tree
Showing 29 changed files with 4,286 additions and 3,061 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,111 @@
import React, { useState } from 'react';
import classNames from 'classnames';
import { Button } from 'primereact/button';
import { Dialog } from 'primereact/dialog';
import { Message } from 'primereact/message';
import { InputText } from 'primereact/inputtext';
import { Dropdown } from 'primereact/dropdown';
import { InputTextarea } from 'primereact/inputtextarea';

/**
* A generic dialog for creating new items.
* Optionally a category for the items can be used. The category options will be listed in a dropdown box.
*/
const CreateDialog = ({ categories, initialCategory, useDescription, title, categoryTitle, elementTitle, text,
visible, onHide, categoryIcon, targetElementIcon, reservedNames, onSuccess }) => {
const [name, setName] = useState('');
const [category, setCategory] = useState(undefined);
const [description, setDescription] = useState('');
const [isValid, setValidState] = useState(false);
const [error, setError] = useState(undefined);

if (!category && initialCategory) { setCategory(initialCategory); }

const validate = () => {
const reservedName = reservedNames.some(r => r.id === name);
if (reservedName) {
setError('An alerting rule with the given name already exists');
} else {
setError(undefined);
}
setValidState(!reservedName && !!name && !!category);
};

return (
<Dialog
className="this"
style={{ width: '400px' }}
header={title}
modal={true}
visible={visible}
onHide={onHide}
onShow={() => {
setName('');
setDescription('');
setCategory(undefined);
}}
footer={
<div>
<Button label="Create" disabled={!isValid} onClick={() => onSuccess(name, category, description)} />
<Button label="Cancel" className="p-button-secondary" onClick={onHide} />
</div>
}
>
<div style={{ width: '100%', paddingBottom: '0.5em' }}>{text}</div>
<div className="p-grid">
{
categories && (
<div className="p-inputgroup p-col-12" style={{ width: '100%' }}>
<span className="p-inputgroup-addon">
<i className={classNames('pi', { [categoryIcon]: categoryIcon !== undefined })}></i>
</span>
<Dropdown
style={{ width: '100%' }}
value={category}
options={categories.map(c => ({ label: c, value: c }))}
onChange={(e) => { setCategory(e.value); validate(); }}
placeholder={categoryTitle} />
</div>
)
}
<div className="p-inputgroup p-col-12" style={{ width: '100%' }}>
<span className="p-inputgroup-addon">
<i className={classNames('pi', { [targetElementIcon]: targetElementIcon !== undefined })}></i>
</span>
<InputText
style={{ width: '100%' }}
onKeyPress={(e) => (e.key === 'Enter' && isValid && onSuccess(name, category, description))}
value={name}
placeholder={elementTitle}
onChange={(e) => { setName(e.target.value); validate(); }}
/>
</div>
{
useDescription && (
<div className="p-inputgroup p-col-12" style={{ width: '100%' }}>
<span className="p-inputgroup-addon">
<i className="pi pi-align-left"></i>
</span>
<InputTextarea
style={{ width: '100%', height: '8rem' }}
value={description}
onKeyPress={(e) => (e.key === 'Enter' && isValid && onSuccess(name, category, description))}
autoResize={false}
placeholder={'Description'}
onChange={(e) => setDescription(e.target.value)} />
</div>
)
}
</div>
{
error && (
<div style={{ width: '100%', paddingTop: '0.5em' }}>
<Message style={{ width: '100%' }} severity="error" text={error}></Message>
</div>
)
}
</Dialog >
);
};

export default CreateDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useEffect } from 'react';
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>
);
};


export default DeleteDialog;
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import React, { useState } from 'react';
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);
}}
/>);
};

export default BoolEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React, { useState, useEffect } from 'react';
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)}

/>);
};

export default NumberEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import React, { useState, useEffect } from 'react';
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>
);
};

export default SelectionEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React, { useState } from 'react';
import { InputTextarea } from 'primereact/inputtextarea';

/**
* Editor for long texts.
*/
const TextAreaEditor = ({ value, type, disabled, updateValue, height}) => {
const [val, setValue] = useState(value);

const heightObj = height ? {height: height + 'px'} : {};
return (<InputTextarea
style={{...{width: '100%'},...heightObj}}
disabled={disabled}
value={val}
onChange={(e) => setValue(e.target.value)}
autoFocus
onBlur={() => updateValue(val ? val : null)}
/>);
};

export default TextAreaEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import React, { useState } from 'react';
import { InputText } from 'primereact/inputtext';

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

return (
<InputText
style={{ width: '100%' }}
disabled={disabled}
value={val}
onChange={(e) => setValue(e.target.value)}
autoFocus
keyfilter={keyfilter}
onKeyPress={(e) => (e.key === 'Enter') && updateValue(val)}
onBlur={() => updateValue(val)}
/>);
};

export default TextEditor;
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import React, { useState, useEffect} from 'react';
import { TabView, TabPanel } from 'primereact/tabview';
import AlertingRulesView from './rules/AlertingRulesView';
import * as topicsAPI from './topics/TopicsAPI';

/**
* Tab layout for the alerting view. This view provides a navigation between alerting rules and alerting channels.
*/
const AlertingView = () => {
const [activeIndex, setActiveIndex] = useState(0);
const [availableTopics, setAvailableTopics] = useState([]);

useEffect(() => {
topicsAPI.fetchTopics((topics)=>setAvailableTopics(topics));
});

return (
<div className="this">
<style jsx>{`
.this {
height: 100%;
display: flex;
}
.this :global(.p-tabview-panels) {
display: flex;
flex-grow: 1;
max-height: calc(100% - 35px);
padding: 0;
}
.this :global(.p-tabview-panel) {
flex-grow: 1;
display: flex;
}
.this :global(.tabView) {
display: flex;
flex-direction: column;
height: 100%;
width: 100%;
}
`}</style>
<TabView className="tabView" activeIndex={activeIndex} onTabChange={(e) => setActiveIndex(e.index)}>
<TabPanel header="Alerting Rules">
<AlertingRulesView availableTopics={availableTopics}/>
</TabPanel>
{/* Placeholder for the second part of the alerting UI
<TabPanel header="Alerting Topics">
</TabPanel> */}
</TabView>
</div>
);
};

export default AlertingView;
Loading

0 comments on commit cb43da4

Please sign in to comment.