diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..1f6b6e9 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,6 @@ +dist/ +site/ +vendor/ +.idea/ +.vscode/ +room_keys.json diff --git a/.gitignore b/.gitignore index da786f4..b3fd938 100644 --- a/.gitignore +++ b/.gitignore @@ -3,7 +3,11 @@ dist room_keys.json .vscode -.tmp -vendor -node_modules -build/ \ No newline at end of file +.tmp/ +vendor/ +node_modules/ +build/ +livego +static/ +webui/.tmp/ +pkged.go diff --git a/Dockerfile b/Dockerfile index 37aa7d1..5fc5ae5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,22 +1,53 @@ -FROM golang:latest as builder -WORKDIR /app -COPY go.mod go.sum ./ -RUN go mod download -COPY . . -RUN CGO_ENABLED=0 GOOS=linux go build -a -installsuffix cgo -o livego . - -FROM alpine:latest -LABEL maintainer="Ruben Cid Lara " -RUN mkdir -p /app/config -WORKDIR /app +# WEBUI +FROM node:13.12 as webui + +ENV WEBUI_DIR /src/webui +RUN mkdir -p $WEBUI_DIR + +COPY ./webui/ $WEBUI_DIR/ + +WORKDIR $WEBUI_DIR + +RUN npm install +RUN npm run build + +# BUILD +FROM golang:latest as gobuild + +RUN go get github.com/markbates/pkger/cmd/pkger + +WORKDIR /go/src/github.com/gwuhaolin/livego + +# Download go modules +COPY go.mod . +COPY go.sum . +RUN GO111MODULE=on GOPROXY=https://proxy.golang.org go mod download + +ENV REPO github.com/gwuhaolin/livego + +COPY . /go/src/github.com/gwuhaolin/livego + +RUN rm -rf /go/src/github.com/gwuhaolin/livego/static/ +COPY --from=webui /src/webui/static/ /go/src/github.com/gwuhaolin/livego/static/ + +RUN make build + +## IMAGE +FROM alpine:3.10 + +COPY --from=gobuild /go/src/github.com/gwuhaolin/livego/config / +COPY --from=gobuild /go/src/github.com/gwuhaolin/livego/livego / + +VOLUME ["/tmp"] + ENV RTMP_PORT 1935 ENV HTTP_FLV_PORT 7001 ENV HLS_PORT 7002 ENV HTTP_OPERATION_PORT 8090 -COPY --from=builder /app/config ./config -COPY --from=builder /app/livego . + EXPOSE ${RTMP_PORT} EXPOSE ${HTTP_FLV_PORT} EXPOSE ${HLS_PORT} EXPOSE ${HTTP_OPERATION_PORT} -ENTRYPOINT ["./livego"] + +ENTRYPOINT ["/livego"] \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6e6ed5b --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +GO_BIN ?= go + +tidy: +ifeq ($(GO111MODULE),on) + $(GO_BIN) mod tidy +else + echo skipping go mod tidy +endif + +build: + pkger + $(GO_BIN) build -v . + make tidy + +test: + pkger + $(GO_BIN) test -tags ${TAGS} -cover ./... + pkger + make tidy + +lint: + golangci-lint --vendor ./... --deadline=1m --skip=internal + +## Build WebUI Docker image +build-webui-image: + docker build -t livego-webui -f webui/Dockerfile webui + +generate-webui: build-webui-image + if [ ! -d "static" ]; then \ + mkdir -p static; \ + docker run --rm -v "$$PWD/static":'/src/webui/build' livego-webui npm run build; \ + docker run --rm -v "$$PWD/static":'/src/webui/build' livego-webui chown -R $(shell id -u):$(shell id -g) ./build; \ + echo 'For more informations show `webui/readme.md`' > $$PWD/static/DONT-EDIT-FILES-IN-THIS-DIRECTORY.md; \ + fi + +dockerize: + docker build -t gwuhaolin:livego . + +binary: generate-webui + make build + +default: binary diff --git a/configure/liveconfig.go b/configure/liveconfig.go index b7a68ff..1ba574c 100644 --- a/configure/liveconfig.go +++ b/configure/liveconfig.go @@ -21,22 +21,20 @@ import ( */ var ( roomKeySaveFile = flag.String("KeyFile", "room_keys.json", "path to save room keys") - RedisAddr = flag.String("redis_addr", "", "redis addr to save room keys ex. localhost:6379") - RedisPwd = flag.String("redis_pwd", "", "redis password") + redisAddr = flag.String("redis_addr", "", "redis addr to save room keys ex. localhost:6379") + redisPwd = flag.String("redis_pwd", "", "redis password") + dashboard = flag.Bool("dashboard", false, "Enable dashboard ui") ) - type Application struct { Appname string `json:"appname"` Liveon string `json:"liveon"` Hlson string `json:"hlson"` StaticPush []string `json:"static_push"` } - type JWTCfg struct { Secret string `json:"secret"` Algorithm string `json:"algorithm"` } - type ServerCfg struct { DashBoard bool `json:"dashboard"` KeyFile string `json:"key_file"` @@ -80,22 +78,29 @@ func GetKeyFile() *string { func GetRedisAddr() *string { if len(RtmpServercfg.RedisAddr) > 0 { - *RedisAddr = RtmpServercfg.RedisAddr + *redisAddr = RtmpServercfg.RedisAddr } - if len(*RedisAddr) == 0 { + if len(*redisAddr) == 0 { return nil } - return RedisAddr + return redisAddr +} + +func ShowDashboard() bool { + if dashboard != nil && *dashboard == true { + return true + } + return RtmpServercfg.DashBoard } func GetRedisPwd() *string { if len(RtmpServercfg.RedisPwd) > 0 { - *RedisPwd = RtmpServercfg.RedisPwd + *redisPwd = RtmpServercfg.RedisPwd } - return RedisPwd + return redisPwd } func CheckAppName(appname string) bool { diff --git a/go.mod b/go.mod index df73483..212c098 100644 --- a/go.mod +++ b/go.mod @@ -5,9 +5,9 @@ go 1.13 require ( github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b github.com/dgrijalva/jwt-go v3.2.0+incompatible - github.com/elazarl/go-bindata-assetfs v1.0.0 github.com/go-redis/redis/v7 v7.2.0 github.com/gorilla/mux v1.7.4 + github.com/markbates/pkger v0.15.1 github.com/orcaman/concurrent-map v0.0.0-20190826125027-8c72a8bb44f6 github.com/satori/go.uuid v1.2.0 github.com/smartystreets/goconvey v1.6.4 // indirect diff --git a/go.sum b/go.sum index 998adc8..6d45c66 100644 --- a/go.sum +++ b/go.sum @@ -2,14 +2,16 @@ github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b h1:CvoEHGm github.com/auth0/go-jwt-middleware v0.0.0-20190805220309-36081240882b/go.mod h1:LWMyo4iOLWXHGdBki7NIht1kHru/0wM179h+d3g8ATM= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= -github.com/elazarl/go-bindata-assetfs v1.0.0 h1:G/bYguwHIzWq9ZoyUQqrjTmJbbYn3j3CKKpKinvZLFk= -github.com/elazarl/go-bindata-assetfs v1.0.0/go.mod h1:v+YaWX3bdea5J/mo8dSETolEo7R71Vk1u8bnjau5yw4= github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= github.com/go-redis/redis/v7 v7.2.0 h1:CrCexy/jYWZjW0AyVoHlcJUeZN19VWlbepTh1Vq6dJs= github.com/go-redis/redis/v7 v7.2.0/go.mod h1:JDNMw23GTyLNC4GZu9njt15ctBQVn7xjRfnwdHj/Dcg= +github.com/gobuffalo/here v0.6.0 h1:hYrd0a6gDmWxBM4TnrGw8mQg24iSVoIkHEk7FodQcBI= +github.com/gobuffalo/here v0.6.0/go.mod h1:wAG085dHOYqUpf+Ap+WOdrPTp5IYcDAs/x7PLa8Y5fM= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.3.2 h1:6nsPYzhq5kReh6QImI3k5qWzO4PEbvbIW2cwSfR/6xs= github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= @@ -26,6 +28,8 @@ github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORN github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/markbates/pkger v0.15.1 h1:3MPelV53RnGSW07izx5xGxl4e/sdRD6zqseIk0rMASY= +github.com/markbates/pkger v0.15.1/go.mod h1:0JoVlrol20BSywW79rN3kdFFsE5xYM+rSCQDXbLhiuI= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.10.1 h1:q/mM8GF/n0shIN8SaAZ0V+jnLPzen6WIVZdiwrRlMlo= github.com/onsi/ginkgo v1.10.1/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= @@ -62,6 +66,7 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= @@ -72,3 +77,5 @@ gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo= +gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/protocol/dashboard/dashboard.go b/protocol/dashboard/dashboard.go index d8be513..579a319 100644 --- a/protocol/dashboard/dashboard.go +++ b/protocol/dashboard/dashboard.go @@ -4,13 +4,13 @@ import ( "log" "net/http" - assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/gorilla/mux" + "github.com/markbates/pkger" ) // DashboardHandler expose dashboard routes type DashboardHandler struct { - Assets *assetfs.AssetFS + Assets *pkger.Dir } // Append add dashboard routes on a router diff --git a/protocol/httpopera/http_opera.go b/protocol/httpopera/http_opera.go index 7799165..ae55940 100755 --- a/protocol/httpopera/http_opera.go +++ b/protocol/httpopera/http_opera.go @@ -9,13 +9,14 @@ import ( "livego/av" "livego/configure" + "livego/protocol/dashboard" "livego/protocol/rtmp" "livego/protocol/rtmp/rtmprelay" jwtmiddleware "github.com/auth0/go-jwt-middleware" "github.com/dgrijalva/jwt-go" - assetfs "github.com/elazarl/go-bindata-assetfs" "github.com/gorilla/mux" + "github.com/markbates/pkger" ) type Response struct { @@ -88,18 +89,22 @@ func JWTMiddleware(next http.Handler) http.Handler { return } next.ServeHTTP(w, r) - }) } func (s *Server) Serve(l net.Listener) error { router := mux.NewRouter() - if configure.RtmpServercfg.DashBoard { - DashboardHandler{Assets: &assetfs.AssetFS{Asset: genstatic.Asset, AssetInfo: genstatic.AssetInfo, AssetDir: genstatic.AssetDir, Prefix: "static"}}.Append(router) + if configure.ShowDashboard() { + log.Printf("DASHBOARD On /dashboard") + + dir := pkger.Dir("/static") + dashboard.DashboardHandler{Assets: &dir}.Append(router) + } else { + log.Printf("DASHBOARD Off") } - router.Handle("/statics/", http.StripPrefix("/statics/", http.FileServer(http.Dir("statics")))) + // router.Handle("/statics/", http.StripPrefix("/statics/", http.FileServer(http.Dir("statics")))) router.HandleFunc("/control/push", func(w http.ResponseWriter, r *http.Request) { s.handlePush(w, r) diff --git a/webui/.dockerignore b/webui/.dockerignore new file mode 100644 index 0000000..962d6c6 --- /dev/null +++ b/webui/.dockerignore @@ -0,0 +1,5 @@ +# compiled output +build/ + +# dependencies +/node_modules \ No newline at end of file diff --git a/webui/.gitignore b/webui/.gitignore index 4d29575..508eb96 100644 --- a/webui/.gitignore +++ b/webui/.gitignore @@ -9,7 +9,7 @@ /coverage # production -/build +build/ # misc .DS_Store diff --git a/webui/Dockerfile b/webui/Dockerfile new file mode 100644 index 0000000..78b6623 --- /dev/null +++ b/webui/Dockerfile @@ -0,0 +1,13 @@ +FROM node:13.12 + +ENV WEBUI_DIR /src/webui/ +ENV STATIC_DIR /src/static/ +RUN mkdir -p $WEBUI_DIR +RUN mkdir -p $STATIC_DIR + +COPY package.json $WEBUI_DIR/ + +WORKDIR $WEBUI_DIR +RUN npm install + +COPY . $WEBUI_DIR/ diff --git a/webui/package.json b/webui/package.json index 9f8353a..a49eb9a 100644 --- a/webui/package.json +++ b/webui/package.json @@ -1,15 +1,8 @@ { "name": "webui", - "version": "0.1.0", + "version": "0.0.0", "private": true, - "dependencies": { - "@testing-library/jest-dom": "^4.2.4", - "@testing-library/react": "^9.3.2", - "@testing-library/user-event": "^7.1.2", - "react": "^16.13.1", - "react-dom": "^16.13.1", - "react-scripts": "3.4.1" - }, + "homepage": "/dashboard", "scripts": { "start": "react-scripts start", "build": "react-scripts build", @@ -30,5 +23,35 @@ "last 1 firefox version", "last 1 safari version" ] + }, + "dependencies": { + "@material-ui/core": "^4.2.1", + "@material-ui/icons": "^4.2.1", + "@material-ui/styles": "^4.2.1", + "chart.js": "^2.8.0", + "clsx": "^1.0.4", + "history": "^4.9.0", + "moment": "^2.24.0", + "node-sass": "^4.12.0", + "prop-types": "^15.7.2", + "react": "^16.8.6", + "react-chartjs-2": "^2.7.6", + "react-dom": "^16.8.6", + "react-perfect-scrollbar": "^1.5.3", + "react-router-dom": "^5.0.1", + "react-scripts": "^3.0.1", + "recompose": "^0.30.0", + "underscore": "^1.9.1", + "uuid": "^3.3.2", + "validate.js": "^0.13.1" + }, + "devDependencies": { + "eslint": "^6.6.0", + "eslint-plugin-prettier": "^3.0.1", + "eslint-plugin-react": "^7.12.4", + "prettier": "^1.17.1", + "prettier-eslint": "^8.8.2", + "prettier-eslint-cli": "^4.7.1", + "typescript": "^3.5.1" } } diff --git a/webui/public/_redirects b/webui/public/_redirects new file mode 100644 index 0000000..50a4633 --- /dev/null +++ b/webui/public/_redirects @@ -0,0 +1 @@ +/* /index.html 200 \ No newline at end of file diff --git a/webui/public/favicon.ico b/webui/public/favicon.ico index bcd5dfd..56c61fb 100644 Binary files a/webui/public/favicon.ico and b/webui/public/favicon.ico differ diff --git a/webui/public/images/auth.jpg b/webui/public/images/auth.jpg new file mode 100644 index 0000000..6228b3b Binary files /dev/null and b/webui/public/images/auth.jpg differ diff --git a/webui/public/images/avatars/avatar_1.png b/webui/public/images/avatars/avatar_1.png new file mode 100644 index 0000000..c80c89c Binary files /dev/null and b/webui/public/images/avatars/avatar_1.png differ diff --git a/webui/public/images/avatars/avatar_10.png b/webui/public/images/avatars/avatar_10.png new file mode 100644 index 0000000..c6be181 Binary files /dev/null and b/webui/public/images/avatars/avatar_10.png differ diff --git a/webui/public/images/avatars/avatar_11.png b/webui/public/images/avatars/avatar_11.png new file mode 100644 index 0000000..9d46560 Binary files /dev/null and b/webui/public/images/avatars/avatar_11.png differ diff --git a/webui/public/images/avatars/avatar_2.png b/webui/public/images/avatars/avatar_2.png new file mode 100644 index 0000000..4edae21 Binary files /dev/null and b/webui/public/images/avatars/avatar_2.png differ diff --git a/webui/public/images/avatars/avatar_3.png b/webui/public/images/avatars/avatar_3.png new file mode 100644 index 0000000..72a678d Binary files /dev/null and b/webui/public/images/avatars/avatar_3.png differ diff --git a/webui/public/images/avatars/avatar_4.png b/webui/public/images/avatars/avatar_4.png new file mode 100644 index 0000000..cca2f61 Binary files /dev/null and b/webui/public/images/avatars/avatar_4.png differ diff --git a/webui/public/images/avatars/avatar_5.png b/webui/public/images/avatars/avatar_5.png new file mode 100644 index 0000000..4eedc23 Binary files /dev/null and b/webui/public/images/avatars/avatar_5.png differ diff --git a/webui/public/images/avatars/avatar_6.png b/webui/public/images/avatars/avatar_6.png new file mode 100644 index 0000000..67ebc46 Binary files /dev/null and b/webui/public/images/avatars/avatar_6.png differ diff --git a/webui/public/images/avatars/avatar_7.png b/webui/public/images/avatars/avatar_7.png new file mode 100644 index 0000000..b20692b Binary files /dev/null and b/webui/public/images/avatars/avatar_7.png differ diff --git a/webui/public/images/avatars/avatar_8.png b/webui/public/images/avatars/avatar_8.png new file mode 100644 index 0000000..b9cd86e Binary files /dev/null and b/webui/public/images/avatars/avatar_8.png differ diff --git a/webui/public/images/avatars/avatar_9.png b/webui/public/images/avatars/avatar_9.png new file mode 100644 index 0000000..a1d00ff Binary files /dev/null and b/webui/public/images/avatars/avatar_9.png differ diff --git a/webui/public/images/logos/logo--white.svg b/webui/public/images/logos/logo--white.svg new file mode 100755 index 0000000..1ab550f --- /dev/null +++ b/webui/public/images/logos/logo--white.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/webui/public/images/not_found.png b/webui/public/images/not_found.png new file mode 100644 index 0000000..a7160e8 Binary files /dev/null and b/webui/public/images/not_found.png differ diff --git a/webui/public/images/products/product_1.png b/webui/public/images/products/product_1.png new file mode 100644 index 0000000..27b5031 Binary files /dev/null and b/webui/public/images/products/product_1.png differ diff --git a/webui/public/images/products/product_2.png b/webui/public/images/products/product_2.png new file mode 100644 index 0000000..b4b103b Binary files /dev/null and b/webui/public/images/products/product_2.png differ diff --git a/webui/public/images/products/product_3.png b/webui/public/images/products/product_3.png new file mode 100644 index 0000000..5bfd2d9 Binary files /dev/null and b/webui/public/images/products/product_3.png differ diff --git a/webui/public/images/products/product_4.png b/webui/public/images/products/product_4.png new file mode 100644 index 0000000..142ec74 Binary files /dev/null and b/webui/public/images/products/product_4.png differ diff --git a/webui/public/images/products/product_5.png b/webui/public/images/products/product_5.png new file mode 100644 index 0000000..cf13db0 Binary files /dev/null and b/webui/public/images/products/product_5.png differ diff --git a/webui/public/images/products/product_6.png b/webui/public/images/products/product_6.png new file mode 100644 index 0000000..bd98092 Binary files /dev/null and b/webui/public/images/products/product_6.png differ diff --git a/webui/public/images/undraw_page_not_found_su7k.svg b/webui/public/images/undraw_page_not_found_su7k.svg new file mode 100644 index 0000000..9628680 --- /dev/null +++ b/webui/public/images/undraw_page_not_found_su7k.svg @@ -0,0 +1 @@ +page not found \ No newline at end of file diff --git a/webui/public/images/undraw_resume_folder_2_arse.svg b/webui/public/images/undraw_resume_folder_2_arse.svg new file mode 100644 index 0000000..389af57 --- /dev/null +++ b/webui/public/images/undraw_resume_folder_2_arse.svg @@ -0,0 +1 @@ +resume folder_2 \ No newline at end of file diff --git a/webui/public/index.html b/webui/public/index.html old mode 100644 new mode 100755 index aa069f2..c74f8f4 --- a/webui/public/index.html +++ b/webui/public/index.html @@ -1,43 +1,22 @@ - + - - - + - - + - - React App + + React Material Dashboard
- diff --git a/webui/public/logo192.png b/webui/public/logo192.png deleted file mode 100644 index fc44b0a..0000000 Binary files a/webui/public/logo192.png and /dev/null differ diff --git a/webui/public/logo512.png b/webui/public/logo512.png deleted file mode 100644 index a4e47a6..0000000 Binary files a/webui/public/logo512.png and /dev/null differ diff --git a/webui/public/manifest.json b/webui/public/manifest.json old mode 100644 new mode 100755 index 080d6c7..1f2f141 --- a/webui/public/manifest.json +++ b/webui/public/manifest.json @@ -6,16 +6,6 @@ "src": "favicon.ico", "sizes": "64x64 32x32 24x24 16x16", "type": "image/x-icon" - }, - { - "src": "logo192.png", - "type": "image/png", - "sizes": "192x192" - }, - { - "src": "logo512.png", - "type": "image/png", - "sizes": "512x512" } ], "start_url": ".", diff --git a/webui/public/robots.txt b/webui/public/robots.txt deleted file mode 100644 index e9e57dc..0000000 --- a/webui/public/robots.txt +++ /dev/null @@ -1,3 +0,0 @@ -# https://www.robotstxt.org/robotstxt.html -User-agent: * -Disallow: diff --git a/webui/readme.md b/webui/readme.md new file mode 100644 index 0000000..819c3f3 --- /dev/null +++ b/webui/readme.md @@ -0,0 +1,54 @@ +# LiveGo Web UI + +Access to Livego Web UI, ex: http://localhost:8090/dashboard + + +## How to build (for backend developer) + +Use the make file : + +```shell +make build # Generate Docker image +make generate-webui # Generate static contents in `livego/static/` folder. +``` + +## How to build (only for frontend developer) + +- prerequisite: [Node 12.11+](https://nodejs.org) [Npm](https://www.npmjs.com/) + +- Go to the directory `webui` + +- To install dependencies, execute the following commands: + + - `npm install` + +- Build static Web UI, execute the following command: + + - `npm run build` + +- Static contents are build in the directory `build` + +**Don't change manually the files in the directory `build`** + +- The build allow to: + - optimize all JavaScript + - optimize all CSS + - add vendor prefixes to CSS (cross-bowser support) + - add a hash in the file names to prevent browser cache problems + - all images will be optimized at build + - bundle JavaScript in one file + +## How to edit (only for frontend developer) + +**Don't change manually the files in the directory `build`** + +- Go to the directory `webui` +- Edit files in `webui/src` +- Run in development mode : + - `npm run dev` + +## Libraries + +- [Node](https://nodejs.org) +- [Npm](https://www.npmjs.com/) +- [React](https://reactjs.org/) \ No newline at end of file diff --git a/webui/src/App.css b/webui/src/App.css deleted file mode 100644 index 74b5e05..0000000 --- a/webui/src/App.css +++ /dev/null @@ -1,38 +0,0 @@ -.App { - text-align: center; -} - -.App-logo { - height: 40vmin; - pointer-events: none; -} - -@media (prefers-reduced-motion: no-preference) { - .App-logo { - animation: App-logo-spin infinite 20s linear; - } -} - -.App-header { - background-color: #282c34; - min-height: 100vh; - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; - font-size: calc(10px + 2vmin); - color: white; -} - -.App-link { - color: #61dafb; -} - -@keyframes App-logo-spin { - from { - transform: rotate(0deg); - } - to { - transform: rotate(360deg); - } -} diff --git a/webui/src/App.js b/webui/src/App.js index ce9cbd2..e9849d1 100644 --- a/webui/src/App.js +++ b/webui/src/App.js @@ -1,26 +1,36 @@ -import React from 'react'; -import logo from './logo.svg'; -import './App.css'; +import React, { Component } from 'react'; +import { Router } from 'react-router-dom'; +import { createBrowserHistory } from 'history'; +import { Chart } from 'react-chartjs-2'; +import { ThemeProvider } from '@material-ui/styles'; +import validate from 'validate.js'; -function App() { - return ( -
-
- logo -

- Edit src/App.js and save to reload. -

- - Learn React - -
-
- ); -} +import { chartjs } from './helpers'; +import theme from './theme'; +import 'react-perfect-scrollbar/dist/css/styles.css'; +import './assets/scss/index.scss'; +import validators from './common/validators'; +import Routes from './Routes'; + +const browserHistory = createBrowserHistory(); + +Chart.helpers.extend(Chart.elements.Rectangle.prototype, { + draw: chartjs.draw +}); -export default App; +validate.validators = { + ...validate.validators, + ...validators +}; + +export default class App extends Component { + render() { + return ( + + + + + + ); + } +} diff --git a/webui/src/App.test.js b/webui/src/App.test.js deleted file mode 100644 index 4db7ebc..0000000 --- a/webui/src/App.test.js +++ /dev/null @@ -1,9 +0,0 @@ -import React from 'react'; -import { render } from '@testing-library/react'; -import App from './App'; - -test('renders learn react link', () => { - const { getByText } = render(); - const linkElement = getByText(/learn react/i); - expect(linkElement).toBeInTheDocument(); -}); diff --git a/webui/src/Routes.js b/webui/src/Routes.js new file mode 100644 index 0000000..5f723f4 --- /dev/null +++ b/webui/src/Routes.js @@ -0,0 +1,93 @@ +import React from 'react'; +import { Switch, Redirect } from 'react-router-dom'; + +import { RouteWithLayout } from './components'; +import { Main as MainLayout, Minimal as MinimalLayout } from './layouts'; + +import { + Dashboard as DashboardView, + ProductList as ProductListView, + UserList as UserListView, + Typography as TypographyView, + Icons as IconsView, + Account as AccountView, + Settings as SettingsView, + SignUp as SignUpView, + SignIn as SignInView, + NotFound as NotFoundView +} from './views'; + +const Routes = () => { + return ( + + + + + + + + + + + + + + + ); +}; + +export default Routes; diff --git a/webui/src/assets/scss/index.scss b/webui/src/assets/scss/index.scss new file mode 100755 index 0000000..2bb2eba --- /dev/null +++ b/webui/src/assets/scss/index.scss @@ -0,0 +1,24 @@ +* { + box-sizing: border-box; + margin: 0; + padding: 0; +} + +html { + height: 100%; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +body { + background-color: #f4f6f8; + height: 100%; +} + +a { + text-decoration: none; +} + +#root { + height: 100%; +} diff --git a/webui/src/common/validators.js b/webui/src/common/validators.js new file mode 100644 index 0000000..128841b --- /dev/null +++ b/webui/src/common/validators.js @@ -0,0 +1,9 @@ +const checked = (value, options) => { + if (value !== true) { + return options.message || 'must be checked'; + } +}; + +export default { + checked +}; diff --git a/webui/src/components/RouteWithLayout/RouteWithLayout.js b/webui/src/components/RouteWithLayout/RouteWithLayout.js new file mode 100644 index 0000000..ea4f369 --- /dev/null +++ b/webui/src/components/RouteWithLayout/RouteWithLayout.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { Route } from 'react-router-dom'; +import PropTypes from 'prop-types'; + +const RouteWithLayout = props => { + const { layout: Layout, component: Component, ...rest } = props; + + return ( + ( + + + + )} + /> + ); +}; + +RouteWithLayout.propTypes = { + component: PropTypes.any.isRequired, + layout: PropTypes.any.isRequired, + path: PropTypes.string +}; + +export default RouteWithLayout; diff --git a/webui/src/components/RouteWithLayout/index.js b/webui/src/components/RouteWithLayout/index.js new file mode 100644 index 0000000..90f9b97 --- /dev/null +++ b/webui/src/components/RouteWithLayout/index.js @@ -0,0 +1 @@ +export { default } from './RouteWithLayout'; diff --git a/webui/src/components/SearchInput/SearchInput.js b/webui/src/components/SearchInput/SearchInput.js new file mode 100644 index 0000000..bc16cdd --- /dev/null +++ b/webui/src/components/SearchInput/SearchInput.js @@ -0,0 +1,56 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { makeStyles } from '@material-ui/styles'; +import { Paper, Input } from '@material-ui/core'; +import SearchIcon from '@material-ui/icons/Search'; + +const useStyles = makeStyles(theme => ({ + root: { + borderRadius: '4px', + alignItems: 'center', + padding: theme.spacing(1), + display: 'flex', + flexBasis: 420 + }, + icon: { + marginRight: theme.spacing(1), + color: theme.palette.text.secondary + }, + input: { + flexGrow: 1, + fontSize: '14px', + lineHeight: '16px', + letterSpacing: '-0.05px' + } +})); + +const SearchInput = props => { + const { className, onChange, style, ...rest } = props; + + const classes = useStyles(); + + return ( + + + + + ); +}; + +SearchInput.propTypes = { + className: PropTypes.string, + onChange: PropTypes.func, + style: PropTypes.object +}; + +export default SearchInput; diff --git a/webui/src/components/SearchInput/index.js b/webui/src/components/SearchInput/index.js new file mode 100644 index 0000000..7e9c713 --- /dev/null +++ b/webui/src/components/SearchInput/index.js @@ -0,0 +1 @@ +export { default } from './SearchInput'; diff --git a/webui/src/components/StatusBullet/StatusBullet.js b/webui/src/components/StatusBullet/StatusBullet.js new file mode 100644 index 0000000..4fde736 --- /dev/null +++ b/webui/src/components/StatusBullet/StatusBullet.js @@ -0,0 +1,83 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { makeStyles } from '@material-ui/styles'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'inline-block', + borderRadius: '50%', + flexGrow: 0, + flexShrink: 0 + }, + sm: { + height: theme.spacing(1), + width: theme.spacing(1) + }, + md: { + height: theme.spacing(2), + width: theme.spacing(2) + }, + lg: { + height: theme.spacing(3), + width: theme.spacing(3) + }, + neutral: { + backgroundColor: theme.palette.neutral + }, + primary: { + backgroundColor: theme.palette.primary.main + }, + info: { + backgroundColor: theme.palette.info.main + }, + warning: { + backgroundColor: theme.palette.warning.main + }, + danger: { + backgroundColor: theme.palette.error.main + }, + success: { + backgroundColor: theme.palette.success.main + } +})); + +const StatusBullet = props => { + const { className, size, color, ...rest } = props; + + const classes = useStyles(); + + return ( + + ); +}; + +StatusBullet.propTypes = { + className: PropTypes.string, + color: PropTypes.oneOf([ + 'neutral', + 'primary', + 'info', + 'success', + 'warning', + 'danger' + ]), + size: PropTypes.oneOf(['sm', 'md', 'lg']) +}; + +StatusBullet.defaultProps = { + size: 'md', + color: 'default' +}; + +export default StatusBullet; diff --git a/webui/src/components/StatusBullet/index.js b/webui/src/components/StatusBullet/index.js new file mode 100644 index 0000000..f23aa2b --- /dev/null +++ b/webui/src/components/StatusBullet/index.js @@ -0,0 +1 @@ +export { default } from './StatusBullet' \ No newline at end of file diff --git a/webui/src/components/index.js b/webui/src/components/index.js new file mode 100644 index 0000000..ccb77f4 --- /dev/null +++ b/webui/src/components/index.js @@ -0,0 +1,3 @@ +export { default as SearchInput } from './SearchInput'; +export { default as StatusBullet } from './StatusBullet'; +export { default as RouteWithLayout } from './RouteWithLayout'; diff --git a/webui/src/helpers/chartjs.js b/webui/src/helpers/chartjs.js new file mode 100644 index 0000000..55e5a64 --- /dev/null +++ b/webui/src/helpers/chartjs.js @@ -0,0 +1,192 @@ +// ChartJS extension rounded bar chart +// https://codepen.io/jedtrow/full/ygRYgo +function draw() { + const { ctx } = this._chart; + const vm = this._view; + let { borderWidth } = vm; + + let left; + let right; + let top; + let bottom; + let signX; + let signY; + let borderSkipped; + let radius; + + // If radius is less than 0 or is large enough to cause drawing errors a max + // radius is imposed. If cornerRadius is not defined set it to 0. + let { cornerRadius } = this._chart.config.options; + if (cornerRadius < 0) { + cornerRadius = 0; + } + + if (typeof cornerRadius === 'undefined') { + cornerRadius = 0; + } + + if (!vm.horizontal) { + // bar + left = vm.x - vm.width / 2; + right = vm.x + vm.width / 2; + top = vm.y; + bottom = vm.base; + signX = 1; + signY = bottom > top ? 1 : -1; + borderSkipped = vm.borderSkipped || 'bottom'; + } else { + // horizontal bar + left = vm.base; + right = vm.x; + top = vm.y - vm.height / 2; + bottom = vm.y + vm.height / 2; + signX = right > left ? 1 : -1; + signY = 1; + borderSkipped = vm.borderSkipped || 'left'; + } + + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (borderWidth) { + // borderWidth shold be less than bar width and bar height. + const barSize = Math.min(Math.abs(left - right), Math.abs(top - bottom)); + borderWidth = borderWidth > barSize ? barSize : borderWidth; + const halfStroke = borderWidth / 2; + // Adjust borderWidth when bar top position is near vm.base(zero). + const borderLeft = + left + (borderSkipped !== 'left' ? halfStroke * signX : 0); + const borderRight = + right + (borderSkipped !== 'right' ? -halfStroke * signX : 0); + const borderTop = top + (borderSkipped !== 'top' ? halfStroke * signY : 0); + const borderBottom = + bottom + (borderSkipped !== 'bottom' ? -halfStroke * signY : 0); + // not become a vertical line? + if (borderLeft !== borderRight) { + top = borderTop; + bottom = borderBottom; + } + // not become a horizontal line? + if (borderTop !== borderBottom) { + left = borderLeft; + right = borderRight; + } + } + + ctx.beginPath(); + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = borderWidth; + + // Corner points, from bottom-left to bottom-right clockwise + // | 1 2 | + // | 0 3 | + const corners = [[left, bottom], [left, top], [right, top], [right, bottom]]; + + // Find first (starting) corner with fallback to 'bottom' + const borders = ['bottom', 'left', 'top', 'right']; + let startCorner = borders.indexOf(borderSkipped, 0); + if (startCorner === -1) { + startCorner = 0; + } + + function cornerAt(index) { + return corners[(startCorner + index) % 4]; + } + + // Draw rectangle from 'startCorner' + let corner = cornerAt(0); + ctx.moveTo(corner[0], corner[1]); + + for (let i = 1; i < 4; i += 1) { + corner = cornerAt(i); + let nextCornerId = i + 1; + if (nextCornerId === 4) { + nextCornerId = 0; + } + + const width = corners[2][0] - corners[1][0]; + const height = corners[0][1] - corners[1][1]; + const x = corners[1][0]; + const y = corners[1][1]; + + radius = cornerRadius; + // Fix radius being too large + if (radius > Math.abs(height) / 2) { + radius = Math.floor(Math.abs(height) / 2); + } + if (radius > Math.abs(width) / 2) { + radius = Math.floor(Math.abs(width) / 2); + } + + if (height < 0) { + // Negative values in a standard bar chart + const xTl = x; + const xTr = x + width; + const yTl = y + height; + const yTr = y + height; + + const xBl = x; + const xBr = x + width; + const yBl = y; + const yBr = y; + + // Draw + ctx.moveTo(xBl + radius, yBl); + ctx.lineTo(xBr - radius, yBr); + ctx.quadraticCurveTo(xBr, yBr, xBr, yBr - radius); + ctx.lineTo(xTr, yTr + radius); + ctx.quadraticCurveTo(xTr, yTr, xTr - radius, yTr); + ctx.lineTo(xTl + radius, yTl); + ctx.quadraticCurveTo(xTl, yTl, xTl, yTl + radius); + ctx.lineTo(xBl, yBl - radius); + ctx.quadraticCurveTo(xBl, yBl, xBl + radius, yBl); + } else if (width < 0) { + // Negative values in a horizontal bar chart + const xTl = x + width; + const xTr = x; + const yTl = y; + const yTr = y; + + const xBl = x + width; + const xBr = x; + const yBl = y + height; + const yBr = y + height; + + // Draw + ctx.moveTo(xBl + radius, yBl); + ctx.lineTo(xBr - radius, yBr); + ctx.quadraticCurveTo(xBr, yBr, xBr, yBr - radius); + ctx.lineTo(xTr, yTr + radius); + ctx.quadraticCurveTo(xTr, yTr, xTr - radius, yTr); + ctx.lineTo(xTl + radius, yTl); + ctx.quadraticCurveTo(xTl, yTl, xTl, yTl + radius); + ctx.lineTo(xBl, yBl - radius); + ctx.quadraticCurveTo(xBl, yBl, xBl + radius, yBl); + } else { + // Positive Value + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo( + x + width, + y + height, + x + width - radius, + y + height + ); + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + } + } + + ctx.fill(); + if (borderWidth) { + ctx.stroke(); + } +} + +export default { + draw +}; diff --git a/webui/src/helpers/getInitials.js b/webui/src/helpers/getInitials.js new file mode 100644 index 0000000..beba787 --- /dev/null +++ b/webui/src/helpers/getInitials.js @@ -0,0 +1,7 @@ +export default (name = '') => + name + .replace(/\s+/, ' ') + .split(' ') + .slice(0, 2) + .map(v => v && v[0].toUpperCase()) + .join(''); diff --git a/webui/src/helpers/index.js b/webui/src/helpers/index.js new file mode 100644 index 0000000..f28708c --- /dev/null +++ b/webui/src/helpers/index.js @@ -0,0 +1,2 @@ +export { default as chartjs } from './chartjs'; +export { default as getInitials } from './getInitials'; diff --git a/webui/src/icons/Facebook/index.js b/webui/src/icons/Facebook/index.js new file mode 100644 index 0000000..d28f39c --- /dev/null +++ b/webui/src/icons/Facebook/index.js @@ -0,0 +1,12 @@ +import React from 'react'; + +// Material components +import { SvgIcon } from '@material-ui/core'; + +export default function Facebook(props) { + return ( + + + + ); +} diff --git a/webui/src/icons/Google/index.js b/webui/src/icons/Google/index.js new file mode 100644 index 0000000..6ada903 --- /dev/null +++ b/webui/src/icons/Google/index.js @@ -0,0 +1,12 @@ +import React from 'react'; + +// Material components +import { SvgIcon } from '@material-ui/core'; + +export default function Google(props) { + return ( + + + + ); +} diff --git a/webui/src/icons/index.js b/webui/src/icons/index.js new file mode 100644 index 0000000..9442fd1 --- /dev/null +++ b/webui/src/icons/index.js @@ -0,0 +1,2 @@ +export { default as Facebook } from './Facebook'; +export { default as Google } from './Google'; diff --git a/webui/src/index.css b/webui/src/index.css deleted file mode 100644 index ec2585e..0000000 --- a/webui/src/index.css +++ /dev/null @@ -1,13 +0,0 @@ -body { - margin: 0; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', - 'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue', - sans-serif; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; -} - -code { - font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New', - monospace; -} diff --git a/webui/src/index.js b/webui/src/index.js old mode 100644 new mode 100755 index f5185c1..c3e57d1 --- a/webui/src/index.js +++ b/webui/src/index.js @@ -1,17 +1,9 @@ import React from 'react'; import ReactDOM from 'react-dom'; -import './index.css'; -import App from './App'; + import * as serviceWorker from './serviceWorker'; +import App from './App'; -ReactDOM.render( - - - , - document.getElementById('root') -); +ReactDOM.render(, document.getElementById('root')); -// If you want your app to work offline and load faster, you can change -// unregister() to register() below. Note this comes with some pitfalls. -// Learn more about service workers: https://bit.ly/CRA-PWA serviceWorker.unregister(); diff --git a/webui/src/layouts/Main/Main.js b/webui/src/layouts/Main/Main.js new file mode 100644 index 0000000..6d41314 --- /dev/null +++ b/webui/src/layouts/Main/Main.js @@ -0,0 +1,71 @@ +import React, { useState } from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { makeStyles, useTheme } from '@material-ui/styles'; +import { useMediaQuery } from '@material-ui/core'; + +import { Sidebar, Topbar, Footer } from './components'; + +const useStyles = makeStyles(theme => ({ + root: { + paddingTop: 56, + height: '100%', + [theme.breakpoints.up('sm')]: { + paddingTop: 64 + } + }, + shiftContent: { + paddingLeft: 240 + }, + content: { + height: '100%' + } +})); + +const Main = props => { + const { children } = props; + + const classes = useStyles(); + const theme = useTheme(); + const isDesktop = useMediaQuery(theme.breakpoints.up('lg'), { + defaultMatches: true + }); + + const [openSidebar, setOpenSidebar] = useState(false); + + const handleSidebarOpen = () => { + setOpenSidebar(true); + }; + + const handleSidebarClose = () => { + setOpenSidebar(false); + }; + + const shouldOpenSidebar = isDesktop ? true : openSidebar; + + return ( +
+ + +
+ {children} +
+
+
+ ); +}; + +Main.propTypes = { + children: PropTypes.node +}; + +export default Main; diff --git a/webui/src/layouts/Main/components/Footer/Footer.js b/webui/src/layouts/Main/components/Footer/Footer.js new file mode 100644 index 0000000..e872edc --- /dev/null +++ b/webui/src/layouts/Main/components/Footer/Footer.js @@ -0,0 +1,46 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import { makeStyles } from '@material-ui/styles'; +import { Typography, Link } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(4) + } +})); + +const Footer = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( +
+ + ©{' '} + + Devias IO + + . 2019 + + + Created with love for the environment. By designers and developers who + love to work together in offices! + +
+ ); +}; + +Footer.propTypes = { + className: PropTypes.string +}; + +export default Footer; diff --git a/webui/src/layouts/Main/components/Footer/index.js b/webui/src/layouts/Main/components/Footer/index.js new file mode 100644 index 0000000..be92134 --- /dev/null +++ b/webui/src/layouts/Main/components/Footer/index.js @@ -0,0 +1 @@ +export { default } from './Footer'; diff --git a/webui/src/layouts/Main/components/Sidebar/Sidebar.js b/webui/src/layouts/Main/components/Sidebar/Sidebar.js new file mode 100644 index 0000000..c2394b5 --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/Sidebar.js @@ -0,0 +1,119 @@ +import React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { Divider, Drawer } from '@material-ui/core'; +import DashboardIcon from '@material-ui/icons/Dashboard'; +import PeopleIcon from '@material-ui/icons/People'; +import ShoppingBasketIcon from '@material-ui/icons/ShoppingBasket'; +import TextFieldsIcon from '@material-ui/icons/TextFields'; +import ImageIcon from '@material-ui/icons/Image'; +import AccountBoxIcon from '@material-ui/icons/AccountBox'; +import SettingsIcon from '@material-ui/icons/Settings'; +import LockOpenIcon from '@material-ui/icons/LockOpen'; + +import { Profile, SidebarNav, UpgradePlan } from './components'; + +const useStyles = makeStyles(theme => ({ + drawer: { + width: 240, + [theme.breakpoints.up('lg')]: { + marginTop: 64, + height: 'calc(100% - 64px)' + } + }, + root: { + backgroundColor: theme.palette.white, + display: 'flex', + flexDirection: 'column', + height: '100%', + padding: theme.spacing(2) + }, + divider: { + margin: theme.spacing(2, 0) + }, + nav: { + marginBottom: theme.spacing(2) + } +})); + +const Sidebar = props => { + const { open, variant, onClose, className, ...rest } = props; + + const classes = useStyles(); + + const pages = [ + { + title: 'Dashboard', + href: '/dashboard', + icon: + }, + { + title: 'Users', + href: '/users', + icon: + }, + { + title: 'Products', + href: '/products', + icon: + }, + { + title: 'Authentication', + href: '/sign-in', + icon: + }, + { + title: 'Typography', + href: '/typography', + icon: + }, + { + title: 'Icons', + href: '/icons', + icon: + }, + { + title: 'Account', + href: '/account', + icon: + }, + { + title: 'Settings', + href: '/settings', + icon: + } + ]; + + return ( + +
+ + + + +
+
+ ); +}; + +Sidebar.propTypes = { + className: PropTypes.string, + onClose: PropTypes.func, + open: PropTypes.bool.isRequired, + variant: PropTypes.string.isRequired +}; + +export default Sidebar; diff --git a/webui/src/layouts/Main/components/Sidebar/components/Profile/Profile.js b/webui/src/layouts/Main/components/Sidebar/components/Profile/Profile.js new file mode 100644 index 0000000..c1de16e --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/components/Profile/Profile.js @@ -0,0 +1,62 @@ +import React from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { Avatar, Typography } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: { + display: 'flex', + flexDirection: 'column', + alignItems: 'center', + minHeight: 'fit-content' + }, + avatar: { + width: 60, + height: 60 + }, + name: { + marginTop: theme.spacing(1) + } +})); + +const Profile = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + const user = { + name: 'Shen Zhi', + avatar: '/images/avatars/avatar_11.png', + bio: 'Brain Director' + }; + + return ( +
+ + + {user.name} + + {user.bio} +
+ ); +}; + +Profile.propTypes = { + className: PropTypes.string +}; + +export default Profile; diff --git a/webui/src/layouts/Main/components/Sidebar/components/Profile/index.js b/webui/src/layouts/Main/components/Sidebar/components/Profile/index.js new file mode 100644 index 0000000..23358a2 --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/components/Profile/index.js @@ -0,0 +1 @@ +export { default } from './Profile'; diff --git a/webui/src/layouts/Main/components/Sidebar/components/SidebarNav/SidebarNav.js b/webui/src/layouts/Main/components/Sidebar/components/SidebarNav/SidebarNav.js new file mode 100644 index 0000000..e33703f --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/components/SidebarNav/SidebarNav.js @@ -0,0 +1,88 @@ +/* eslint-disable react/no-multi-comp */ +/* eslint-disable react/display-name */ +import React, { forwardRef } from 'react'; +import { NavLink as RouterLink } from 'react-router-dom'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { List, ListItem, Button, colors } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: {}, + item: { + display: 'flex', + paddingTop: 0, + paddingBottom: 0 + }, + button: { + color: colors.blueGrey[800], + padding: '10px 8px', + justifyContent: 'flex-start', + textTransform: 'none', + letterSpacing: 0, + width: '100%', + fontWeight: theme.typography.fontWeightMedium + }, + icon: { + color: theme.palette.icon, + width: 24, + height: 24, + display: 'flex', + alignItems: 'center', + marginRight: theme.spacing(1) + }, + active: { + color: theme.palette.primary.main, + fontWeight: theme.typography.fontWeightMedium, + '& $icon': { + color: theme.palette.primary.main + } + } +})); + +const CustomRouterLink = forwardRef((props, ref) => ( +
+ +
+)); + +const SidebarNav = props => { + const { pages, className, ...rest } = props; + + const classes = useStyles(); + + return ( + + {pages.map(page => ( + + + + ))} + + ); +}; + +SidebarNav.propTypes = { + className: PropTypes.string, + pages: PropTypes.array.isRequired +}; + +export default SidebarNav; diff --git a/webui/src/layouts/Main/components/Sidebar/components/SidebarNav/index.js b/webui/src/layouts/Main/components/Sidebar/components/SidebarNav/index.js new file mode 100644 index 0000000..fd7a5db --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/components/SidebarNav/index.js @@ -0,0 +1 @@ +export { default } from './SidebarNav'; diff --git a/webui/src/layouts/Main/components/Sidebar/components/UpgradePlan/UpgradePlan.js b/webui/src/layouts/Main/components/Sidebar/components/UpgradePlan/UpgradePlan.js new file mode 100644 index 0000000..b5c90ea --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/components/UpgradePlan/UpgradePlan.js @@ -0,0 +1,79 @@ +import React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { Typography, Button, colors } from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: { + backgroundColor: colors.grey[50] + }, + media: { + paddingTop: theme.spacing(2), + height: 80, + textAlign: 'center', + '& > img': { + height: '100%', + width: 'auto' + } + }, + content: { + padding: theme.spacing(1, 2) + }, + actions: { + padding: theme.spacing(1, 2), + display: 'flex', + justifyContent: 'center' + } +})); + +const UpgradePlan = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( +
+
+ Upgrade to PRO +
+
+ + Upgrade to PRO + + + Upgrade to Devias Kit PRO and get even more components + +
+
+ +
+
+ ); +}; + +UpgradePlan.propTypes = { + className: PropTypes.string +}; + +export default UpgradePlan; diff --git a/webui/src/layouts/Main/components/Sidebar/components/UpgradePlan/index.js b/webui/src/layouts/Main/components/Sidebar/components/UpgradePlan/index.js new file mode 100644 index 0000000..91bd301 --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/components/UpgradePlan/index.js @@ -0,0 +1 @@ +export { default } from './UpgradePlan'; diff --git a/webui/src/layouts/Main/components/Sidebar/components/index.js b/webui/src/layouts/Main/components/Sidebar/components/index.js new file mode 100644 index 0000000..679c17e --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/components/index.js @@ -0,0 +1,3 @@ +export { default as Profile } from './Profile'; +export { default as SidebarNav } from './SidebarNav'; +export { default as UpgradePlan } from './UpgradePlan'; diff --git a/webui/src/layouts/Main/components/Sidebar/index.js b/webui/src/layouts/Main/components/Sidebar/index.js new file mode 100644 index 0000000..e842a85 --- /dev/null +++ b/webui/src/layouts/Main/components/Sidebar/index.js @@ -0,0 +1 @@ +export { default } from './Sidebar'; diff --git a/webui/src/layouts/Main/components/Topbar/Topbar.js b/webui/src/layouts/Main/components/Topbar/Topbar.js new file mode 100644 index 0000000..dff9168 --- /dev/null +++ b/webui/src/layouts/Main/components/Topbar/Topbar.js @@ -0,0 +1,78 @@ +import React, { useState } from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { AppBar, Toolbar, Badge, Hidden, IconButton } from '@material-ui/core'; +import MenuIcon from '@material-ui/icons/Menu'; +import NotificationsIcon from '@material-ui/icons/NotificationsOutlined'; +import InputIcon from '@material-ui/icons/Input'; + +const useStyles = makeStyles(theme => ({ + root: { + boxShadow: 'none' + }, + flexGrow: { + flexGrow: 1 + }, + signOutButton: { + marginLeft: theme.spacing(1) + } +})); + +const Topbar = props => { + const { className, onSidebarOpen, ...rest } = props; + + const classes = useStyles(); + + const [notifications] = useState([]); + + return ( + + + + Logo + +
+ + + + + + + + + + + + + + + + + + ); +}; + +Topbar.propTypes = { + className: PropTypes.string, + onSidebarOpen: PropTypes.func +}; + +export default Topbar; diff --git a/webui/src/layouts/Main/components/Topbar/index.js b/webui/src/layouts/Main/components/Topbar/index.js new file mode 100644 index 0000000..9115687 --- /dev/null +++ b/webui/src/layouts/Main/components/Topbar/index.js @@ -0,0 +1 @@ +export { default } from './Topbar'; diff --git a/webui/src/layouts/Main/components/index.js b/webui/src/layouts/Main/components/index.js new file mode 100644 index 0000000..17441e4 --- /dev/null +++ b/webui/src/layouts/Main/components/index.js @@ -0,0 +1,3 @@ +export { default as Footer } from './Footer'; +export { default as Sidebar } from './Sidebar'; +export { default as Topbar } from './Topbar'; diff --git a/webui/src/layouts/Main/index.js b/webui/src/layouts/Main/index.js new file mode 100644 index 0000000..47dec62 --- /dev/null +++ b/webui/src/layouts/Main/index.js @@ -0,0 +1 @@ +export { default } from './Main'; diff --git a/webui/src/layouts/Minimal/Minimal.js b/webui/src/layouts/Minimal/Minimal.js new file mode 100644 index 0000000..d2f0e27 --- /dev/null +++ b/webui/src/layouts/Minimal/Minimal.js @@ -0,0 +1,35 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; + +import { Topbar } from './components'; + +const useStyles = makeStyles(() => ({ + root: { + paddingTop: 64, + height: '100%' + }, + content: { + height: '100%' + } +})); + +const Minimal = props => { + const { children } = props; + + const classes = useStyles(); + + return ( +
+ +
{children}
+
+ ); +}; + +Minimal.propTypes = { + children: PropTypes.node, + className: PropTypes.string +}; + +export default Minimal; diff --git a/webui/src/layouts/Minimal/components/Topbar/Topbar.js b/webui/src/layouts/Minimal/components/Topbar/Topbar.js new file mode 100644 index 0000000..8757144 --- /dev/null +++ b/webui/src/layouts/Minimal/components/Topbar/Topbar.js @@ -0,0 +1,42 @@ +import React from 'react'; +import { Link as RouterLink } from 'react-router-dom'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { AppBar, Toolbar } from '@material-ui/core'; + +const useStyles = makeStyles(() => ({ + root: { + boxShadow: 'none' + } +})); + +const Topbar = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( + + + + Logo + + + + ); +}; + +Topbar.propTypes = { + className: PropTypes.string +}; + +export default Topbar; diff --git a/webui/src/layouts/Minimal/components/Topbar/index.js b/webui/src/layouts/Minimal/components/Topbar/index.js new file mode 100644 index 0000000..9115687 --- /dev/null +++ b/webui/src/layouts/Minimal/components/Topbar/index.js @@ -0,0 +1 @@ +export { default } from './Topbar'; diff --git a/webui/src/layouts/Minimal/components/index.js b/webui/src/layouts/Minimal/components/index.js new file mode 100644 index 0000000..488bb67 --- /dev/null +++ b/webui/src/layouts/Minimal/components/index.js @@ -0,0 +1 @@ +export { default as Topbar } from './Topbar'; diff --git a/webui/src/layouts/Minimal/index.js b/webui/src/layouts/Minimal/index.js new file mode 100644 index 0000000..3f140a2 --- /dev/null +++ b/webui/src/layouts/Minimal/index.js @@ -0,0 +1 @@ +export { default } from './Minimal'; diff --git a/webui/src/layouts/index.js b/webui/src/layouts/index.js new file mode 100644 index 0000000..39ad3b6 --- /dev/null +++ b/webui/src/layouts/index.js @@ -0,0 +1,2 @@ +export { default as Main } from './Main'; +export { default as Minimal } from './Minimal'; diff --git a/webui/src/logo.svg b/webui/src/logo.svg deleted file mode 100644 index 6b60c10..0000000 --- a/webui/src/logo.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/webui/src/serviceWorker.js b/webui/src/serviceWorker.js old mode 100644 new mode 100755 index b04b771..2283ff9 --- a/webui/src/serviceWorker.js +++ b/webui/src/serviceWorker.js @@ -8,13 +8,13 @@ // resources are updated in the background. // To learn more about the benefits of this model and instructions on how to -// opt-in, read https://bit.ly/CRA-PWA +// opt-in, read http://bit.ly/CRA-PWA const isLocalhost = Boolean( window.location.hostname === 'localhost' || // [::1] is the IPv6 localhost address. window.location.hostname === '[::1]' || - // 127.0.0.0/8 are considered localhost for IPv4. + // 127.0.0.1/8 is considered localhost for IPv4. window.location.hostname.match( /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ ) @@ -43,7 +43,7 @@ export function register(config) { navigator.serviceWorker.ready.then(() => { console.log( 'This web app is being served cache-first by a service ' + - 'worker. To learn more, visit https://bit.ly/CRA-PWA' + 'worker. To learn more, visit http://bit.ly/CRA-PWA' ); }); } else { @@ -71,7 +71,7 @@ function registerValidSW(swUrl, config) { // content until all client tabs are closed. console.log( 'New content is available and will be used when all ' + - 'tabs for this page are closed. See https://bit.ly/CRA-PWA.' + 'tabs for this page are closed. See http://bit.ly/CRA-PWA.' ); // Execute callback @@ -100,9 +100,7 @@ function registerValidSW(swUrl, config) { function checkValidServiceWorker(swUrl, config) { // Check if the service worker can be found. If it can't reload the page. - fetch(swUrl, { - headers: { 'Service-Worker': 'script' }, - }) + fetch(swUrl) .then(response => { // Ensure service worker exists, and that we really are getting a JS file. const contentType = response.headers.get('content-type'); @@ -130,12 +128,8 @@ function checkValidServiceWorker(swUrl, config) { export function unregister() { if ('serviceWorker' in navigator) { - navigator.serviceWorker.ready - .then(registration => { - registration.unregister(); - }) - .catch(error => { - console.error(error.message); - }); + navigator.serviceWorker.ready.then(registration => { + registration.unregister(); + }); } } diff --git a/webui/src/setupTests.js b/webui/src/setupTests.js deleted file mode 100644 index 74b1a27..0000000 --- a/webui/src/setupTests.js +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import '@testing-library/jest-dom/extend-expect'; diff --git a/webui/src/theme/index.js b/webui/src/theme/index.js new file mode 100644 index 0000000..c359d09 --- /dev/null +++ b/webui/src/theme/index.js @@ -0,0 +1,17 @@ +import { createMuiTheme } from '@material-ui/core'; + +import palette from './palette'; +import typography from './typography'; +import overrides from './overrides'; + +const theme = createMuiTheme({ + palette, + typography, + overrides, + zIndex: { + appBar: 1200, + drawer: 1100 + } +}); + +export default theme; diff --git a/webui/src/theme/overrides/MuiButton.js b/webui/src/theme/overrides/MuiButton.js new file mode 100644 index 0000000..646f5a4 --- /dev/null +++ b/webui/src/theme/overrides/MuiButton.js @@ -0,0 +1,7 @@ +export default { + contained: { + boxShadow: + '0 1px 1px 0 rgba(0,0,0,0.14), 0 2px 1px -1px rgba(0,0,0,0.12), 0 1px 3px 0 rgba(0,0,0,0.20)', + backgroundColor: '#FFFFFF' + } +}; diff --git a/webui/src/theme/overrides/MuiIconButton.js b/webui/src/theme/overrides/MuiIconButton.js new file mode 100644 index 0000000..1c1b5e9 --- /dev/null +++ b/webui/src/theme/overrides/MuiIconButton.js @@ -0,0 +1,10 @@ +import palette from '../palette'; + +export default { + root: { + color: palette.icon, + '&:hover': { + backgroundColor: 'rgba(0, 0, 0, 0.03)' + } + } +}; diff --git a/webui/src/theme/overrides/MuiPaper.js b/webui/src/theme/overrides/MuiPaper.js new file mode 100644 index 0000000..654b2b0 --- /dev/null +++ b/webui/src/theme/overrides/MuiPaper.js @@ -0,0 +1,5 @@ +export default { + elevation1: { + boxShadow: '0 0 0 1px rgba(63,63,68,0.05), 0 1px 3px 0 rgba(63,63,68,0.15)' + } +}; diff --git a/webui/src/theme/overrides/MuiTableCell.js b/webui/src/theme/overrides/MuiTableCell.js new file mode 100644 index 0000000..89e36e3 --- /dev/null +++ b/webui/src/theme/overrides/MuiTableCell.js @@ -0,0 +1,9 @@ +import palette from '../palette'; +import typography from '../typography'; + +export default { + root: { + ...typography.body1, + borderBottom: `1px solid ${palette.divider}` + } +}; diff --git a/webui/src/theme/overrides/MuiTableHead.js b/webui/src/theme/overrides/MuiTableHead.js new file mode 100644 index 0000000..bd5e5ab --- /dev/null +++ b/webui/src/theme/overrides/MuiTableHead.js @@ -0,0 +1,7 @@ +import { colors } from '@material-ui/core'; + +export default { + root: { + backgroundColor: colors.grey[50] + } +}; diff --git a/webui/src/theme/overrides/MuiTableRow.js b/webui/src/theme/overrides/MuiTableRow.js new file mode 100644 index 0000000..02a7db2 --- /dev/null +++ b/webui/src/theme/overrides/MuiTableRow.js @@ -0,0 +1,14 @@ +import palette from '../palette'; + +export default { + root: { + '&$selected': { + backgroundColor: palette.background.default + }, + '&$hover': { + '&:hover': { + backgroundColor: palette.background.default + } + } + } +}; diff --git a/webui/src/theme/overrides/MuiTypography.js b/webui/src/theme/overrides/MuiTypography.js new file mode 100644 index 0000000..a2e9a98 --- /dev/null +++ b/webui/src/theme/overrides/MuiTypography.js @@ -0,0 +1,5 @@ +export default { + gutterBottom: { + marginBottom: 8 + } +}; diff --git a/webui/src/theme/overrides/index.js b/webui/src/theme/overrides/index.js new file mode 100644 index 0000000..596e324 --- /dev/null +++ b/webui/src/theme/overrides/index.js @@ -0,0 +1,15 @@ +import MuiButton from './MuiButton'; +import MuiIconButton from './MuiIconButton'; +import MuiPaper from './MuiPaper'; +import MuiTableCell from './MuiTableCell'; +import MuiTableHead from './MuiTableHead'; +import MuiTypography from './MuiTypography'; + +export default { + MuiButton, + MuiIconButton, + MuiPaper, + MuiTableCell, + MuiTableHead, + MuiTypography +}; diff --git a/webui/src/theme/palette.js b/webui/src/theme/palette.js new file mode 100644 index 0000000..f703f4f --- /dev/null +++ b/webui/src/theme/palette.js @@ -0,0 +1,56 @@ +import { colors } from '@material-ui/core'; + +const white = '#FFFFFF'; +const black = '#000000'; + +export default { + black, + white, + primary: { + contrastText: white, + dark: colors.indigo[900], + main: colors.indigo[500], + light: colors.indigo[100] + }, + secondary: { + contrastText: white, + dark: colors.blue[900], + main: colors.blue['A400'], + light: colors.blue['A400'] + }, + success: { + contrastText: white, + dark: colors.green[900], + main: colors.green[600], + light: colors.green[400] + }, + info: { + contrastText: white, + dark: colors.blue[900], + main: colors.blue[600], + light: colors.blue[400] + }, + warning: { + contrastText: white, + dark: colors.orange[900], + main: colors.orange[600], + light: colors.orange[400] + }, + error: { + contrastText: white, + dark: colors.red[900], + main: colors.red[600], + light: colors.red[400] + }, + text: { + primary: colors.blueGrey[900], + secondary: colors.blueGrey[600], + link: colors.blue[600] + }, + background: { + default: '#F4F6F8', + paper: white + }, + icon: colors.blueGrey[600], + divider: colors.grey[200] +}; diff --git a/webui/src/theme/typography.js b/webui/src/theme/typography.js new file mode 100644 index 0000000..3d88dc1 --- /dev/null +++ b/webui/src/theme/typography.js @@ -0,0 +1,89 @@ +import palette from './palette'; + +export default { + h1: { + color: palette.text.primary, + fontWeight: 500, + fontSize: '35px', + letterSpacing: '-0.24px', + lineHeight: '40px' + }, + h2: { + color: palette.text.primary, + fontWeight: 500, + fontSize: '29px', + letterSpacing: '-0.24px', + lineHeight: '32px' + }, + h3: { + color: palette.text.primary, + fontWeight: 500, + fontSize: '24px', + letterSpacing: '-0.06px', + lineHeight: '28px' + }, + h4: { + color: palette.text.primary, + fontWeight: 500, + fontSize: '20px', + letterSpacing: '-0.06px', + lineHeight: '24px' + }, + h5: { + color: palette.text.primary, + fontWeight: 500, + fontSize: '16px', + letterSpacing: '-0.05px', + lineHeight: '20px' + }, + h6: { + color: palette.text.primary, + fontWeight: 500, + fontSize: '14px', + letterSpacing: '-0.05px', + lineHeight: '20px' + }, + subtitle1: { + color: palette.text.primary, + fontSize: '16px', + letterSpacing: '-0.05px', + lineHeight: '25px' + }, + subtitle2: { + color: palette.text.secondary, + fontWeight: 400, + fontSize: '14px', + letterSpacing: '-0.05px', + lineHeight: '21px' + }, + body1: { + color: palette.text.primary, + fontSize: '14px', + letterSpacing: '-0.05px', + lineHeight: '21px' + }, + body2: { + color: palette.text.secondary, + fontSize: '12px', + letterSpacing: '-0.04px', + lineHeight: '18px' + }, + button: { + color: palette.text.primary, + fontSize: '14px' + }, + caption: { + color: palette.text.secondary, + fontSize: '11px', + letterSpacing: '0.33px', + lineHeight: '13px' + }, + overline: { + color: palette.text.secondary, + fontSize: '11px', + fontWeight: 500, + letterSpacing: '0.33px', + lineHeight: '13px', + textTransform: 'uppercase' + } +}; diff --git a/webui/src/views/Account/Account.js b/webui/src/views/Account/Account.js new file mode 100644 index 0000000..d3a07d9 --- /dev/null +++ b/webui/src/views/Account/Account.js @@ -0,0 +1,45 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/styles'; +import { Grid } from '@material-ui/core'; + +import { AccountProfile, AccountDetails } from './components'; + +const useStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(4) + } +})); + +const Account = () => { + const classes = useStyles(); + + return ( +
+ + + + + + + + +
+ ); +}; + +export default Account; diff --git a/webui/src/views/Account/components/AccountDetails/AccountDetails.js b/webui/src/views/Account/components/AccountDetails/AccountDetails.js new file mode 100644 index 0000000..2e2c713 --- /dev/null +++ b/webui/src/views/Account/components/AccountDetails/AccountDetails.js @@ -0,0 +1,204 @@ +import React, { useState } from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { + Card, + CardHeader, + CardContent, + CardActions, + Divider, + Grid, + Button, + TextField +} from '@material-ui/core'; + +const useStyles = makeStyles(() => ({ + root: {} +})); + +const AccountDetails = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + const [values, setValues] = useState({ + firstName: 'Shen', + lastName: 'Zhi', + email: 'shen.zhi@devias.io', + phone: '', + state: 'Alabama', + country: 'USA' + }); + + const handleChange = event => { + setValues({ + ...values, + [event.target.name]: event.target.value + }); + }; + + const states = [ + { + value: 'alabama', + label: 'Alabama' + }, + { + value: 'new-york', + label: 'New York' + }, + { + value: 'san-francisco', + label: 'San Francisco' + } + ]; + + return ( + +
+ + + + + + + + + + + + + + + + + + + {states.map(option => ( + + ))} + + + + + + + + + + + + +
+ ); +}; + +AccountDetails.propTypes = { + className: PropTypes.string +}; + +export default AccountDetails; diff --git a/webui/src/views/Account/components/AccountDetails/index.js b/webui/src/views/Account/components/AccountDetails/index.js new file mode 100644 index 0000000..9d75950 --- /dev/null +++ b/webui/src/views/Account/components/AccountDetails/index.js @@ -0,0 +1 @@ +export { default } from './AccountDetails'; diff --git a/webui/src/views/Account/components/AccountProfile/AccountProfile.js b/webui/src/views/Account/components/AccountProfile/AccountProfile.js new file mode 100644 index 0000000..738da9a --- /dev/null +++ b/webui/src/views/Account/components/AccountProfile/AccountProfile.js @@ -0,0 +1,111 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import clsx from 'clsx'; +import moment from 'moment'; +import { makeStyles } from '@material-ui/styles'; +import { + Card, + CardActions, + CardContent, + Avatar, + Typography, + Divider, + Button, + LinearProgress +} from '@material-ui/core'; + +const useStyles = makeStyles(theme => ({ + root: {}, + details: { + display: 'flex' + }, + avatar: { + marginLeft: 'auto', + height: 110, + width: 100, + flexShrink: 0, + flexGrow: 0 + }, + progress: { + marginTop: theme.spacing(2) + }, + uploadButton: { + marginRight: theme.spacing(2) + } +})); + +const AccountProfile = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + const user = { + name: 'Shen Zhi', + city: 'Los Angeles', + country: 'USA', + timezone: 'GTM-7', + avatar: '/images/avatars/avatar_11.png' + }; + + return ( + + +
+
+ + John Doe + + + {user.city}, {user.country} + + + {moment().format('hh:mm A')} ({user.timezone}) + +
+ +
+
+ Profile Completeness: 70% + +
+
+ + + + + +
+ ); +}; + +AccountProfile.propTypes = { + className: PropTypes.string +}; + +export default AccountProfile; diff --git a/webui/src/views/Account/components/AccountProfile/index.js b/webui/src/views/Account/components/AccountProfile/index.js new file mode 100644 index 0000000..1b70be5 --- /dev/null +++ b/webui/src/views/Account/components/AccountProfile/index.js @@ -0,0 +1 @@ +export { default } from './AccountProfile'; diff --git a/webui/src/views/Account/components/index.js b/webui/src/views/Account/components/index.js new file mode 100644 index 0000000..a3bb00a --- /dev/null +++ b/webui/src/views/Account/components/index.js @@ -0,0 +1,2 @@ +export { default as AccountDetails } from './AccountDetails'; +export { default as AccountProfile } from './AccountProfile'; diff --git a/webui/src/views/Account/index.js b/webui/src/views/Account/index.js new file mode 100644 index 0000000..ce6b24c --- /dev/null +++ b/webui/src/views/Account/index.js @@ -0,0 +1 @@ +export { default } from './Account'; diff --git a/webui/src/views/Dashboard/Dashboard.js b/webui/src/views/Dashboard/Dashboard.js new file mode 100644 index 0000000..0b705f6 --- /dev/null +++ b/webui/src/views/Dashboard/Dashboard.js @@ -0,0 +1,108 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/styles'; +import { Grid } from '@material-ui/core'; + +import { + Budget, + TotalUsers, + TasksProgress, + TotalProfit, + LatestSales, + UsersByDevice, + LatestProducts, + LatestOrders +} from './components'; + +const useStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(4) + } +})); + +const Dashboard = () => { + const classes = useStyles(); + + return ( +
+ + + + + + + + + + + + + + + + + + + + + + + + + + +
+ ); +}; + +export default Dashboard; diff --git a/webui/src/views/Dashboard/components/Budget/Budget.js b/webui/src/views/Dashboard/components/Budget/Budget.js new file mode 100644 index 0000000..431f5e1 --- /dev/null +++ b/webui/src/views/Dashboard/components/Budget/Budget.js @@ -0,0 +1,99 @@ +import React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { Card, CardContent, Grid, Typography, Avatar } from '@material-ui/core'; +import ArrowDownwardIcon from '@material-ui/icons/ArrowDownward'; +import MoneyIcon from '@material-ui/icons/Money'; + +const useStyles = makeStyles(theme => ({ + root: { + height: '100%' + }, + content: { + alignItems: 'center', + display: 'flex' + }, + title: { + fontWeight: 700 + }, + avatar: { + backgroundColor: theme.palette.error.main, + height: 56, + width: 56 + }, + icon: { + height: 32, + width: 32 + }, + difference: { + marginTop: theme.spacing(2), + display: 'flex', + alignItems: 'center' + }, + differenceIcon: { + color: theme.palette.error.dark + }, + differenceValue: { + color: theme.palette.error.dark, + marginRight: theme.spacing(1) + } +})); + +const Budget = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( + + + + + + BUDGET + + $24,000 + + + + + + + +
+ + + 12% + + + Since last month + +
+
+
+ ); +}; + +Budget.propTypes = { + className: PropTypes.string +}; + +export default Budget; diff --git a/webui/src/views/Dashboard/components/Budget/index.js b/webui/src/views/Dashboard/components/Budget/index.js new file mode 100644 index 0000000..b414a90 --- /dev/null +++ b/webui/src/views/Dashboard/components/Budget/index.js @@ -0,0 +1 @@ +export { default } from './Budget'; diff --git a/webui/src/views/Dashboard/components/LatestOrders/LatestOrders.js b/webui/src/views/Dashboard/components/LatestOrders/LatestOrders.js new file mode 100644 index 0000000..82a61c0 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestOrders/LatestOrders.js @@ -0,0 +1,148 @@ +import React, { useState } from 'react'; +import clsx from 'clsx'; +import moment from 'moment'; +import PerfectScrollbar from 'react-perfect-scrollbar'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { + Card, + CardActions, + CardHeader, + CardContent, + Button, + Divider, + Table, + TableBody, + TableCell, + TableHead, + TableRow, + Tooltip, + TableSortLabel +} from '@material-ui/core'; +import ArrowRightIcon from '@material-ui/icons/ArrowRight'; + +import mockData from './data'; +import {StatusBullet} from '../../../../components'; + +const useStyles = makeStyles(theme => ({ + root: {}, + content: { + padding: 0 + }, + inner: { + minWidth: 800 + }, + statusContainer: { + display: 'flex', + alignItems: 'center' + }, + status: { + marginRight: theme.spacing(1) + }, + actions: { + justifyContent: 'flex-end' + } +})); + +const statusColors = { + delivered: 'success', + pending: 'info', + refunded: 'danger' +}; + +const LatestOrders = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + const [orders] = useState(mockData); + + return ( + + + New entry + + } + title="Latest Orders" + /> + + + +
+ + + + Order Ref + Customer + + + + Date + + + + Status + + + + {orders.map(order => ( + + {order.ref} + {order.customer.name} + + {moment(order.createdAt).format('DD/MM/YYYY')} + + +
+ + {order.status} +
+
+
+ ))} +
+
+
+
+
+ + + + +
+ ); +}; + +LatestOrders.propTypes = { + className: PropTypes.string +}; + +export default LatestOrders; diff --git a/webui/src/views/Dashboard/components/LatestOrders/data.js b/webui/src/views/Dashboard/components/LatestOrders/data.js new file mode 100644 index 0000000..bbf5879 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestOrders/data.js @@ -0,0 +1,64 @@ +import uuid from 'uuid/v1'; + +export default [ + { + id: uuid(), + ref: 'CDD1049', + amount: 30.5, + customer: { + name: 'Ekaterina Tankova' + }, + createdAt: 1555016400000, + status: 'pending' + }, + { + id: uuid(), + ref: 'CDD1048', + amount: 25.1, + customer: { + name: 'Cao Yu' + }, + createdAt: 1555016400000, + status: 'delivered' + }, + { + id: uuid(), + ref: 'CDD1047', + amount: 10.99, + customer: { + name: 'Alexa Richardson' + }, + createdAt: 1554930000000, + status: 'refunded' + }, + { + id: uuid(), + ref: 'CDD1046', + amount: 96.43, + customer: { + name: 'Anje Keizer' + }, + createdAt: 1554757200000, + status: 'pending' + }, + { + id: uuid(), + ref: 'CDD1045', + amount: 32.54, + customer: { + name: 'Clarke Gillebert' + }, + createdAt: 1554670800000, + status: 'delivered' + }, + { + id: uuid(), + ref: 'CDD1044', + amount: 16.76, + customer: { + name: 'Adam Denisov' + }, + createdAt: 1554670800000, + status: 'delivered' + } +]; diff --git a/webui/src/views/Dashboard/components/LatestOrders/index.js b/webui/src/views/Dashboard/components/LatestOrders/index.js new file mode 100644 index 0000000..d806aca --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestOrders/index.js @@ -0,0 +1 @@ +export { default } from './LatestOrders'; diff --git a/webui/src/views/Dashboard/components/LatestProducts/LatestProducts.js b/webui/src/views/Dashboard/components/LatestProducts/LatestProducts.js new file mode 100644 index 0000000..b3525f0 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestProducts/LatestProducts.js @@ -0,0 +1,102 @@ +import React, { useState } from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { + Card, + CardHeader, + CardContent, + CardActions, + Button, + Divider, + List, + ListItem, + ListItemAvatar, + ListItemText, + IconButton +} from '@material-ui/core'; +import ArrowRightIcon from '@material-ui/icons/ArrowRight'; +import MoreVertIcon from '@material-ui/icons/MoreVert'; + +import mockData from './data'; + +const useStyles = makeStyles(() => ({ + root: { + height: '100%' + }, + content: { + padding: 0 + }, + image: { + height: 48, + width: 48 + }, + actions: { + justifyContent: 'flex-end' + } +})); + +const LatestProducts = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + const [products] = useState(mockData); + + return ( + + + + + + {products.map((product, i) => ( + + + Product + + + + + + + ))} + + + + + + + + ); +}; + +LatestProducts.propTypes = { + className: PropTypes.string +}; + +export default LatestProducts; diff --git a/webui/src/views/Dashboard/components/LatestProducts/data.js b/webui/src/views/Dashboard/components/LatestProducts/data.js new file mode 100644 index 0000000..bbe9e09 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestProducts/data.js @@ -0,0 +1,35 @@ +import uuid from 'uuid/v1'; +import moment from 'moment'; + +export default [ + { + id: uuid(), + name: 'Dropbox', + imageUrl: '/images/products/product_1.png', + updatedAt: moment().subtract(2, 'hours') + }, + { + id: uuid(), + name: 'Medium Corporation', + imageUrl: '/images/products/product_2.png', + updatedAt: moment().subtract(2, 'hours') + }, + { + id: uuid(), + name: 'Slack', + imageUrl: '/images/products/product_3.png', + updatedAt: moment().subtract(3, 'hours') + }, + { + id: uuid(), + name: 'Lyft', + imageUrl: '/images/products/product_4.png', + updatedAt: moment().subtract(5, 'hours') + }, + { + id: uuid(), + name: 'GitHub', + imageUrl: '/images/products/product_5.png', + updatedAt: moment().subtract(9, 'hours') + } +]; diff --git a/webui/src/views/Dashboard/components/LatestProducts/index.js b/webui/src/views/Dashboard/components/LatestProducts/index.js new file mode 100644 index 0000000..8d53be3 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestProducts/index.js @@ -0,0 +1 @@ +export { default } from './LatestProducts'; diff --git a/webui/src/views/Dashboard/components/LatestSales/LatestSales.js b/webui/src/views/Dashboard/components/LatestSales/LatestSales.js new file mode 100644 index 0000000..fd2f207 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestSales/LatestSales.js @@ -0,0 +1,78 @@ +import React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { Bar } from 'react-chartjs-2'; +import { makeStyles } from '@material-ui/styles'; +import { + Card, + CardHeader, + CardContent, + CardActions, + Divider, + Button +} from '@material-ui/core'; +import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; +import ArrowRightIcon from '@material-ui/icons/ArrowRight'; + +import { data, options } from './chart'; + +const useStyles = makeStyles(() => ({ + root: {}, + chartContainer: { + height: 400, + position: 'relative' + }, + actions: { + justifyContent: 'flex-end' + } +})); + +const LatestSales = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( + + + Last 7 days + + } + title="Latest Sales" + /> + + +
+ +
+
+ + + + +
+ ); +}; + +LatestSales.propTypes = { + className: PropTypes.string +}; + +export default LatestSales; diff --git a/webui/src/views/Dashboard/components/LatestSales/chart.js b/webui/src/views/Dashboard/components/LatestSales/chart.js new file mode 100644 index 0000000..68e7545 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestSales/chart.js @@ -0,0 +1,72 @@ +import palette from '../../../../theme/palette'; + +export const data = { + labels: ['1 Aug', '2 Aug', '3 Aug', '4 Aug', '5 Aug', '6 Aug'], + datasets: [ + { + label: 'This year', + backgroundColor: palette.primary.main, + data: [18, 5, 19, 27, 29, 19, 20] + }, + { + label: 'Last year', + backgroundColor: palette.neutral, + data: [11, 20, 12, 29, 30, 25, 13] + } + ] +}; + +export const options = { + responsive: true, + maintainAspectRatio: false, + animation: false, + legend: { display: false }, + cornerRadius: 20, + tooltips: { + enabled: true, + mode: 'index', + intersect: false, + borderWidth: 1, + borderColor: palette.divider, + backgroundColor: palette.white, + titleFontColor: palette.text.primary, + bodyFontColor: palette.text.secondary, + footerFontColor: palette.text.secondary + }, + layout: { padding: 0 }, + scales: { + xAxes: [ + { + barThickness: 12, + maxBarThickness: 10, + barPercentage: 0.5, + categoryPercentage: 0.5, + ticks: { + fontColor: palette.text.secondary + }, + gridLines: { + display: false, + drawBorder: false + } + } + ], + yAxes: [ + { + ticks: { + fontColor: palette.text.secondary, + beginAtZero: true, + min: 0 + }, + gridLines: { + borderDash: [2], + borderDashOffset: [2], + color: palette.divider, + drawBorder: false, + zeroLineBorderDash: [2], + zeroLineBorderDashOffset: [2], + zeroLineColor: palette.divider + } + } + ] + } +}; diff --git a/webui/src/views/Dashboard/components/LatestSales/index.js b/webui/src/views/Dashboard/components/LatestSales/index.js new file mode 100644 index 0000000..f5cde02 --- /dev/null +++ b/webui/src/views/Dashboard/components/LatestSales/index.js @@ -0,0 +1 @@ +export { default } from './LatestSales'; diff --git a/webui/src/views/Dashboard/components/TasksProgress/TasksProgress.js b/webui/src/views/Dashboard/components/TasksProgress/TasksProgress.js new file mode 100644 index 0000000..046de74 --- /dev/null +++ b/webui/src/views/Dashboard/components/TasksProgress/TasksProgress.js @@ -0,0 +1,87 @@ +import React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { + Card, + CardContent, + Grid, + Typography, + Avatar, + LinearProgress +} from '@material-ui/core'; +import InsertChartIcon from '@material-ui/icons/InsertChartOutlined'; + +const useStyles = makeStyles(theme => ({ + root: { + height: '100%' + }, + content: { + alignItems: 'center', + display: 'flex' + }, + title: { + fontWeight: 700 + }, + avatar: { + backgroundColor: theme.palette.primary.main, + color: theme.palette.primary.contrastText, + height: 56, + width: 56 + }, + icon: { + height: 32, + width: 32 + }, + progress: { + marginTop: theme.spacing(3) + } +})); + +const TasksProgress = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( + + + + + + TASKS PROGRESS + + 75.5% + + + + + + + + + + + ); +}; + +TasksProgress.propTypes = { + className: PropTypes.string +}; + +export default TasksProgress; diff --git a/webui/src/views/Dashboard/components/TasksProgress/index.js b/webui/src/views/Dashboard/components/TasksProgress/index.js new file mode 100644 index 0000000..71c88d2 --- /dev/null +++ b/webui/src/views/Dashboard/components/TasksProgress/index.js @@ -0,0 +1 @@ +export { default } from './TasksProgress'; diff --git a/webui/src/views/Dashboard/components/TotalProfit/TotalProfit.js b/webui/src/views/Dashboard/components/TotalProfit/TotalProfit.js new file mode 100644 index 0000000..ff6f2bc --- /dev/null +++ b/webui/src/views/Dashboard/components/TotalProfit/TotalProfit.js @@ -0,0 +1,79 @@ +import React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { Card, CardContent, Grid, Typography, Avatar } from '@material-ui/core'; +import AttachMoneyIcon from '@material-ui/icons/AttachMoney'; + +const useStyles = makeStyles(theme => ({ + root: { + height: '100%', + backgroundColor: theme.palette.primary.main, + color: theme.palette.primary.contrastText + }, + content: { + alignItems: 'center', + display: 'flex' + }, + title: { + fontWeight: 700 + }, + avatar: { + backgroundColor: theme.palette.white, + color: theme.palette.primary.main, + height: 56, + width: 56 + }, + icon: { + height: 32, + width: 32 + } +})); + +const TotalProfit = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( + + + + + + TOTAL PROFIT + + + $23,200 + + + + + + + + + + + ); +}; + +TotalProfit.propTypes = { + className: PropTypes.string +}; + +export default TotalProfit; diff --git a/webui/src/views/Dashboard/components/TotalProfit/index.js b/webui/src/views/Dashboard/components/TotalProfit/index.js new file mode 100644 index 0000000..756f00c --- /dev/null +++ b/webui/src/views/Dashboard/components/TotalProfit/index.js @@ -0,0 +1 @@ +export { default } from './TotalProfit'; diff --git a/webui/src/views/Dashboard/components/TotalUsers/TotalUsers.js b/webui/src/views/Dashboard/components/TotalUsers/TotalUsers.js new file mode 100644 index 0000000..5b7fd1b --- /dev/null +++ b/webui/src/views/Dashboard/components/TotalUsers/TotalUsers.js @@ -0,0 +1,99 @@ +import React from 'react'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles } from '@material-ui/styles'; +import { Card, CardContent, Grid, Typography, Avatar } from '@material-ui/core'; +import ArrowUpwardIcon from '@material-ui/icons/ArrowUpward'; +import PeopleIcon from '@material-ui/icons/PeopleOutlined'; + +const useStyles = makeStyles(theme => ({ + root: { + height: '100%' + }, + content: { + alignItems: 'center', + display: 'flex' + }, + title: { + fontWeight: 700 + }, + avatar: { + backgroundColor: theme.palette.success.main, + height: 56, + width: 56 + }, + icon: { + height: 32, + width: 32 + }, + difference: { + marginTop: theme.spacing(2), + display: 'flex', + alignItems: 'center' + }, + differenceIcon: { + color: theme.palette.success.dark + }, + differenceValue: { + color: theme.palette.success.dark, + marginRight: theme.spacing(1) + } +})); + +const TotalUsers = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + + return ( + + + + + + TOTAL USERS + + 1,600 + + + + + + + +
+ + + 16% + + + Since last month + +
+
+
+ ); +}; + +TotalUsers.propTypes = { + className: PropTypes.string +}; + +export default TotalUsers; diff --git a/webui/src/views/Dashboard/components/TotalUsers/index.js b/webui/src/views/Dashboard/components/TotalUsers/index.js new file mode 100644 index 0000000..99ed27b --- /dev/null +++ b/webui/src/views/Dashboard/components/TotalUsers/index.js @@ -0,0 +1 @@ +export { default } from './TotalUsers'; diff --git a/webui/src/views/Dashboard/components/UsersByDevice/UsersByDevice.js b/webui/src/views/Dashboard/components/UsersByDevice/UsersByDevice.js new file mode 100644 index 0000000..cf4d8fb --- /dev/null +++ b/webui/src/views/Dashboard/components/UsersByDevice/UsersByDevice.js @@ -0,0 +1,154 @@ +import React from 'react'; +import { Doughnut } from 'react-chartjs-2'; +import clsx from 'clsx'; +import PropTypes from 'prop-types'; +import { makeStyles, useTheme } from '@material-ui/styles'; +import { + Card, + CardHeader, + CardContent, + IconButton, + Divider, + Typography +} from '@material-ui/core'; +import LaptopMacIcon from '@material-ui/icons/LaptopMac'; +import PhoneIphoneIcon from '@material-ui/icons/PhoneIphone'; +import RefreshIcon from '@material-ui/icons/Refresh'; +import TabletMacIcon from '@material-ui/icons/TabletMac'; + +const useStyles = makeStyles(theme => ({ + root: { + height: '100%' + }, + chartContainer: { + position: 'relative', + height: '300px' + }, + stats: { + marginTop: theme.spacing(2), + display: 'flex', + justifyContent: 'center' + }, + device: { + textAlign: 'center', + padding: theme.spacing(1) + }, + deviceIcon: { + color: theme.palette.icon + } +})); + +const UsersByDevice = props => { + const { className, ...rest } = props; + + const classes = useStyles(); + const theme = useTheme(); + + const data = { + datasets: [ + { + data: [63, 15, 22], + backgroundColor: [ + theme.palette.primary.main, + theme.palette.error.main, + theme.palette.warning.main + ], + borderWidth: 8, + borderColor: theme.palette.white, + hoverBorderColor: theme.palette.white + } + ], + labels: ['Desktop', 'Tablet', 'Mobile'] + }; + + const options = { + legend: { + display: false + }, + responsive: true, + maintainAspectRatio: false, + animation: false, + cutoutPercentage: 80, + layout: { padding: 0 }, + tooltips: { + enabled: true, + mode: 'index', + intersect: false, + borderWidth: 1, + borderColor: theme.palette.divider, + backgroundColor: theme.palette.white, + titleFontColor: theme.palette.text.primary, + bodyFontColor: theme.palette.text.secondary, + footerFontColor: theme.palette.text.secondary + } + }; + + const devices = [ + { + title: 'Desktop', + value: '63', + icon: , + color: theme.palette.primary.main + }, + { + title: 'Tablet', + value: '15', + icon: , + color: theme.palette.error.main + }, + { + title: 'Mobile', + value: '23', + icon: , + color: theme.palette.warning.main + } + ]; + + return ( + + + + + } + title="Users By Device" + /> + + +
+ +
+
+ {devices.map(device => ( +
+ {device.icon} + {device.title} + + {device.value}% + +
+ ))} +
+
+
+ ); +}; + +UsersByDevice.propTypes = { + className: PropTypes.string +}; + +export default UsersByDevice; diff --git a/webui/src/views/Dashboard/components/UsersByDevice/index.js b/webui/src/views/Dashboard/components/UsersByDevice/index.js new file mode 100644 index 0000000..95465d2 --- /dev/null +++ b/webui/src/views/Dashboard/components/UsersByDevice/index.js @@ -0,0 +1 @@ +export { default } from './UsersByDevice'; diff --git a/webui/src/views/Dashboard/components/index.js b/webui/src/views/Dashboard/components/index.js new file mode 100644 index 0000000..9efde58 --- /dev/null +++ b/webui/src/views/Dashboard/components/index.js @@ -0,0 +1,8 @@ +export { default as Budget } from './Budget'; +export { default as LatestOrders } from './LatestOrders'; +export { default as LatestProducts } from './LatestProducts'; +export { default as LatestSales } from './LatestSales'; +export { default as TasksProgress } from './TasksProgress'; +export { default as TotalProfit } from './TotalProfit'; +export { default as TotalUsers } from './TotalUsers'; +export { default as UsersByDevice } from './UsersByDevice'; diff --git a/webui/src/views/Dashboard/index.js b/webui/src/views/Dashboard/index.js new file mode 100644 index 0000000..449ae56 --- /dev/null +++ b/webui/src/views/Dashboard/index.js @@ -0,0 +1 @@ +export { default } from './Dashboard'; diff --git a/webui/src/views/Icons/Icons.js b/webui/src/views/Icons/Icons.js new file mode 100644 index 0000000..b0b1c1c --- /dev/null +++ b/webui/src/views/Icons/Icons.js @@ -0,0 +1,29 @@ +import React from 'react'; +import { makeStyles } from '@material-ui/styles'; + +const useStyles = makeStyles(theme => ({ + root: { + padding: theme.spacing(4) + }, + iframe: { + width: '100%', + minHeight: 640, + border: 0 + } +})); + +const Icons = () => { + const classes = useStyles(); + + return ( +
+