Compare commits
No commits in common. "master" and "develop" have entirely different histories.
@ -6,90 +6,13 @@ import { Duration } from "@common/Duration";
|
|||||||
|
|
||||||
import "@client/styles/DurationView";
|
import "@client/styles/DurationView";
|
||||||
|
|
||||||
export interface DurationViewProps {
|
export default class DurationView extends React.Component<{
|
||||||
label?: string;
|
label?: string;
|
||||||
inline?: boolean;
|
inline?: boolean;
|
||||||
duration: Duration;
|
duration: Duration;
|
||||||
onDurationChange?: (newDuration: Duration) => void;
|
onDurationChange?: (newDuration: Duration) => void;
|
||||||
className?: string;
|
className?: string;
|
||||||
}
|
}> {
|
||||||
|
|
||||||
function roundOrString(val: number | string): number | string {
|
|
||||||
if (typeof val === "number") {
|
|
||||||
return Math.round(val);
|
|
||||||
} else {
|
|
||||||
return val;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
interface NumberInputProps {
|
|
||||||
className?: string;
|
|
||||||
label?: string;
|
|
||||||
value: number;
|
|
||||||
max?: number;
|
|
||||||
onChange: (value: number) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function NumberInput(props: NumberInputProps): React.ReactElement {
|
|
||||||
const [valueState, setValueState] = React.useState<number | string>(props.value);
|
|
||||||
const [elementId, setElementId] = React.useState(() => `NumberInput-${Math.round(Math.random() * 100000000)}`);
|
|
||||||
const [isWheelChange, setIsWheelChange] = React.useState(false);
|
|
||||||
|
|
||||||
const onChange: InputProps["onChange"] = (_e, data) => {
|
|
||||||
setValueState(data.value);
|
|
||||||
const newValue = parseFloat(data.value);
|
|
||||||
if (!isNaN(newValue) && data.value.length > 0 && isWheelChange) {
|
|
||||||
props.onChange(Math.round(newValue));
|
|
||||||
setIsWheelChange(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onBlur: React.FocusEventHandler = () => {
|
|
||||||
const newValue = (typeof valueState === "number") ? valueState : parseFloat(valueState);
|
|
||||||
if (!props.onChange || isNaN(newValue)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (props.value !== newValue) {
|
|
||||||
props.onChange(Math.round(newValue));
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const onWheel = (e: Event) => {
|
|
||||||
// do nothing
|
|
||||||
setIsWheelChange(true);
|
|
||||||
};
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
const el = document.getElementById(elementId);
|
|
||||||
if (el) {
|
|
||||||
// Not passive events
|
|
||||||
el.addEventListener("wheel", onWheel);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
React.useEffect(() => {
|
|
||||||
if (props.value !== valueState) {
|
|
||||||
setValueState(props.value);
|
|
||||||
}
|
|
||||||
}, [props.value]);
|
|
||||||
|
|
||||||
return <Input
|
|
||||||
id={elementId}
|
|
||||||
type="number"
|
|
||||||
pattern="[0-9\.]*" // for safari
|
|
||||||
inputMode="numeric"
|
|
||||||
value={roundOrString(valueState)}
|
|
||||||
onChange={onChange}
|
|
||||||
// onMouseOut={onBlur}
|
|
||||||
onBlur={onBlur}
|
|
||||||
className={props.className}
|
|
||||||
label={props.label}
|
|
||||||
max={props.max}
|
|
||||||
labelPosition="right"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
|
|
||||||
export default class DurationView extends React.Component<DurationViewProps> {
|
|
||||||
render() {
|
render() {
|
||||||
const { duration, label, inline, onDurationChange, className } = this.props;
|
const { duration, label, inline, onDurationChange, className } = this.props;
|
||||||
const inputsClassName = classNames("durationInputs", { inline });
|
const inputsClassName = classNames("durationInputs", { inline });
|
||||||
@ -99,18 +22,24 @@ export default class DurationView extends React.Component<DurationViewProps> {
|
|||||||
<Form.Field inline={inline} className={className}>
|
<Form.Field inline={inline} className={className}>
|
||||||
{label && <label>{label}</label>}
|
{label && <label>{label}</label>}
|
||||||
<div className={inputsClassName}>
|
<div className={inputsClassName}>
|
||||||
<NumberInput
|
<Input
|
||||||
|
type="number"
|
||||||
className="durationInput minutes"
|
className="durationInput minutes"
|
||||||
value={this.props.duration.minutes}
|
value={duration.minutes}
|
||||||
onChange={this.onMinutesChange}
|
onChange={this.onMinutesChange}
|
||||||
label="M"
|
label="M"
|
||||||
|
labelPosition="right"
|
||||||
|
onWheel={this.onWheel}
|
||||||
/>
|
/>
|
||||||
<NumberInput
|
<Input
|
||||||
|
type="number"
|
||||||
className="durationInput seconds"
|
className="durationInput seconds"
|
||||||
value={this.props.duration.seconds}
|
value={duration.seconds}
|
||||||
onChange={this.onSecondsChange}
|
onChange={this.onSecondsChange}
|
||||||
max={60}
|
max="60"
|
||||||
label="S"
|
label="S"
|
||||||
|
labelPosition="right"
|
||||||
|
onWheel={this.onWheel}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</Form.Field>
|
</Form.Field>
|
||||||
@ -126,25 +55,23 @@ export default class DurationView extends React.Component<DurationViewProps> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
componentWillReceiveProps(nextProps: Readonly<DurationViewProps>) {
|
private onMinutesChange: InputProps["onChange"] = (e, { value }) => {
|
||||||
if (nextProps.duration.minutes !== this.props.duration.minutes ||
|
if (!this.props.onDurationChange || isNaN(Number(value))) {
|
||||||
nextProps.duration.seconds !== this.props.duration.seconds) {
|
return;
|
||||||
this.setState({
|
|
||||||
minutes: nextProps.duration.minutes,
|
|
||||||
seconds: nextProps.duration.seconds,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private onMinutesChange = (newMinutes: number) => {
|
|
||||||
if (this.props.onDurationChange) {
|
|
||||||
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
|
|
||||||
}
|
}
|
||||||
|
const newMinutes = Number(value);
|
||||||
|
this.props.onDurationChange(this.props.duration.withMinutes(newMinutes));
|
||||||
};
|
};
|
||||||
|
|
||||||
private onSecondsChange = (newSeconds: number) => {
|
private onSecondsChange: InputProps["onChange"] = (e, { value }) => {
|
||||||
if (this.props.onDurationChange) {
|
if (!this.props.onDurationChange || isNaN(Number(value))) {
|
||||||
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds));
|
return;
|
||||||
}
|
}
|
||||||
|
const newSeconds = Number(value);
|
||||||
|
this.props.onDurationChange(this.props.duration.withSeconds(newSeconds));
|
||||||
|
};
|
||||||
|
|
||||||
|
private onWheel = () => {
|
||||||
|
// do nothing
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -105,17 +105,16 @@ 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(
|
||||||
function ProgramSequenceList(props: {
|
(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}`;
|
||||||
@ -133,6 +132,7 @@ const ProgramSequenceList = SortableContainer(
|
|||||||
return <ul className={className}>{listItems}</ul>;
|
return <ul className={className}>{listItems}</ul>;
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
|
{ withRef: true }
|
||||||
);
|
);
|
||||||
|
|
||||||
@observer
|
@observer
|
||||||
|
@ -8,7 +8,6 @@ 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<{
|
||||||
@ -70,12 +69,6 @@ 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,9 +20,7 @@ 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 classNames = require("classnames");
|
|
||||||
import { action } from "mobx";
|
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 }> {
|
||||||
@ -175,10 +173,8 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
|||||||
|
|
||||||
const { running, enabled, schedule, sequence } = program;
|
const { running, enabled, schedule, sequence } = program;
|
||||||
|
|
||||||
const className = classNames("programEditor", editing && "editing");
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Modal open onClose={this.close} className={className}>
|
<Modal open onClose={this.close} className="programEditor">
|
||||||
<Modal.Header>{this.renderName(program)}</Modal.Header>
|
<Modal.Header>{this.renderName(program)}</Modal.Header>
|
||||||
<Modal.Content>
|
<Modal.Content>
|
||||||
<Form>
|
<Form>
|
||||||
@ -187,6 +183,7 @@ 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,16 +208,6 @@ 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)}
|
||||||
@ -253,10 +240,6 @@ 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();
|
||||||
@ -282,28 +265,11 @@ class ProgramPage extends React.Component<ProgramPageProps> {
|
|||||||
|
|
||||||
@action.bound
|
@action.bound
|
||||||
private onEnabledChange(e: any, p: CheckboxProps) {
|
private onEnabledChange(e: any, p: CheckboxProps) {
|
||||||
if (p.checked !== undefined && this.program) {
|
if (this.programView) {
|
||||||
this.program.enabled = p.checked;
|
this.programView.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(ProgramPage));
|
const DecoratedProgramPage = injectState(withRouter(observer(ProgramPage)));
|
||||||
export default DecoratedProgramPage;
|
export default DecoratedProgramPage;
|
||||||
|
@ -92,10 +92,6 @@ export class WebSocketRpcClient extends s.SprinklersRPC {
|
|||||||
this._connect();
|
this._connect();
|
||||||
}
|
}
|
||||||
|
|
||||||
reconnect() {
|
|
||||||
this._connect();
|
|
||||||
}
|
|
||||||
|
|
||||||
stop() {
|
stop() {
|
||||||
if (this.reconnectTimer != null) {
|
if (this.reconnectTimer != null) {
|
||||||
clearTimeout(this.reconnectTimer);
|
clearTimeout(this.reconnectTimer);
|
||||||
|
@ -38,16 +38,8 @@ export default class AppState extends TypedEventEmitter<AppEvents> {
|
|||||||
when(() => !this.tokenStore.accessToken.isValid, this.checkToken);
|
when(() => !this.tokenStore.accessToken.isValid, this.checkToken);
|
||||||
this.sprinklersRpc.start();
|
this.sprinklersRpc.start();
|
||||||
});
|
});
|
||||||
|
|
||||||
document.addEventListener("visibilitychange", this.onPageFocus);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onPageFocus = () => {
|
|
||||||
if (document.visibilityState === "visible") {
|
|
||||||
this.sprinklersRpc.reconnect();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
@computed
|
@computed
|
||||||
get isLoggedIn() {
|
get isLoggedIn() {
|
||||||
return this.tokenStore.accessToken.isValid;
|
return this.tokenStore.accessToken.isValid;
|
||||||
|
@ -33,16 +33,20 @@ export function ConsumeState({ children }: ConsumeStateProps) {
|
|||||||
|
|
||||||
export function injectState<P extends { appState: AppState }>(
|
export function injectState<P extends { appState: AppState }>(
|
||||||
Component: React.ComponentType<P>
|
Component: React.ComponentType<P>
|
||||||
): React.FunctionComponent<Omit<P, "appState">> {
|
): React.ComponentClass<Omit<P, "appState">> {
|
||||||
return function InjectState(props) {
|
return class extends React.Component<Omit<P, "appState">> {
|
||||||
const state = React.useContext(StateContext);
|
render() {
|
||||||
if (state == null) {
|
const consumeState = (state: AppState | null) => {
|
||||||
throw new Error(
|
if (state == null) {
|
||||||
"Component with injectState must be mounted inside ProvideState"
|
throw new Error(
|
||||||
);
|
"Component with injectState must be mounted inside ProvideState"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// tslint:disable-next-line:no-object-literal-type-assertion
|
||||||
|
const allProps: Readonly<P> = {...this.props, appState: state} as Readonly<P>;
|
||||||
|
return <Component {...allProps} />;
|
||||||
|
};
|
||||||
|
return <StateContext.Consumer>{consumeState}</StateContext.Consumer>;
|
||||||
}
|
}
|
||||||
// tslint:disable-next-line: no-object-literal-type-assertion
|
};
|
||||||
const allProps: Readonly<P> = {...props, appState: state} as Readonly<P>;
|
|
||||||
return <Component {...allProps} />;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -78,19 +78,8 @@ $connected-color: #13d213;
|
|||||||
color: green;
|
color: green;
|
||||||
}
|
}
|
||||||
|
|
||||||
.program--nextRun {
|
.ui.modal.programEditor > .header > .header.item .inline.fields {
|
||||||
display: inline-block;
|
margin-bottom: 0;
|
||||||
padding-right: 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ui.modal.programEditor {
|
|
||||||
&.editing > .content {
|
|
||||||
min-height: 80vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
> .header > .header.item .inline.fields {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
.runSectionForm-runButton {
|
.runSectionForm-runButton {
|
||||||
|
@ -235,8 +235,7 @@ function getConfig(env) {
|
|||||||
extensions: [".ts", ".tsx", ".js", ".json", ".scss"],
|
extensions: [".ts", ".tsx", ".js", ".json", ".scss"],
|
||||||
alias: {
|
alias: {
|
||||||
"@client": paths.clientDir,
|
"@client": paths.clientDir,
|
||||||
"@common": paths.commonDir,
|
"@common": paths.commonDir
|
||||||
"react-dom": isDev ? "@hot-loader/react-dom" : "react-dom"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
module: { rules },
|
module: { rules },
|
||||||
@ -258,7 +257,7 @@ function getConfig(env) {
|
|||||||
target: paths.publicUrl
|
target: paths.publicUrl
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,8 +32,6 @@ 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;
|
||||||
@ -62,8 +60,7 @@ 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,
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -71,7 +68,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}, nextRun=${this.nextRun}}`
|
}, ` + `sequence=${this.sequence}, running=${this.running}}`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,8 +7,6 @@ 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, date } from "serializr";
|
import { createSimpleSchema, ModelSchema, object, primitive } from "serializr";
|
||||||
import * as s from "..";
|
import * as s from "..";
|
||||||
import list from "./list";
|
import list from "./list";
|
||||||
|
|
||||||
@ -85,8 +85,7 @@ 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(),
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -46,6 +46,8 @@
|
|||||||
"@oclif/command": "^1.5.0",
|
"@oclif/command": "^1.5.0",
|
||||||
"@oclif/config": "^1.7.4",
|
"@oclif/config": "^1.7.4",
|
||||||
"@oclif/plugin-help": "^2.1.1",
|
"@oclif/plugin-help": "^2.1.1",
|
||||||
|
"@types/pino-http": "^4.0.2",
|
||||||
|
"@types/split2": "^2.1.6",
|
||||||
"bcrypt": "^3.0.0",
|
"bcrypt": "^3.0.0",
|
||||||
"body-parser": "^1.18.3",
|
"body-parser": "^1.18.3",
|
||||||
"chalk": "^2.4.1",
|
"chalk": "^2.4.1",
|
||||||
@ -73,7 +75,6 @@
|
|||||||
"ws": "^7.1.1"
|
"ws": "^7.1.1"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@hot-loader/react-dom": "^16.8.6",
|
|
||||||
"@types/async": "^3.0.0",
|
"@types/async": "^3.0.0",
|
||||||
"@types/bcrypt": "^3.0.0",
|
"@types/bcrypt": "^3.0.0",
|
||||||
"@types/classnames": "^2.2.6",
|
"@types/classnames": "^2.2.6",
|
||||||
@ -85,7 +86,6 @@
|
|||||||
"@types/node": "^11.11.3",
|
"@types/node": "^11.11.3",
|
||||||
"@types/object-assign": "^4.0.30",
|
"@types/object-assign": "^4.0.30",
|
||||||
"@types/pino": "^5.20.0",
|
"@types/pino": "^5.20.0",
|
||||||
"@types/pino-http": "^4.0.2",
|
|
||||||
"@types/prop-types": "^15.5.5",
|
"@types/prop-types": "^15.5.5",
|
||||||
"@types/pump": "^1.0.1",
|
"@types/pump": "^1.0.1",
|
||||||
"@types/query-string": "^6.1.0",
|
"@types/query-string": "^6.1.0",
|
||||||
@ -94,7 +94,6 @@
|
|||||||
"@types/react-hot-loader": "^4.1.0",
|
"@types/react-hot-loader": "^4.1.0",
|
||||||
"@types/react-router-dom": "^4.3.0",
|
"@types/react-router-dom": "^4.3.0",
|
||||||
"@types/react-sortable-hoc": "^0.6.4",
|
"@types/react-sortable-hoc": "^0.6.4",
|
||||||
"@types/split2": "^2.1.6",
|
|
||||||
"@types/through2": "^2.0.33",
|
"@types/through2": "^2.0.33",
|
||||||
"@types/webpack-env": "^1.13.6",
|
"@types/webpack-env": "^1.13.6",
|
||||||
"@types/ws": "^6.0.0",
|
"@types/ws": "^6.0.0",
|
||||||
@ -124,7 +123,7 @@
|
|||||||
"promise": "^8.0.1",
|
"promise": "^8.0.1",
|
||||||
"prop-types": "^15.6.2",
|
"prop-types": "^15.6.2",
|
||||||
"query-string": "^6.1.0",
|
"query-string": "^6.1.0",
|
||||||
"react": "^16.8.0",
|
"react": "^16.6.3",
|
||||||
"react-dev-utils": "^9.0.1",
|
"react-dev-utils": "^9.0.1",
|
||||||
"react-dom": "^16.6.3",
|
"react-dom": "^16.6.3",
|
||||||
"react-hot-loader": "^4.3.5",
|
"react-hot-loader": "^4.3.5",
|
||||||
|
@ -22,7 +22,7 @@ if (!JWT_SECRET) {
|
|||||||
const ISSUER = "sprinklers3";
|
const ISSUER = "sprinklers3";
|
||||||
|
|
||||||
const ACCESS_TOKEN_LIFETIME = 30 * 60; // 30 minutes
|
const ACCESS_TOKEN_LIFETIME = 30 * 60; // 30 minutes
|
||||||
const REFRESH_TOKEN_LIFETIME = 7 * 24 * 60 * 60; // 7 days
|
const REFRESH_TOKEN_LIFETIME = 24 * 60 * 60; // 24 hours
|
||||||
|
|
||||||
function signToken(
|
function signToken(
|
||||||
claims: tok.TokenClaimTypes,
|
claims: tok.TokenClaimTypes,
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import { flags } from "@oclif/command";
|
|
||||||
import * as auth from "@server/authentication"
|
|
||||||
|
|
||||||
import ManageCommand from "@server/ManageCommand";
|
|
||||||
|
|
||||||
// tslint:disable:no-shadowed-variable
|
|
||||||
|
|
||||||
export default class TokenCommand extends ManageCommand {
|
|
||||||
static description = "Manage tokens";
|
|
||||||
|
|
||||||
static flags = {
|
|
||||||
"gen-device-reg": flags.boolean({
|
|
||||||
char: "d",
|
|
||||||
description: "Generate a device registration token",
|
|
||||||
}),
|
|
||||||
};
|
|
||||||
|
|
||||||
async run() {
|
|
||||||
const parseResult = this.parse(TokenCommand);
|
|
||||||
|
|
||||||
const flags = parseResult.flags;
|
|
||||||
|
|
||||||
if (flags["gen-device-reg"]) {
|
|
||||||
const token = await auth.generateDeviceRegistrationToken();
|
|
||||||
this.log(`Device registration token: "${token}"`)
|
|
||||||
} else {
|
|
||||||
this.error("Must specify a command to run");
|
|
||||||
this._help();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
12
yarn.lock
12
yarn.lock
@ -56,16 +56,6 @@
|
|||||||
reflect-metadata "^0.1.12"
|
reflect-metadata "^0.1.12"
|
||||||
tslib "^1.8.1"
|
tslib "^1.8.1"
|
||||||
|
|
||||||
"@hot-loader/react-dom@^16.8.6":
|
|
||||||
version "16.8.6"
|
|
||||||
resolved "https://registry.yarnpkg.com/@hot-loader/react-dom/-/react-dom-16.8.6.tgz#7923ba27db1563a7cc48d4e0b2879a140df461ea"
|
|
||||||
integrity sha512-+JHIYh33FVglJYZAUtRjfT5qZoT2mueJGNzU5weS2CVw26BgbxGKSujlJhO85BaRbg8sqNWyW1hYBILgK3ZCgA==
|
|
||||||
dependencies:
|
|
||||||
loose-envify "^1.1.0"
|
|
||||||
object-assign "^4.1.1"
|
|
||||||
prop-types "^15.6.2"
|
|
||||||
scheduler "^0.13.6"
|
|
||||||
|
|
||||||
"@most/multicast@^1.2.5":
|
"@most/multicast@^1.2.5":
|
||||||
version "1.3.0"
|
version "1.3.0"
|
||||||
resolved "https://registry.yarnpkg.com/@most/multicast/-/multicast-1.3.0.tgz#e01574840df634478ac3fabd164c6e830fb3b966"
|
resolved "https://registry.yarnpkg.com/@most/multicast/-/multicast-1.3.0.tgz#e01574840df634478ac3fabd164c6e830fb3b966"
|
||||||
@ -7191,7 +7181,7 @@ react-sortable-hoc@^1.9.1:
|
|||||||
invariant "^2.2.4"
|
invariant "^2.2.4"
|
||||||
prop-types "^15.5.7"
|
prop-types "^15.5.7"
|
||||||
|
|
||||||
react@^16.8.0:
|
react@^16.6.3:
|
||||||
version "16.8.6"
|
version "16.8.6"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe"
|
||||||
integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
|
integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw==
|
||||||
|
Loading…
x
Reference in New Issue
Block a user