Show next run time for programs
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
89562b11b0
commit
f6d6ef7c0c
@ -105,16 +105,17 @@ class ProgramSequenceItem extends React.Component<{
|
|||||||
|
|
||||||
const ProgramSequenceItemD = SortableElement(ProgramSequenceItem);
|
const ProgramSequenceItemD = SortableElement(ProgramSequenceItem);
|
||||||
|
|
||||||
|
// tslint:disable: no-shadowed-variable
|
||||||
const ProgramSequenceList = SortableContainer(
|
const ProgramSequenceList = SortableContainer(
|
||||||
observer(
|
observer(
|
||||||
(props: {
|
function ProgramSequenceList(props: {
|
||||||
className: string;
|
className: string;
|
||||||
list: ProgramItem[];
|
list: ProgramItem[];
|
||||||
sections: Section[];
|
sections: Section[];
|
||||||
editing: boolean;
|
editing: boolean;
|
||||||
onChange: ItemChangeHandler;
|
onChange: ItemChangeHandler;
|
||||||
onRemove: ItemRemoveHandler;
|
onRemove: ItemRemoveHandler;
|
||||||
}) => {
|
}) {
|
||||||
const { className, list, sections, ...rest } = props;
|
const { className, list, sections, ...rest } = props;
|
||||||
const listItems = list.map((item, index) => {
|
const listItems = list.map((item, index) => {
|
||||||
const key = `item-${index}`;
|
const key = `item-${index}`;
|
||||||
@ -132,7 +133,6 @@ const ProgramSequenceList = SortableContainer(
|
|||||||
return <ul className={className}>{listItems}</ul>;
|
return <ul className={className}>{listItems}</ul>;
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
{ withRef: true }
|
|
||||||
);
|
);
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
@ -8,6 +8,7 @@ import { ProgramSequenceView, ScheduleView } from "@client/components";
|
|||||||
import * as route from "@client/routePaths";
|
import * as route from "@client/routePaths";
|
||||||
import { ISprinklersDevice } from "@common/httpApi";
|
import { ISprinklersDevice } from "@common/httpApi";
|
||||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||||
|
import moment = require("moment");
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
class ProgramRows extends React.Component<{
|
class ProgramRows extends React.Component<{
|
||||||
@ -69,6 +70,12 @@ class ProgramRows extends React.Component<{
|
|||||||
<h4>Sequence: </h4>{" "}
|
<h4>Sequence: </h4>{" "}
|
||||||
<ProgramSequenceView sequence={sequence} sections={sections} />
|
<ProgramSequenceView sequence={sequence} sections={sections} />
|
||||||
<ScheduleView schedule={schedule} label={<h4>Schedule: </h4>} />
|
<ScheduleView schedule={schedule} label={<h4>Schedule: </h4>} />
|
||||||
|
<h4 className="program--nextRun">Next run: </h4>
|
||||||
|
{
|
||||||
|
program.nextRun
|
||||||
|
? <time title={moment(program.nextRun).toString()}>{moment(program.nextRun).fromNow()}</time>
|
||||||
|
: <time title="never">never</time>
|
||||||
|
}
|
||||||
</Form>
|
</Form>
|
||||||
</Table.Cell>
|
</Table.Cell>
|
||||||
</Table.Row>
|
</Table.Row>
|
||||||
|
@ -20,8 +20,9 @@ import { AppState, injectState } from "@client/state";
|
|||||||
import { ISprinklersDevice } from "@common/httpApi";
|
import { ISprinklersDevice } from "@common/httpApi";
|
||||||
import log from "@common/logger";
|
import log from "@common/logger";
|
||||||
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
import { Program, SprinklersDevice } from "@common/sprinklersRpc";
|
||||||
import { action } from "mobx";
|
|
||||||
import classNames = require("classnames");
|
import classNames = require("classnames");
|
||||||
|
import { action } from "mobx";
|
||||||
|
import * as moment from "moment";
|
||||||
|
|
||||||
interface ProgramPageProps
|
interface ProgramPageProps
|
||||||
extends RouteComponentProps<{ deviceId: string; programId: string }> {
|
extends RouteComponentProps<{ deviceId: string; programId: string }> {
|
||||||
@ -186,7 +187,6 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
|||||||
toggle
|
toggle
|
||||||
label="Enabled"
|
label="Enabled"
|
||||||
checked={enabled}
|
checked={enabled}
|
||||||
readOnly={!editing}
|
|
||||||
onChange={this.onEnabledChange}
|
onChange={this.onEnabledChange}
|
||||||
/>
|
/>
|
||||||
<Form.Checkbox
|
<Form.Checkbox
|
||||||
@ -211,6 +211,16 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
|||||||
editing={editing}
|
editing={editing}
|
||||||
label={<h4>Schedule</h4>}
|
label={<h4>Schedule</h4>}
|
||||||
/>
|
/>
|
||||||
|
{ !editing && (
|
||||||
|
<h4 className="program--nextRun">Next run: </h4>)
|
||||||
|
}
|
||||||
|
{
|
||||||
|
!editing && (
|
||||||
|
program.nextRun
|
||||||
|
? <time title={moment(program.nextRun).toString()}>{moment(program.nextRun).fromNow()}</time>
|
||||||
|
: <time title="never">never</time>
|
||||||
|
)
|
||||||
|
}
|
||||||
</Form>
|
</Form>
|
||||||
</Modal.Content>
|
</Modal.Content>
|
||||||
{this.renderActions(program)}
|
{this.renderActions(program)}
|
||||||
@ -243,6 +253,10 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
|||||||
},
|
},
|
||||||
err => {
|
err => {
|
||||||
log.error({ err }, "error updating Program");
|
log.error({ err }, "error updating Program");
|
||||||
|
this.props.appState.uiStore.addMessage({
|
||||||
|
error: true,
|
||||||
|
content: `Error updating program: ${err}`,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
this.stopEditing();
|
this.stopEditing();
|
||||||
@ -268,11 +282,28 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
|||||||
|
|
||||||
@action.bound
|
@action.bound
|
||||||
private onEnabledChange(e: any, p: CheckboxProps) {
|
private onEnabledChange(e: any, p: CheckboxProps) {
|
||||||
if (this.programView) {
|
if (p.checked !== undefined && this.program) {
|
||||||
this.programView.enabled = p.checked!;
|
this.program.enabled = p.checked;
|
||||||
|
this.program.update().then(
|
||||||
|
data => {
|
||||||
|
log.info({ data }, "Program updated");
|
||||||
|
this.props.appState.uiStore.addMessage({
|
||||||
|
success: true,
|
||||||
|
content: `Program ${this.program!.name} ${this.program!.enabled ? "enabled" : "disabled"}`,
|
||||||
|
timeout: 2000,
|
||||||
|
});
|
||||||
|
},
|
||||||
|
err => {
|
||||||
|
log.error({ err }, "error updating Program");
|
||||||
|
this.props.appState.uiStore.addMessage({
|
||||||
|
error: true,
|
||||||
|
content: `Error updating program: ${err}`,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const DecoratedProgramPage = injectState(withRouter(observer(ProgramPage)));
|
const DecoratedProgramPage = injectState(withRouter(ProgramPage));
|
||||||
export default DecoratedProgramPage;
|
export default DecoratedProgramPage;
|
||||||
|
@ -78,6 +78,11 @@ $connected-color: #13d213;
|
|||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.program--nextRun {
|
||||||
|
display: inline-block;
|
||||||
|
padding-right: 0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
.ui.modal.programEditor {
|
.ui.modal.programEditor {
|
||||||
&.editing > .content {
|
&.editing > .content {
|
||||||
min-height: 80vh;
|
min-height: 80vh;
|
||||||
|
@ -32,6 +32,8 @@ export class Program {
|
|||||||
sequence: ProgramItem[] = [];
|
sequence: ProgramItem[] = [];
|
||||||
@observable
|
@observable
|
||||||
running: boolean = false;
|
running: boolean = false;
|
||||||
|
@observable
|
||||||
|
nextRun: Date | null = null;
|
||||||
|
|
||||||
constructor(device: SprinklersDevice, id: number, data?: Partial<Program>) {
|
constructor(device: SprinklersDevice, id: number, data?: Partial<Program>) {
|
||||||
this.device = device;
|
this.device = device;
|
||||||
@ -60,7 +62,8 @@ export class Program {
|
|||||||
enabled: this.enabled,
|
enabled: this.enabled,
|
||||||
running: this.running,
|
running: this.running,
|
||||||
schedule: this.schedule.clone(),
|
schedule: this.schedule.clone(),
|
||||||
sequence: this.sequence.slice()
|
sequence: this.sequence.slice(),
|
||||||
|
nextRun: this.nextRun,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -68,7 +71,7 @@ export class Program {
|
|||||||
return (
|
return (
|
||||||
`Program{name="${this.name}", enabled=${this.enabled}, schedule=${
|
`Program{name="${this.name}", enabled=${this.enabled}, schedule=${
|
||||||
this.schedule
|
this.schedule
|
||||||
}, ` + `sequence=${this.sequence}, running=${this.running}}`
|
}, ` + `sequence=${this.sequence}, running=${this.running}, nextRun=${this.nextRun}}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,8 @@ export class MqttProgram extends s.Program {
|
|||||||
onMessage(payload: string, topic: string | undefined) {
|
onMessage(payload: string, topic: string | undefined) {
|
||||||
if (topic === "running") {
|
if (topic === "running") {
|
||||||
this.running = payload === "true";
|
this.running = payload === "true";
|
||||||
|
} else if (topic === "nextRun") {
|
||||||
|
this.nextRun = (payload.length > 0) ? new Date(Number(payload) * 1000.0) : null;
|
||||||
} else if (topic == null) {
|
} else if (topic == null) {
|
||||||
this.updateFromJSON(JSON.parse(payload));
|
this.updateFromJSON(JSON.parse(payload));
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { createSimpleSchema, ModelSchema, object, primitive } from "serializr";
|
import { createSimpleSchema, ModelSchema, object, primitive, date } from "serializr";
|
||||||
import * as s from "..";
|
import * as s from "..";
|
||||||
import list from "./list";
|
import list from "./list";
|
||||||
|
|
||||||
@ -85,7 +85,8 @@ export const program: ModelSchema<s.Program> = {
|
|||||||
enabled: primitive(),
|
enabled: primitive(),
|
||||||
schedule: object(schedule),
|
schedule: object(schedule),
|
||||||
sequence: list(object(programItem)),
|
sequence: list(object(programItem)),
|
||||||
running: primitive()
|
running: primitive(),
|
||||||
|
nextRun: date(),
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user