Implemented a working devices list
This commit is contained in:
		
							parent
							
								
									187172e9e7
								
							
						
					
					
						commit
						e35f9bf0d9
					
				@ -19,7 +19,9 @@ function NavContainer() {
 | 
			
		||||
 | 
			
		||||
            <Switch>
 | 
			
		||||
                <Route path={route.device(":deviceId")} component={p.DevicePage}/>
 | 
			
		||||
                <Route path={route.device()} component={p.DevicesPage}/>
 | 
			
		||||
                <Route path={route.messagesTest} component={p.MessageTest}/>
 | 
			
		||||
                <Redirect from="/" to={route.device()} />
 | 
			
		||||
                <Redirect to="/"/>
 | 
			
		||||
            </Switch>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										6
									
								
								client/components/DeviceImage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										6
									
								
								client/components/DeviceImage.tsx
									
									
									
									
									
										Normal file
									
								
							@ -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")} />;
 | 
			
		||||
}
 | 
			
		||||
@ -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<DeviceViewProps & RouteComponentProps<any>> {
 | 
			
		||||
    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 (
 | 
			
		||||
            <React.Fragment>
 | 
			
		||||
                <Grid>
 | 
			
		||||
                    <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} />
 | 
			
		||||
            </React.Fragment>
 | 
			
		||||
        );
 | 
			
		||||
        return (
 | 
			
		||||
            <Item>
 | 
			
		||||
                <Item.Image src={require("@client/images/raspberry_pi.png")} />
 | 
			
		||||
                <Item.Content className="device">
 | 
			
		||||
                    <Header as="h1">
 | 
			
		||||
                        <div>Device <kbd>{id}</kbd></div>
 | 
			
		||||
                        <ConnectionState connectionState={connectionState} />
 | 
			
		||||
                    </Header>
 | 
			
		||||
                    <Item.Meta>
 | 
			
		||||
                        Raspberry Pi Grinklers Device
 | 
			
		||||
                    </Item.Meta>
 | 
			
		||||
                    {deviceBody}
 | 
			
		||||
                </Item.Content>
 | 
			
		||||
            </Item>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    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 = <span>You do not have access to this device</span>;
 | 
			
		||||
        } else {
 | 
			
		||||
            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>;
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -35,7 +35,7 @@ function NavBar({ appState }: { appState: AppState }) {
 | 
			
		||||
    }
 | 
			
		||||
    return (
 | 
			
		||||
        <Menu>
 | 
			
		||||
            <NavItem to={route.device("grinklers")}>Device grinklers</NavItem>
 | 
			
		||||
            <NavItem to={route.device()}>Devices</NavItem>
 | 
			
		||||
            <NavItem to={route.messagesTest}>Messages test</NavItem>
 | 
			
		||||
            <Menu.Menu position="right">
 | 
			
		||||
                {loginMenu}
 | 
			
		||||
 | 
			
		||||
@ -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";
 | 
			
		||||
 | 
			
		||||
@ -8,9 +8,10 @@ import { RouteComponentProps, withRouter } from "react-router";
 | 
			
		||||
class DevicePage extends React.Component<RouteComponentProps<{ deviceId: string }>> {
 | 
			
		||||
    render() {
 | 
			
		||||
        const { match: { params: { deviceId } } } = this.props;
 | 
			
		||||
        const devId = Number(deviceId);
 | 
			
		||||
        return (
 | 
			
		||||
            <Item.Group divided>
 | 
			
		||||
                <DeviceView deviceId={deviceId} />
 | 
			
		||||
                <DeviceView deviceId={devId} inList={false} />
 | 
			
		||||
            </Item.Group>
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										33
									
								
								client/pages/DevicesPage.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								client/pages/DevicesPage.tsx
									
									
									
									
									
										Normal file
									
								
							@ -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,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";
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@ -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 {
 | 
			
		||||
 | 
			
		||||
@ -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();
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -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;
 | 
			
		||||
 | 
			
		||||
@ -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_,
 | 
			
		||||
                });
 | 
			
		||||
            });
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
@ -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
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user