Browse Source

Implemented a working devices list

update-deps
Alex Mikhalev 7 years ago
parent
commit
e35f9bf0d9
  1. 2
      client/App.tsx
  2. 6
      client/components/DeviceImage.tsx
  3. 72
      client/components/DeviceView.tsx
  4. 2
      client/components/NavBar.tsx
  5. 1
      client/components/index.ts
  6. 3
      client/pages/DevicePage.tsx
  7. 33
      client/pages/DevicesPage.tsx
  8. 1
      client/pages/index.tsx
  9. 3
      client/state/UserStore.ts
  10. 14
      common/httpApi/index.ts
  11. 3
      common/sprinklersRpc/websocketData.ts
  12. 2
      server/Database.ts
  13. 3
      server/entities/SprinklersDevice.ts
  14. 3
      server/entities/User.ts
  15. 2
      server/express/api/devices.ts
  16. 4
      server/express/api/users.ts
  17. 2
      server/repositories/UserRepository.ts
  18. 2
      server/sprinklersRpc/websocketServer.ts

2
client/App.tsx

@ -19,7 +19,9 @@ function NavContainer() {
<Switch> <Switch>
<Route path={route.device(":deviceId")} component={p.DevicePage}/> <Route path={route.device(":deviceId")} component={p.DevicePage}/>
<Route path={route.device()} component={p.DevicesPage}/>
<Route path={route.messagesTest} component={p.MessageTest}/> <Route path={route.messagesTest} component={p.MessageTest}/>
<Redirect from="/" to={route.device()} />
<Redirect to="/"/> <Redirect to="/"/>
</Switch> </Switch>

6
client/components/DeviceImage.tsx

@ -0,0 +1,6 @@
import * as React from "react";
import { Item, ItemImageProps } from "semantic-ui-react";
export default function DeviceImage(props: ItemImageProps) {
return <Item.Image {...props} src={require("@client/images/raspberry_pi.png")} />;
}

72
client/components/DeviceView.tsx

@ -1,12 +1,14 @@
import * as classNames from "classnames"; import * as classNames from "classnames";
import { observer } from "mobx-react"; import { observer } from "mobx-react";
import * as React from "react"; import * as React from "react";
import { Link } from "react-router-dom";
import { Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react"; import { Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react";
import { DeviceImage } from "@client/components";
import * as p from "@client/pages"; import * as p from "@client/pages";
import * as route from "@client/routePaths"; import * as route from "@client/routePaths";
import { AppState, injectState } from "@client/state"; import { AppState, injectState } from "@client/state";
import { ConnectionState as ConState } from "@common/sprinklersRpc"; import { ConnectionState as ConState, SprinklersDevice } from "@common/sprinklersRpc";
import { Route, RouteComponentProps, withRouter } from "react-router"; import { Route, RouteComponentProps, withRouter } from "react-router";
import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from "."; import { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from ".";
@ -44,16 +46,19 @@ const ConnectionState = observer(({ connectionState, className }:
}); });
interface DeviceViewProps { interface DeviceViewProps {
deviceId: string; deviceId: number;
appState: AppState; appState: AppState;
inList?: boolean;
} }
class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<any>> { class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<any>> {
render() { renderBody(device: SprinklersDevice) {
const { uiStore, sprinklersRpc, routerStore } = this.props.appState; const { inList, appState: { uiStore, routerStore } } = this.props;
const device = sprinklersRpc.getDevice(this.props.deviceId); const { connectionState, sectionRunner, sections } = device;
const { id, connectionState, sections, sectionRunner } = device; if (!connectionState.isAvailable || inList) {
const deviceBody = connectionState.isAvailable && ( return null;
}
return (
<React.Fragment> <React.Fragment>
<Grid> <Grid>
<Grid.Column mobile="16" tablet="16" computer="16" largeScreen="6"> <Grid.Column mobile="16" tablet="16" computer="16" largeScreen="6">
@ -70,21 +75,44 @@ class DeviceView extends React.Component<DeviceViewProps & RouteComponentProps<a
<Route path={route.program(":deviceId", ":programId")} component={p.ProgramPage} /> <Route path={route.program(":deviceId", ":programId")} component={p.ProgramPage} />
</React.Fragment> </React.Fragment>
); );
return ( }
<Item>
<Item.Image src={require("@client/images/raspberry_pi.png")} /> render() {
<Item.Content className="device"> const { deviceId, inList, appState: { sprinklersRpc, userStore } } = this.props;
<Header as="h1"> const { userData } = userStore;
<div>Device <kbd>{id}</kbd></div> const iDevice = userData &&
<ConnectionState connectionState={connectionState} /> userData.devices &&
</Header> userData.devices.find((dev) => dev.id === deviceId);
<Item.Meta> let itemContent: React.ReactNode;
Raspberry Pi Grinklers Device if (!iDevice || !iDevice.deviceId) {
</Item.Meta> // TODO: better and link back to devices list
{deviceBody} itemContent = <span>You do not have access to this device</span>;
</Item.Content> } else {
</Item> const device = sprinklersRpc.getDevice(iDevice.deviceId);
); const { connectionState } = device;
let header: React.ReactNode;
if (inList) { // tslint:disable-line:prefer-conditional-expression
header = <Link to={route.device(iDevice.id)}>Device <kbd>{iDevice.name}</kbd></Link>;
} else {
header = <span>Device <kbd>{iDevice.name}</kbd></span>;
}
itemContent = (
<React.Fragment>
<DeviceImage size={inList ? "tiny" : undefined} />
<Item.Content className="device">
<Header as={inList ? "h2" : "h1"}>
{header}
<ConnectionState connectionState={connectionState} />
</Header>
<Item.Meta>
Raspberry Pi Grinklers Device
</Item.Meta>
{this.renderBody(device)}
</Item.Content>
</React.Fragment>
);
}
return <Item>{itemContent}</Item>;
} }
} }

2
client/components/NavBar.tsx

@ -35,7 +35,7 @@ function NavBar({ appState }: { appState: AppState }) {
} }
return ( return (
<Menu> <Menu>
<NavItem to={route.device("grinklers")}>Device grinklers</NavItem> <NavItem to={route.device()}>Devices</NavItem>
<NavItem to={route.messagesTest}>Messages test</NavItem> <NavItem to={route.messagesTest}>Messages test</NavItem>
<Menu.Menu position="right"> <Menu.Menu position="right">
{loginMenu} {loginMenu}

1
client/components/index.ts

@ -1,3 +1,4 @@
export { default as DeviceImage } from "./DeviceImage";
export { default as DeviceView } from "./DeviceView"; export { default as DeviceView } from "./DeviceView";
export { default as DurationView } from "./DurationView"; export { default as DurationView } from "./DurationView";
export { default as MessagesView } from "./MessagesView"; export { default as MessagesView } from "./MessagesView";

3
client/pages/DevicePage.tsx

@ -8,9 +8,10 @@ import { RouteComponentProps, withRouter } from "react-router";
class DevicePage extends React.Component<RouteComponentProps<{ deviceId: string }>> { class DevicePage extends React.Component<RouteComponentProps<{ deviceId: string }>> {
render() { render() {
const { match: { params: { deviceId } } } = this.props; const { match: { params: { deviceId } } } = this.props;
const devId = Number(deviceId);
return ( return (
<Item.Group divided> <Item.Group divided>
<DeviceView deviceId={deviceId} /> <DeviceView deviceId={devId} inList={false} />
</Item.Group> </Item.Group>
); );
} }

33
client/pages/DevicesPage.tsx

@ -0,0 +1,33 @@
import { observer } from "mobx-react";
import * as React from "react";
import { Item } from "semantic-ui-react";
import { DeviceView } from "@client/components";
import { AppState, injectState } from "@client/state";
class DevicesPage extends React.Component<{ appState: AppState }> {
render() {
const { appState } = this.props;
const { userData } = appState.userStore;
let deviceNodes: React.ReactNode;
if (!userData) {
deviceNodes = <span>Not logged in</span>;
} else if (!userData.devices || !userData.devices.length) {
deviceNodes = <span>You have no devices</span>;
} else {
deviceNodes = userData.devices.map((device) => (
<DeviceView key={device.id} deviceId={device.id} inList />
));
}
return (
<React.Fragment>
<h1>Devices</h1>
<Item.Group>
{deviceNodes}
</Item.Group>
</React.Fragment>
);
}
}
export default injectState(observer(DevicesPage));

1
client/pages/index.tsx

@ -1,4 +1,5 @@
export { default as DevicePage } from "./DevicePage"; export { default as DevicePage } from "./DevicePage";
export { default as DevicesPage } from "./DevicesPage";
export { default as LoginPage } from "./LoginPage"; export { default as LoginPage } from "./LoginPage";
export { default as LogoutPage } from "./LogoutPage"; export { default as LogoutPage } from "./LogoutPage";
export { default as MessageTest } from "./MessageTest"; export { default as MessageTest } from "./MessageTest";

3
client/state/UserStore.ts

@ -1,5 +1,6 @@
import { IUser } from "@common/httpApi";
import { observable } from "mobx"; import { observable } from "mobx";
export class UserStore { export class UserStore {
@observable userData: any = null; @observable userData: IUser | null = null;
} }

14
common/httpApi/index.ts

@ -15,3 +15,17 @@ export interface TokenGrantResponse {
access_token: string; access_token: string;
refresh_token: string; refresh_token: string;
} }
export interface IUser {
id: number;
username: string;
name: string;
devices: ISprinklersDevice[] | undefined;
}
export interface ISprinklersDevice {
id: number;
deviceId: string | null;
name: string;
users: IUser[] | undefined;
}

3
common/sprinklersRpc/websocketData.ts

@ -1,6 +1,7 @@
import * as rpc from "../jsonRpc/index"; import * as rpc from "../jsonRpc/index";
import { ErrorCode } from "@common/ErrorCode"; import { ErrorCode } from "@common/ErrorCode";
import { IUser } from "@common/httpApi";
import { Response as ResponseData } from "@common/sprinklersRpc/deviceRequests"; import { Response as ResponseData } from "@common/sprinklersRpc/deviceRequests";
export interface IAuthenticateRequest { export interface IAuthenticateRequest {
@ -25,7 +26,7 @@ export interface IClientRequestTypes {
export interface IAuthenticateResponse { export interface IAuthenticateResponse {
authenticated: boolean; authenticated: boolean;
message: string; message: string;
user: any; user: IUser;
} }
export interface IDeviceSubscribeResponse { export interface IDeviceSubscribeResponse {

2
server/Database.ts

@ -61,7 +61,7 @@ export class Database {
} }
for (let i = 0; i < NUM; i++) { for (let i = 0; i < NUM; i++) {
const name = "test" + i; const name = "Test" + i;
let device = await this.sprinklersDevices.findByName(name); let device = await this.sprinklersDevices.findByName(name);
if (!device) { if (!device) {
device = await this.sprinklersDevices.create(); device = await this.sprinklersDevices.create();

3
server/entities/SprinklersDevice.ts

@ -1,9 +1,10 @@
import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
import { ISprinklersDevice } from "@common/httpApi";
import { User } from "./User"; import { User } from "./User";
@Entity() @Entity()
export class SprinklersDevice { export class SprinklersDevice implements ISprinklersDevice {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id!: number; id!: number;

3
server/entities/User.ts

@ -2,12 +2,13 @@ import * as bcrypt from "bcrypt";
import { omit } from "lodash"; import { omit } from "lodash";
import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from "typeorm"; import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from "typeorm";
import { IUser } from "@common/httpApi";
import { SprinklersDevice} from "./SprinklersDevice"; import { SprinklersDevice} from "./SprinklersDevice";
const HASH_ROUNDS = 1; const HASH_ROUNDS = 1;
@Entity() @Entity()
export class User { export class User implements IUser {
@PrimaryGeneratedColumn() @PrimaryGeneratedColumn()
id!: number; id!: number;

2
server/express/api/devices.ts

@ -28,7 +28,7 @@ export function devices(state: ServerState) {
router.post("/register", verifyAuthorization({ router.post("/register", verifyAuthorization({
type: "device_reg", type: "device_reg",
}), async (req, res) => { }), async (req, res) => {
// TODO: Implement device registration
}); });
return router; return router;

4
server/express/api/users.ts

@ -23,9 +23,9 @@ export function users(state: ServerState) {
router.get("/", (req, res) => { router.get("/", (req, res) => {
state.database.users.findAll() state.database.users.findAll()
.then((users) => { .then((users_) => {
res.json({ res.json({
data: users, data: users_,
}); });
}); });
}); });

2
server/repositories/UserRepository.ts

@ -19,7 +19,7 @@ export class UserRepository extends Repository<User> {
const opts = applyDefaultOptions(options); const opts = applyDefaultOptions(options);
return super.find(opts); return super.find(opts);
} }
findById(id: number, options?: Partial<FindUserOptions>) { findById(id: number, options?: Partial<FindUserOptions>) {
const opts = applyDefaultOptions(options); const opts = applyDefaultOptions(options);
return super.findOne(id, opts); return super.findOne(id, opts);

2
server/sprinklersRpc/websocketServer.ts

@ -8,10 +8,10 @@ import log from "@common/logger";
import * as deviceRequests from "@common/sprinklersRpc/deviceRequests"; import * as deviceRequests from "@common/sprinklersRpc/deviceRequests";
import * as schema from "@common/sprinklersRpc/schema"; import * as schema from "@common/sprinklersRpc/schema";
import * as ws from "@common/sprinklersRpc/websocketData"; import * as ws from "@common/sprinklersRpc/websocketData";
import { AccessToken } from "@common/TokenClaims";
import { User } from "@server/entities"; import { User } from "@server/entities";
import { verifyToken } from "@server/express/authentication"; import { verifyToken } from "@server/express/authentication";
import { ServerState } from "@server/state"; import { ServerState } from "@server/state";
import { AccessToken } from "@common/TokenClaims";
// tslint:disable:member-ordering // tslint:disable:member-ordering

Loading…
Cancel
Save