crono_trigger 0.3.2 → 0.3.4
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.travis.yml +1 -2
- data/README.md +40 -0
- data/Rakefile +17 -0
- data/crono_trigger.gemspec +4 -1
- data/exe/crono_trigger-web +33 -0
- data/lib/crono_trigger.rb +20 -2
- data/lib/crono_trigger/cli.rb +8 -3
- data/lib/crono_trigger/models/signal.rb +52 -0
- data/lib/crono_trigger/models/worker.rb +16 -0
- data/lib/crono_trigger/polling_thread.rb +58 -20
- data/lib/crono_trigger/railtie.rb +15 -0
- data/lib/crono_trigger/schedulable.rb +69 -17
- data/lib/crono_trigger/version.rb +1 -1
- data/lib/crono_trigger/web.rb +163 -0
- data/lib/crono_trigger/worker.rb +118 -8
- data/lib/generators/crono_trigger/install/install_generator.rb +16 -0
- data/lib/generators/crono_trigger/install/templates/install.rb +23 -0
- data/lib/generators/crono_trigger/migration/templates/create_table_migration.rb +1 -0
- data/lib/generators/crono_trigger/migration/templates/migration.rb +1 -0
- data/web/app/.gitignore +21 -0
- data/web/app/README.md +2448 -0
- data/web/app/images.d.ts +3 -0
- data/web/app/package-lock.json +12439 -0
- data/web/app/package.json +36 -0
- data/web/app/public/favicon.ico +0 -0
- data/web/app/public/index.html +45 -0
- data/web/app/public/manifest.json +8 -0
- data/web/app/src/App.css +5 -0
- data/web/app/src/App.test.tsx +9 -0
- data/web/app/src/App.tsx +91 -0
- data/web/app/src/Models.tsx +61 -0
- data/web/app/src/SchedulableRecord.tsx +208 -0
- data/web/app/src/SchedulableRecordTableCell.tsx +19 -0
- data/web/app/src/SchedulableRecords.tsx +110 -0
- data/web/app/src/Signal.tsx +21 -0
- data/web/app/src/Signals.tsx +74 -0
- data/web/app/src/Worker.tsx +106 -0
- data/web/app/src/Workers.tsx +78 -0
- data/web/app/src/index.css +5 -0
- data/web/app/src/index.tsx +15 -0
- data/web/app/src/interfaces.ts +77 -0
- data/web/app/tsconfig.json +30 -0
- data/web/app/tsconfig.prod.json +3 -0
- data/web/app/tsconfig.test.json +6 -0
- data/web/app/tslint.json +13 -0
- data/web/public/asset-manifest.json +6 -0
- data/web/public/favicon.ico +0 -0
- data/web/public/manifest.json +8 -0
- data/web/public/service-worker.js +1 -0
- data/web/public/static/css/main.0f826673.css +2 -0
- data/web/public/static/css/main.0f826673.css.map +1 -0
- data/web/public/static/js/main.1413dc51.js +2 -0
- data/web/public/static/js/main.1413dc51.js.map +1 -0
- data/web/views/index.erb +1 -0
- data/web/views/signals.erb +9 -0
- data/web/views/workers.erb +9 -0
- metadata +89 -3
@@ -0,0 +1,36 @@
|
|
1
|
+
{
|
2
|
+
"name": "app",
|
3
|
+
"version": "0.1.0",
|
4
|
+
"private": true,
|
5
|
+
"proxy": "http://localhost:9292",
|
6
|
+
"dependencies": {
|
7
|
+
"@material-ui/core": "^1.4.3",
|
8
|
+
"@material-ui/icons": "^2.0.1",
|
9
|
+
"classnames": "^2.2.6",
|
10
|
+
"date-fns": "^1.29.0",
|
11
|
+
"react": "^16.4.2",
|
12
|
+
"react-dom": "^16.4.2",
|
13
|
+
"react-router-dom": "^4.3.1",
|
14
|
+
"react-scripts-ts": "2.17.0",
|
15
|
+
"react-syntax-highlighter": "^8.0.1"
|
16
|
+
},
|
17
|
+
"scripts": {
|
18
|
+
"start": "react-scripts-ts start",
|
19
|
+
"build": "react-scripts-ts build",
|
20
|
+
"test": "react-scripts-ts test --env=jsdom",
|
21
|
+
"eject": "react-scripts-ts eject"
|
22
|
+
},
|
23
|
+
"devDependencies": {
|
24
|
+
"@types/classnames": "^2.2.6",
|
25
|
+
"@types/date-fns": "^2.6.0",
|
26
|
+
"@types/jest": "^23.3.1",
|
27
|
+
"@types/lodash": "^4.14.116",
|
28
|
+
"@types/node": "^10.5.7",
|
29
|
+
"@types/react": "^16.4.8",
|
30
|
+
"@types/react-dom": "^16.0.7",
|
31
|
+
"@types/react-router-dom": "^4.3.0",
|
32
|
+
"@types/react-syntax-highlighter": "0.0.6",
|
33
|
+
"lodash": "^4.17.10",
|
34
|
+
"typescript": "^3.0.1"
|
35
|
+
}
|
36
|
+
}
|
Binary file
|
@@ -0,0 +1,45 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="utf-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
6
|
+
<meta name="theme-color" content="#000000">
|
7
|
+
<!--
|
8
|
+
manifest.json provides metadata used when your web app is added to the
|
9
|
+
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
10
|
+
-->
|
11
|
+
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
12
|
+
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
13
|
+
<!--
|
14
|
+
Notice the use of %PUBLIC_URL% in the tags above.
|
15
|
+
It will be replaced with the URL of the `public` folder during the build.
|
16
|
+
Only files inside the `public` folder can be referenced from the HTML.
|
17
|
+
|
18
|
+
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
19
|
+
work correctly both with client-side routing and a non-root public URL.
|
20
|
+
Learn how to configure a non-root public URL by running `npm run build`.
|
21
|
+
-->
|
22
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
|
23
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
24
|
+
<script>
|
25
|
+
window.mountPath = "%PUBLIC_URL%";
|
26
|
+
</script>
|
27
|
+
<title>CronoTrigger</title>
|
28
|
+
</head>
|
29
|
+
<body>
|
30
|
+
<noscript>
|
31
|
+
You need to enable JavaScript to run this app.
|
32
|
+
</noscript>
|
33
|
+
<div id="root"></div>
|
34
|
+
<!--
|
35
|
+
This HTML file is a template.
|
36
|
+
If you open it directly in the browser, you will see an empty page.
|
37
|
+
|
38
|
+
You can add webfonts, meta tags, or analytics to this file.
|
39
|
+
The build step will place the bundled scripts into the <body> tag.
|
40
|
+
|
41
|
+
To begin the development, run `npm start` or `yarn start`.
|
42
|
+
To create a production bundle, use `npm run build` or `yarn build`.
|
43
|
+
-->
|
44
|
+
</body>
|
45
|
+
</html>
|
data/web/app/src/App.css
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
import * as React from 'react';
|
2
|
+
import * as ReactDOM from 'react-dom';
|
3
|
+
import App from './App';
|
4
|
+
|
5
|
+
it('renders without crashing', () => {
|
6
|
+
const div = document.createElement('div');
|
7
|
+
ReactDOM.render(<App />, div);
|
8
|
+
ReactDOM.unmountComponentAtNode(div);
|
9
|
+
});
|
data/web/app/src/App.tsx
ADDED
@@ -0,0 +1,91 @@
|
|
1
|
+
import AppBar from '@material-ui/core/AppBar';
|
2
|
+
import IconButton from '@material-ui/core/IconButton';
|
3
|
+
import Menu from '@material-ui/core/Menu';
|
4
|
+
import MenuItem from '@material-ui/core/MenuItem';
|
5
|
+
import Toolbar from '@material-ui/core/Toolbar';
|
6
|
+
import Typography from '@material-ui/core/Typography';
|
7
|
+
import MenuIcon from '@material-ui/icons/Menu';
|
8
|
+
import * as React from 'react';
|
9
|
+
import { Link, Route } from "react-router-dom";
|
10
|
+
import './App.css';
|
11
|
+
import Models from './Models';
|
12
|
+
import SchedulableRecords from './SchedulableRecords';
|
13
|
+
import Signals from './Signals';
|
14
|
+
import Workers from './Workers';
|
15
|
+
|
16
|
+
interface IAppState {
|
17
|
+
menuAnchorEl: HTMLElement | null
|
18
|
+
}
|
19
|
+
|
20
|
+
class App extends React.Component<any, IAppState> {
|
21
|
+
private workersTitleRender: () => JSX.Element;
|
22
|
+
private signalsTitleRender: () => JSX.Element;
|
23
|
+
private modelsTitleRender: () => JSX.Element;
|
24
|
+
private schedulableRecordsTitleRender: (props: any) => JSX.Element;
|
25
|
+
private schedulableRecordsRender: (props: any) => JSX.Element;
|
26
|
+
|
27
|
+
public constructor(props: any) {
|
28
|
+
super(props);
|
29
|
+
this.handleMenuButtonClick = this.handleMenuButtonClick.bind(this);
|
30
|
+
this.handleMenuClose = this.handleMenuClose.bind(this);
|
31
|
+
this.state = {menuAnchorEl: null};
|
32
|
+
this.workersTitleRender = () => (
|
33
|
+
<Typography variant="title" color="inherit">Workers</Typography>
|
34
|
+
)
|
35
|
+
this.signalsTitleRender = () => (
|
36
|
+
<Typography variant="title" color="inherit">Signals</Typography>
|
37
|
+
)
|
38
|
+
this.modelsTitleRender = () => (
|
39
|
+
<Typography variant="title" color="inherit">Models</Typography>
|
40
|
+
)
|
41
|
+
this.schedulableRecordsTitleRender = ({ match }) => (
|
42
|
+
<Typography variant="title" color="inherit">{match.params.name}</Typography>
|
43
|
+
)
|
44
|
+
this.schedulableRecordsRender = ({ match }) => (
|
45
|
+
<SchedulableRecords model_name={match.params.name} />
|
46
|
+
)
|
47
|
+
}
|
48
|
+
|
49
|
+
public handleMenuButtonClick(event: any) {
|
50
|
+
this.setState({menuAnchorEl: event.currentTarget});
|
51
|
+
}
|
52
|
+
|
53
|
+
public handleMenuClose() {
|
54
|
+
this.setState({menuAnchorEl: null});
|
55
|
+
}
|
56
|
+
|
57
|
+
public render() {
|
58
|
+
const { menuAnchorEl }= this.state;
|
59
|
+
|
60
|
+
return (
|
61
|
+
<div className="main">
|
62
|
+
<AppBar position="static">
|
63
|
+
<Toolbar>
|
64
|
+
<IconButton className="menu" color="inherit" aria-label="Menu" onClick={this.handleMenuButtonClick}>
|
65
|
+
<MenuIcon />
|
66
|
+
</IconButton>
|
67
|
+
<Menu id="nav-menu" anchorEl={menuAnchorEl} open={Boolean(menuAnchorEl)} onClose={this.handleMenuClose}>
|
68
|
+
<MenuItem><Link to="/workers" onClick={this.handleMenuClose}>Workers</Link></MenuItem>
|
69
|
+
<MenuItem><Link to="/signals" onClick={this.handleMenuClose}>Signals</Link></MenuItem>
|
70
|
+
<MenuItem><Link to="/models" onClick={this.handleMenuClose}>Models</Link></MenuItem>
|
71
|
+
</Menu>
|
72
|
+
|
73
|
+
<Route path="/workers" render={this.workersTitleRender} />
|
74
|
+
<Route path="/signals" render={this.signalsTitleRender} />
|
75
|
+
<Route path="/models/:name" render={this.schedulableRecordsTitleRender} />
|
76
|
+
<Route exact={true} path="/models" render={this.modelsTitleRender} />
|
77
|
+
</Toolbar>
|
78
|
+
</AppBar>
|
79
|
+
|
80
|
+
<div className="content" style={{"padding": "15px"}}>
|
81
|
+
<Route path="/workers" component={Workers} />
|
82
|
+
<Route path="/signals" component={Signals} />
|
83
|
+
<Route path="/models/:name" render={this.schedulableRecordsRender} />
|
84
|
+
<Route exact={true} path="/models" component={Models} />
|
85
|
+
</div>
|
86
|
+
</div>
|
87
|
+
);
|
88
|
+
}
|
89
|
+
}
|
90
|
+
|
91
|
+
export default App;
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import Paper from '@material-ui/core/Paper';
|
2
|
+
import Table from '@material-ui/core/Table';
|
3
|
+
import TableBody from '@material-ui/core/TableBody';
|
4
|
+
import TableCell from '@material-ui/core/TableCell';
|
5
|
+
import TableHead from '@material-ui/core/TableHead';
|
6
|
+
import TableRow from '@material-ui/core/TableRow';
|
7
|
+
import * as React from 'react';
|
8
|
+
import { Link } from "react-router-dom";
|
9
|
+
|
10
|
+
import { IGlobalWindow } from './interfaces';
|
11
|
+
|
12
|
+
declare var window: IGlobalWindow
|
13
|
+
|
14
|
+
interface IModelsState {
|
15
|
+
models: string[]
|
16
|
+
}
|
17
|
+
|
18
|
+
class Models extends React.Component<any, IModelsState> {
|
19
|
+
constructor(props: any) {
|
20
|
+
super(props)
|
21
|
+
this.state = {models: []}
|
22
|
+
}
|
23
|
+
|
24
|
+
public componentDidMount() {
|
25
|
+
this.fetchModels();
|
26
|
+
}
|
27
|
+
|
28
|
+
public fetchModels(): void {
|
29
|
+
const that = this;
|
30
|
+
fetch(`${window.mountPath}/models.json`)
|
31
|
+
.then((res) => res.json())
|
32
|
+
.then((data) => {
|
33
|
+
that.setState(data);
|
34
|
+
}).catch((err) => {
|
35
|
+
console.error(err);
|
36
|
+
});
|
37
|
+
}
|
38
|
+
|
39
|
+
public render() {
|
40
|
+
return (
|
41
|
+
<Paper className="models-container">
|
42
|
+
<Table className="models">
|
43
|
+
<TableHead>
|
44
|
+
<TableRow>
|
45
|
+
<TableCell>Model Name</TableCell>
|
46
|
+
</TableRow>
|
47
|
+
</TableHead>
|
48
|
+
<TableBody>
|
49
|
+
{this.state.models.map((model) => (
|
50
|
+
<TableRow key={model}>
|
51
|
+
<TableCell><Link to={`/models/${model}`}>{model}</Link></TableCell>
|
52
|
+
</TableRow>
|
53
|
+
))}
|
54
|
+
</TableBody>
|
55
|
+
</Table>
|
56
|
+
</Paper>
|
57
|
+
)
|
58
|
+
}
|
59
|
+
}
|
60
|
+
|
61
|
+
export default Models;
|
@@ -0,0 +1,208 @@
|
|
1
|
+
import Button from '@material-ui/core/Button';
|
2
|
+
import Chip from '@material-ui/core/Chip';
|
3
|
+
import Modal from '@material-ui/core/Modal';
|
4
|
+
import Paper from '@material-ui/core/Paper';
|
5
|
+
import Snackbar from '@material-ui/core/Snackbar';
|
6
|
+
import TableRow from '@material-ui/core/TableRow';
|
7
|
+
import Typography from '@material-ui/core/Typography';
|
8
|
+
import classNames from 'classnames';
|
9
|
+
import { format, parse } from 'date-fns';
|
10
|
+
import * as React from 'react';
|
11
|
+
import SyntaxHighligher from 'react-syntax-highlighter';
|
12
|
+
import { dark } from 'react-syntax-highlighter/styles/hljs';
|
13
|
+
|
14
|
+
import { IGlobalWindow, ISchedulableRecordProps } from './interfaces';
|
15
|
+
import SchedulableRecordTableCell from './SchedulableRecordTableCell';
|
16
|
+
|
17
|
+
declare var window: IGlobalWindow
|
18
|
+
|
19
|
+
class SchedulableRecord extends React.Component<ISchedulableRecordProps, any> {
|
20
|
+
private statusChipColors: object = {
|
21
|
+
locked: "secondary",
|
22
|
+
not_scheduled: "default",
|
23
|
+
waiting: "primary",
|
24
|
+
};
|
25
|
+
|
26
|
+
constructor(props: ISchedulableRecordProps) {
|
27
|
+
super(props)
|
28
|
+
|
29
|
+
this.state = {
|
30
|
+
detailModalOpen: false,
|
31
|
+
notificationMessage: <span />,
|
32
|
+
notificationOpen: false,
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
public render() {
|
37
|
+
const record = this.props.record;
|
38
|
+
const rowClassNames = classNames({
|
39
|
+
"late": this.isLate(),
|
40
|
+
"too-late": this.isTooLate(),
|
41
|
+
});
|
42
|
+
|
43
|
+
return (
|
44
|
+
<TableRow key={record.id} className={rowClassNames} style={this.rowStyle()}>
|
45
|
+
<SchedulableRecordTableCell><Chip label={record.crono_trigger_status} color={this.statusChipColors[record.crono_trigger_status]}/></SchedulableRecordTableCell>
|
46
|
+
<SchedulableRecordTableCell>{record.id}</SchedulableRecordTableCell>
|
47
|
+
<SchedulableRecordTableCell>{record.cron}</SchedulableRecordTableCell>
|
48
|
+
<SchedulableRecordTableCell>{this.formatTime(record.next_execute_at)}</SchedulableRecordTableCell>
|
49
|
+
<SchedulableRecordTableCell>{record.delay_sec}</SchedulableRecordTableCell>
|
50
|
+
<SchedulableRecordTableCell>{record.execute_lock}</SchedulableRecordTableCell>
|
51
|
+
<SchedulableRecordTableCell>{record.time_to_unlock}</SchedulableRecordTableCell>
|
52
|
+
<SchedulableRecordTableCell>{this.formatTime(record.last_executed_at)}</SchedulableRecordTableCell>
|
53
|
+
<SchedulableRecordTableCell>{record.locked_by}</SchedulableRecordTableCell>
|
54
|
+
<SchedulableRecordTableCell>{this.formatTime(record.last_error_time)}</SchedulableRecordTableCell>
|
55
|
+
<SchedulableRecordTableCell>{record.retry_count}</SchedulableRecordTableCell>
|
56
|
+
<SchedulableRecordTableCell>
|
57
|
+
<Button variant="contained" style={{"marginRight": "8px"}} onClick={this.handleDetailClick}>Detail</Button>
|
58
|
+
|
59
|
+
<Modal
|
60
|
+
aria-labelledby={`schedulable-record-modal-title-${record.id}`}
|
61
|
+
open={this.state.detailModalOpen}
|
62
|
+
onClose={this.handleDetailModalClose}
|
63
|
+
style={{display: "flex", alignItems: "center", justifyContent: "center"}}
|
64
|
+
>
|
65
|
+
<Paper className="schedulable-record-modal" style={{width: "600px", padding: "8px"}}>
|
66
|
+
<Typography variant="title" id={`schedulable-record-modal-title-${record.id}`}>
|
67
|
+
{this.props.model_name}: {record.id}
|
68
|
+
</Typography>
|
69
|
+
<SyntaxHighligher language="json" style={dark}>
|
70
|
+
{JSON.stringify(record, null, " ")}
|
71
|
+
</SyntaxHighligher>
|
72
|
+
</Paper>
|
73
|
+
</Modal>
|
74
|
+
</SchedulableRecordTableCell>
|
75
|
+
<SchedulableRecordTableCell>
|
76
|
+
<Button variant="contained" style={{"marginRight": "8px"}} onClick={this.handleUnlockClick}>Unlock</Button>
|
77
|
+
<Button variant="contained" style={{"marginRight": "8px"}} onClick={this.handleRetryClick}>Retry</Button>
|
78
|
+
<Button variant="contained" color="secondary" onClick={this.handleResetClick}>Reset</Button>
|
79
|
+
|
80
|
+
<Snackbar
|
81
|
+
anchorOrigin={{vertical: "bottom", horizontal: "right"}}
|
82
|
+
open={this.state.notificationOpen}
|
83
|
+
autoHideDuration={3000}
|
84
|
+
onClose={this.handleNotificationClose}
|
85
|
+
message={this.state.notificationMessage}
|
86
|
+
/>
|
87
|
+
</SchedulableRecordTableCell>
|
88
|
+
</TableRow>
|
89
|
+
)
|
90
|
+
}
|
91
|
+
|
92
|
+
private formatTime(iso8601: string | null): string {
|
93
|
+
if (iso8601 === null) {
|
94
|
+
return "";
|
95
|
+
}
|
96
|
+
const date = parse(iso8601);
|
97
|
+
return format(date, "YYYY/MM/DD (ddd) HH:mm:ss Z");
|
98
|
+
}
|
99
|
+
|
100
|
+
private isLate(): boolean {
|
101
|
+
const record = this.props.record;
|
102
|
+
return record.delay_sec > 60 && record.delay_sec <= 180;
|
103
|
+
}
|
104
|
+
|
105
|
+
private isTooLate(): boolean {
|
106
|
+
const record = this.props.record;
|
107
|
+
return record.delay_sec > 180;
|
108
|
+
}
|
109
|
+
|
110
|
+
private rowStyle(): object {
|
111
|
+
if (this.isLate()) {
|
112
|
+
return {backgroundColor: "#FFEA00"}
|
113
|
+
} else if (this.isTooLate()) {
|
114
|
+
return {backgroundColor: "#C62828"}
|
115
|
+
} else {
|
116
|
+
return {}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
|
120
|
+
private handleUnlockClick = (event: any) => {
|
121
|
+
const record = this.props.record;
|
122
|
+
fetch(`${window.mountPath}/models/${this.props.model_name}/${record.id}/unlock`, {
|
123
|
+
headers: {"content-type": "application/json"},
|
124
|
+
method: "POST"
|
125
|
+
}).then(this.handleResponseStatus).then((res) => {
|
126
|
+
this.setState({
|
127
|
+
...this.state,
|
128
|
+
notificationMessage: <span>Unlock id:{record.id}</span>,
|
129
|
+
notificationOpen: true,
|
130
|
+
})
|
131
|
+
}).catch((err) => {
|
132
|
+
this.setState({
|
133
|
+
...this.state,
|
134
|
+
notificationMessage: <span>Failed to unlock ({err.message})</span>,
|
135
|
+
notificationOpen: true,
|
136
|
+
})
|
137
|
+
})
|
138
|
+
}
|
139
|
+
|
140
|
+
private handleRetryClick = (event: any) => {
|
141
|
+
const record = this.props.record;
|
142
|
+
fetch(`${window.mountPath}/models/${this.props.model_name}/${record.id}/retry`, {
|
143
|
+
headers: {"content-type": "application/json"},
|
144
|
+
method: "POST"
|
145
|
+
}).then(this.handleResponseStatus).then((res) => {
|
146
|
+
this.setState({
|
147
|
+
notificationMessage: <span>Retry id:{record.id}</span>,
|
148
|
+
notificationOpen: true,
|
149
|
+
})
|
150
|
+
}).catch((err) => {
|
151
|
+
this.setState({
|
152
|
+
notificationMessage: <span>Failed to retry ({err.message})</span>,
|
153
|
+
notificationOpen: true,
|
154
|
+
})
|
155
|
+
})
|
156
|
+
}
|
157
|
+
|
158
|
+
private handleResetClick = (event: any) => {
|
159
|
+
const record = this.props.record;
|
160
|
+
fetch(`${window.mountPath}/models/${this.props.model_name}/${record.id}/reset`, {
|
161
|
+
headers: {"content-type": "application/json"},
|
162
|
+
method: "POST"
|
163
|
+
}).then(this.handleResponseStatus).then((res) => {
|
164
|
+
this.setState({
|
165
|
+
notificationMessage: <span>Reset id:{record.id}</span>,
|
166
|
+
notificationOpen: true,
|
167
|
+
})
|
168
|
+
}).catch((err) => {
|
169
|
+
this.setState({
|
170
|
+
notificationMessage: <span>Failed to reset ({err.message})</span>,
|
171
|
+
notificationOpen: true,
|
172
|
+
})
|
173
|
+
})
|
174
|
+
}
|
175
|
+
|
176
|
+
private handleResponseStatus = (res: Response) => {
|
177
|
+
if (!res.ok) {
|
178
|
+
return res.json().then((data: any) => {
|
179
|
+
throw new Error(data.error);
|
180
|
+
})
|
181
|
+
} else {
|
182
|
+
return Promise.resolve(res);
|
183
|
+
}
|
184
|
+
}
|
185
|
+
|
186
|
+
private handleNotificationClose = (ev: any, reason: any) => {
|
187
|
+
this.setState({
|
188
|
+
...this.state,
|
189
|
+
notificationOpen: false,
|
190
|
+
})
|
191
|
+
}
|
192
|
+
|
193
|
+
private handleDetailClick = (ev: any) => {
|
194
|
+
this.setState({
|
195
|
+
...this.state,
|
196
|
+
detailModalOpen: true,
|
197
|
+
})
|
198
|
+
}
|
199
|
+
|
200
|
+
private handleDetailModalClose = (ev: any) => {
|
201
|
+
this.setState({
|
202
|
+
...this.state,
|
203
|
+
detailModalOpen: false,
|
204
|
+
})
|
205
|
+
}
|
206
|
+
}
|
207
|
+
|
208
|
+
export default SchedulableRecord;
|