From e35f9bf0d928db25f161081b4e59e6b19679ff8c Mon Sep 17 00:00:00 2001 From: Alex Mikhalev Date: Sun, 12 Aug 2018 11:55:15 +0300 Subject: [PATCH] Implemented a working devices list --- client/App.tsx | 2 + client/components/DeviceImage.tsx | 6 +++ client/components/DeviceView.tsx | 72 +++++++++++++++++-------- client/components/NavBar.tsx | 2 +- client/components/index.ts | 1 + client/pages/DevicePage.tsx | 3 +- client/pages/DevicesPage.tsx | 33 ++++++++++++ client/pages/index.tsx | 1 + client/state/UserStore.ts | 3 +- common/httpApi/index.ts | 14 +++++ common/sprinklersRpc/websocketData.ts | 3 +- server/Database.ts | 2 +- server/entities/SprinklersDevice.ts | 3 +- server/entities/User.ts | 3 +- server/express/api/devices.ts | 2 +- server/express/api/users.ts | 4 +- server/repositories/UserRepository.ts | 2 +- server/sprinklersRpc/websocketServer.ts | 2 +- 18 files changed, 124 insertions(+), 34 deletions(-) create mode 100644 client/components/DeviceImage.tsx create mode 100644 client/pages/DevicesPage.tsx diff --git a/client/App.tsx b/client/App.tsx index a41b8be..29885f8 100644 --- a/client/App.tsx +++ b/client/App.tsx @@ -19,7 +19,9 @@ function NavContainer() { + + diff --git a/client/components/DeviceImage.tsx b/client/components/DeviceImage.tsx new file mode 100644 index 0000000..ad444f0 --- /dev/null +++ b/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 ; +} diff --git a/client/components/DeviceView.tsx b/client/components/DeviceView.tsx index 6ed54c8..082d3a4 100644 --- a/client/components/DeviceView.tsx +++ b/client/components/DeviceView.tsx @@ -1,12 +1,14 @@ import * as classNames from "classnames"; import { observer } from "mobx-react"; import * as React from "react"; +import { Link } from "react-router-dom"; import { Grid, Header, Icon, Item, SemanticICONS } from "semantic-ui-react"; +import { DeviceImage } from "@client/components"; import * as p from "@client/pages"; import * as route from "@client/routePaths"; 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 { ProgramTable, RunSectionForm, SectionRunnerView, SectionTable } from "."; @@ -44,16 +46,19 @@ const ConnectionState = observer(({ connectionState, className }: }); interface DeviceViewProps { - deviceId: string; + deviceId: number; appState: AppState; + inList?: boolean; } class DeviceView extends React.Component> { - render() { - const { uiStore, sprinklersRpc, routerStore } = this.props.appState; - const device = sprinklersRpc.getDevice(this.props.deviceId); - const { id, connectionState, sections, sectionRunner } = device; - const deviceBody = connectionState.isAvailable && ( + renderBody(device: SprinklersDevice) { + const { inList, appState: { uiStore, routerStore } } = this.props; + const { connectionState, sectionRunner, sections } = device; + if (!connectionState.isAvailable || inList) { + return null; + } + return ( @@ -70,21 +75,44 @@ class DeviceView extends React.Component ); - return ( - - - -
-
Device {id}
- -
- - Raspberry Pi Grinklers Device - - {deviceBody} -
-
- ); + } + + render() { + const { deviceId, inList, appState: { sprinklersRpc, userStore } } = this.props; + const { userData } = userStore; + const iDevice = userData && + userData.devices && + userData.devices.find((dev) => dev.id === deviceId); + let itemContent: React.ReactNode; + if (!iDevice || !iDevice.deviceId) { + // TODO: better and link back to devices list + itemContent = You do not have access to this device; + } else { + const device = sprinklersRpc.getDevice(iDevice.deviceId); + const { connectionState } = device; + let header: React.ReactNode; + if (inList) { // tslint:disable-line:prefer-conditional-expression + header = Device {iDevice.name}; + } else { + header = Device {iDevice.name}; + } + itemContent = ( + + + +
+ {header} + +
+ + Raspberry Pi Grinklers Device + + {this.renderBody(device)} +
+
+ ); + } + return {itemContent}; } } diff --git a/client/components/NavBar.tsx b/client/components/NavBar.tsx index 541fae2..64bf494 100644 --- a/client/components/NavBar.tsx +++ b/client/components/NavBar.tsx @@ -35,7 +35,7 @@ function NavBar({ appState }: { appState: AppState }) { } return ( - Device grinklers + Devices Messages test {loginMenu} diff --git a/client/components/index.ts b/client/components/index.ts index 77c3cf6..466809f 100644 --- a/client/components/index.ts +++ b/client/components/index.ts @@ -1,3 +1,4 @@ +export { default as DeviceImage } from "./DeviceImage"; export { default as DeviceView } from "./DeviceView"; export { default as DurationView } from "./DurationView"; export { default as MessagesView } from "./MessagesView"; diff --git a/client/pages/DevicePage.tsx b/client/pages/DevicePage.tsx index 06417df..52b902a 100644 --- a/client/pages/DevicePage.tsx +++ b/client/pages/DevicePage.tsx @@ -8,9 +8,10 @@ import { RouteComponentProps, withRouter } from "react-router"; class DevicePage extends React.Component> { render() { const { match: { params: { deviceId } } } = this.props; + const devId = Number(deviceId); return ( - + ); } diff --git a/client/pages/DevicesPage.tsx b/client/pages/DevicesPage.tsx new file mode 100644 index 0000000..3770a7a --- /dev/null +++ b/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 = Not logged in; + } else if (!userData.devices || !userData.devices.length) { + deviceNodes = You have no devices; + } else { + deviceNodes = userData.devices.map((device) => ( + + )); + } + return ( + +

Devices

+ + {deviceNodes} + +
+ ); + } +} + +export default injectState(observer(DevicesPage)); diff --git a/client/pages/index.tsx b/client/pages/index.tsx index b4fffd0..d74cdc2 100644 --- a/client/pages/index.tsx +++ b/client/pages/index.tsx @@ -1,4 +1,5 @@ export { default as DevicePage } from "./DevicePage"; +export { default as DevicesPage } from "./DevicesPage"; export { default as LoginPage } from "./LoginPage"; export { default as LogoutPage } from "./LogoutPage"; export { default as MessageTest } from "./MessageTest"; diff --git a/client/state/UserStore.ts b/client/state/UserStore.ts index b5e4fff..7efd0fb 100644 --- a/client/state/UserStore.ts +++ b/client/state/UserStore.ts @@ -1,5 +1,6 @@ +import { IUser } from "@common/httpApi"; import { observable } from "mobx"; export class UserStore { - @observable userData: any = null; + @observable userData: IUser | null = null; } diff --git a/common/httpApi/index.ts b/common/httpApi/index.ts index f5cc09f..7f3803d 100644 --- a/common/httpApi/index.ts +++ b/common/httpApi/index.ts @@ -15,3 +15,17 @@ export interface TokenGrantResponse { access_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; +} diff --git a/common/sprinklersRpc/websocketData.ts b/common/sprinklersRpc/websocketData.ts index 9850a57..4235b81 100644 --- a/common/sprinklersRpc/websocketData.ts +++ b/common/sprinklersRpc/websocketData.ts @@ -1,6 +1,7 @@ import * as rpc from "../jsonRpc/index"; import { ErrorCode } from "@common/ErrorCode"; +import { IUser } from "@common/httpApi"; import { Response as ResponseData } from "@common/sprinklersRpc/deviceRequests"; export interface IAuthenticateRequest { @@ -25,7 +26,7 @@ export interface IClientRequestTypes { export interface IAuthenticateResponse { authenticated: boolean; message: string; - user: any; + user: IUser; } export interface IDeviceSubscribeResponse { diff --git a/server/Database.ts b/server/Database.ts index f59edea..03d9ae3 100644 --- a/server/Database.ts +++ b/server/Database.ts @@ -61,7 +61,7 @@ export class Database { } for (let i = 0; i < NUM; i++) { - const name = "test" + i; + const name = "Test" + i; let device = await this.sprinklersDevices.findByName(name); if (!device) { device = await this.sprinklersDevices.create(); diff --git a/server/entities/SprinklersDevice.ts b/server/entities/SprinklersDevice.ts index 48a6594..cb14c0e 100644 --- a/server/entities/SprinklersDevice.ts +++ b/server/entities/SprinklersDevice.ts @@ -1,9 +1,10 @@ import { Column, Entity, ManyToMany, PrimaryGeneratedColumn } from "typeorm"; +import { ISprinklersDevice } from "@common/httpApi"; import { User } from "./User"; @Entity() -export class SprinklersDevice { +export class SprinklersDevice implements ISprinklersDevice { @PrimaryGeneratedColumn() id!: number; diff --git a/server/entities/User.ts b/server/entities/User.ts index 4d9263a..be9c01d 100644 --- a/server/entities/User.ts +++ b/server/entities/User.ts @@ -2,12 +2,13 @@ import * as bcrypt from "bcrypt"; import { omit } from "lodash"; import { Column, Entity, JoinTable, ManyToMany, PrimaryGeneratedColumn } from "typeorm"; +import { IUser } from "@common/httpApi"; import { SprinklersDevice} from "./SprinklersDevice"; const HASH_ROUNDS = 1; @Entity() -export class User { +export class User implements IUser { @PrimaryGeneratedColumn() id!: number; diff --git a/server/express/api/devices.ts b/server/express/api/devices.ts index 56554b1..8e56827 100644 --- a/server/express/api/devices.ts +++ b/server/express/api/devices.ts @@ -28,7 +28,7 @@ export function devices(state: ServerState) { router.post("/register", verifyAuthorization({ type: "device_reg", }), async (req, res) => { - + // TODO: Implement device registration }); return router; diff --git a/server/express/api/users.ts b/server/express/api/users.ts index 034d9e9..62dc06e 100644 --- a/server/express/api/users.ts +++ b/server/express/api/users.ts @@ -23,9 +23,9 @@ export function users(state: ServerState) { router.get("/", (req, res) => { state.database.users.findAll() - .then((users) => { + .then((users_) => { res.json({ - data: users, + data: users_, }); }); }); diff --git a/server/repositories/UserRepository.ts b/server/repositories/UserRepository.ts index 8112800..a2372a5 100644 --- a/server/repositories/UserRepository.ts +++ b/server/repositories/UserRepository.ts @@ -19,7 +19,7 @@ export class UserRepository extends Repository { const opts = applyDefaultOptions(options); return super.find(opts); } - + findById(id: number, options?: Partial) { const opts = applyDefaultOptions(options); return super.findOne(id, opts); diff --git a/server/sprinklersRpc/websocketServer.ts b/server/sprinklersRpc/websocketServer.ts index f229428..9e64be6 100644 --- a/server/sprinklersRpc/websocketServer.ts +++ b/server/sprinklersRpc/websocketServer.ts @@ -8,10 +8,10 @@ import log from "@common/logger"; import * as deviceRequests from "@common/sprinklersRpc/deviceRequests"; import * as schema from "@common/sprinklersRpc/schema"; import * as ws from "@common/sprinklersRpc/websocketData"; +import { AccessToken } from "@common/TokenClaims"; import { User } from "@server/entities"; import { verifyToken } from "@server/express/authentication"; import { ServerState } from "@server/state"; -import { AccessToken } from "@common/TokenClaims"; // tslint:disable:member-ordering