diff --git a/Makefile b/Makefile
index bcd73601..c7bdddb6 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,4 @@
-VERSION :=$(shell git describe --tags)
+VERSION :=$(shell git describe --tags --always)
LDFLAGS := "-s -w -X main.version=$(VERSION)"
OUT_DIR := dist
CMD := ./cmd/rmfakecloud
diff --git a/dockerbuild.sh b/dockerbuild.sh
index c8a66c3b..925b5a3b 100755
--- a/dockerbuild.sh
+++ b/dockerbuild.sh
@@ -1,2 +1,2 @@
export DOCKER_BUILDKIT=1
-docker build -t rmfakecloud --no-cache --build-arg VERSION="$(git describe --tags)" .
+docker build -t rmfakecloud --no-cache --build-arg VERSION="$(git describe --tags --always)" .
diff --git a/ui/README.md b/ui/README.md
index 4a9d6ad7..125c4ac0 100644
--- a/ui/README.md
+++ b/ui/README.md
@@ -1,68 +1 @@
-This project was bootstrapped with [Create React App](https://github.com/facebook/create-react-app).
-
-## Available Scripts
-
-In the project directory, you can run:
-
-### `yarn start`
-
-Runs the app in the development mode.
-Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
-
-The page will reload if you make edits.
-You will also see any lint errors in the console.
-
-### `yarn test`
-
-Launches the test runner in the interactive watch mode.
-See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
-
-### `yarn build`
-
-Builds the app for production to the `build` folder.
-It correctly bundles React in production mode and optimizes the build for the best performance.
-
-The build is minified and the filenames include the hashes.
-Your app is ready to be deployed!
-
-See the section about [deployment](https://facebook.github.io/create-react-app/docs/deployment) for more information.
-
-### `yarn run eject`
-
-**Note: this is a one-way operation. Once you `eject`, you can’t go back!**
-
-If you aren’t satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
-
-Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point you’re on your own.
-
-You don’t have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldn’t feel obligated to use this feature. However we understand that this tool wouldn’t be useful if you couldn’t customize it when you are ready for it.
-
-## Learn More
-
-You can learn more in the [Create React App documentation](https://facebook.github.io/create-react-app/docs/getting-started).
-
-To learn React, check out the [React documentation](https://reactjs.org/).
-
-### Code Splitting
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/code-splitting
-
-### Analyzing the Bundle Size
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/analyzing-the-bundle-size
-
-### Making a Progressive Web App
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/making-a-progressive-web-app
-
-### Advanced Configuration
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/advanced-configuration
-
-### Deployment
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/deployment
-
-### `npm run build` fails to minify
-
-This section has moved here: https://facebook.github.io/create-react-app/docs/troubleshooting#npm-run-build-fails-to-minify
+# UI for rmfakecloud
diff --git a/ui/package.json b/ui/package.json
index de5c4a6d..93690d2d 100644
--- a/ui/package.json
+++ b/ui/package.json
@@ -5,10 +5,12 @@
"private": true,
"dependencies": {
"autoprefixer": "10.4.5",
- "bootstrap": "5.2.0-beta1",
+ "bootstrap": "^5.3.3",
"jwt-decode": "^3.1.2",
+ "moment": "^2.29.4",
"react": "^17.0.1",
- "react-bootstrap": "^1.4.3",
+ "react-arborist": "^3.4.0",
+ "react-bootstrap": "^2.10.0",
"react-dom": "^17.0.1",
"react-dropzone": "^11.3.2",
"react-dropzone-uploader": "^2.11.0",
@@ -17,6 +19,7 @@
"react-router-dom": "^5.3.0",
"react-toastify": "^8.1.1",
"react-treebeard": "^3.2.4",
+ "sass": "^1.69.5",
"typescript": "^4.4.4"
},
"scripts": {
@@ -41,6 +44,7 @@
]
},
"devDependencies": {
+ "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@testing-library/jest-dom": "^5.11.9",
"@testing-library/react": "^12.1.2",
"@testing-library/user-event": "^13.5.0",
@@ -48,7 +52,9 @@
"@types/react": "^17.0.31",
"@types/react-dom": "^17.0.10",
"@types/react-router-dom": "^5.3.1",
- "react-scripts": "5.0.1"
+ "react-scripts": "5.0.1",
+ "sass-loader": "^13.3.2",
+ "webpack": "^5.89.0"
},
"proxy": "http://localhost:3000"
}
diff --git a/ui/public/index.html b/ui/public/index.html
index 3c37149a..225a5116 100644
--- a/ui/public/index.html
+++ b/ui/public/index.html
@@ -1,11 +1,8 @@
-
+
-
-
rmfakecloud
diff --git a/ui/src/App.jsx b/ui/src/App.jsx
new file mode 100644
index 00000000..dccdc93d
--- /dev/null
+++ b/ui/src/App.jsx
@@ -0,0 +1,47 @@
+import React, {useEffect} from "react";
+import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
+import { ToastContainer } from 'react-toastify';
+
+import apiService from "./services/api.service";
+import { AuthProvider } from "./common/useAuthContext";
+import Role from "./common/Role";
+import { PrivateRoute } from "./components/PrivateRoute";
+import Navigationbar from "./components/Navigation";
+
+import Login from "./pages/Login";
+import Home from "./pages/Home";
+import Connect from "./pages/Connect";
+import Documents from "./pages/Documents";
+import Admin from "./pages/Admin";
+import NoMatch from "./pages/404";
+
+import "react-toastify/dist/ReactToastify.css";
+
+import "./App.scss"
+
+export default function App() {
+
+ useEffect(() => {
+ apiService.checkLogin()
+ }, [])
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/ui/src/App.scss b/ui/src/App.scss
new file mode 100644
index 00000000..d4c26a02
--- /dev/null
+++ b/ui/src/App.scss
@@ -0,0 +1,235 @@
+@import "./theme.scss";
+
+// TODO: Remove styles which are not meant to be global into module files
+
+:root {
+ --bs-code-color: #{$action} !important;
+ --bs-body-bg: #{$background1} !important;
+ --bs-border-color: #{$foreground2} !important;
+ --bs-tertiary-bg: #{$background1} !important;
+
+ .dropdown-item {
+ --bs-dropdown-link-hover-bg: #{$background2};
+ --bs-dropdown-link-active-bg: #{$action};
+ }
+
+ .btn-outline-secondary {
+ --bs-btn-color: #{$foreground2};
+ --bs-btn-border-color: #{$foreground2};
+ }
+
+ .btn-primary {
+ --bs-btn-bg: #{$foreground2};
+ --bs-btn-color: #{$background1};
+ --bs-btn-border-color: #{$foreground2};
+
+ --bs-btn-hover-bg: #{$action};
+ --bs-btn-hover-color: #{$foreground2};
+ --bs-btn-hover-border-color: #{$action};
+
+ --bs-btn-active-bg: #{$action};
+ --bs-btn-active-color: #{$background2};
+ --bs-btn-active-border-color: #{$action};
+
+ --bs-btn-active-shadow: none;
+ --bs-btn-focus-shadow-rgb: none; // #{$action-rgb};
+ }
+
+ .btn-danger {
+ --bs-btn-bg: #{$reject};
+ --bs-btn-color: #{$foreground2};
+ --bs-btn-border-color: #{$reject};
+
+ --bs-btn-hover-bg: #{$reject};
+ --bs-btn-hover-color: #{$foreground2};
+ --bs-btn-hover-border-color: #{$reject};
+
+ --bs-btn-active-bg: #{$reject};
+ --bs-btn-active-color: #{$background2};
+ --bs-btn-active-border-color: #{$reject};
+
+ --bs-btn-active-shadow: none;
+ --bs-btn-focus-shadow-rgb: none; // #{$action-rgb};
+
+ &:hover, &:focus {
+ opacity: 0.7;
+ }
+ }
+}
+
+.form-control:focus {
+ border-color: $action !important;
+ box-shadow: none !important; // 0 0 0 0.25rem rgba(var(--bs-btn-focus-shadow-rgb), 10%) !important;
+}
+
+.card input {
+ margin-bottom: 0.5em;
+}
+
+#root > .container-fluid {
+ margin-top: 25px;
+}
+
+#root > nav {
+ backdrop-filter: blur(12px);
+ background-color: $background1;
+}
+
+html > body {
+ overflow-y: scroll;
+ background-color: $background2;
+}
+
+*:disabled {
+ color: $foreground2 !important;
+ background-color: $background2 !important;
+
+ opacity: 0.7 !important;
+}
+
+div[role="button"] > p {
+ margin: 1em;
+}
+
+.vstack {
+ gap: 2em;
+}
+
+canvas {
+ margin: auto;
+ width: auto !important;
+ height: auto !important;
+ max-width: 100%;
+ max-height: calc(100vh - 230px);
+}
+
+code {
+ padding: 0.5em;
+ background-color: $background1;
+}
+
+div[role="tree"] {
+ height: min-content !important;
+ max-height: 700px !important;
+}
+
+@media (max-width: 768px) {
+ .documents-tree {
+ max-height: 25vh !important;
+ }
+}
+
+.container-fluid {
+ box-sizing: border-box;
+ margin: 0 50px;
+ max-width: none;
+ width: calc(100vw - 100px) !important;
+}
+
+.documents-tree {
+ height: min-content !important;
+ max-height: 700px;
+
+ div[role=treeitem] {
+ outline: none;
+
+ & > div {
+ display: flex;
+ align-items: center;
+ height: 100%;
+ width: 100%;
+ overflow: hidden;
+ }
+
+ &:hover {
+ background-color: rgba(255, 255, 255, .1);
+ cursor: pointer;
+ }
+
+ .treeitem-nodename {
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ overflow: hidden;
+ &.is-folder {
+ font-weight: bold;
+ }
+ }
+
+ }
+}
+
+.filelist {
+ gap: 0 !important;
+
+ .filelist-item {
+ &:hover {
+ background-color: rgba(255, 255, 255, .1);
+ border-bottom: 1px solid $background2;
+ cursor: pointer;
+ }
+
+ .is-folder {
+ font-weight: bold;
+ }
+
+ .filelist-metadata {
+ color: $foreground2;
+ font-size: 0.8em;
+ padding: 0 0.5em;
+ }
+ }
+}
+
+.filegrid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));
+ grid-gap: 1em;
+
+ .filegrid-file-item,
+ .filegrid-folder-item {
+ display: inline-grid;
+ padding: 0.5em;
+
+ &:hover {
+ background-color: rgba(255, 255, 255, .1);
+ cursor: pointer;
+ }
+
+ .filegrid-metadata {
+ font-weight: normal;
+ color: $foreground2;
+ font-size: 0.8em;
+ }
+ }
+
+ .filegrid-file-item {
+ .fileicon {
+ padding: 0.5rem;
+ svg {
+ height: 80px;
+ width: 100%;
+ }
+ }
+ .filename {
+ display: block;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ width: 100%;
+ font-size: 0.8rem;
+ }
+ }
+
+ .filegrid-folder-item {
+ font-weight: bold;
+ overflow: hidden;
+ white-space: nowrap;
+ text-overflow: ellipsis;
+ }
+}
+
+main h2 {
+ padding: 0.5em 0;
+}
+
+@import "node_modules/bootstrap/scss/bootstrap";
\ No newline at end of file
diff --git a/ui/src/App.tsx b/ui/src/App.tsx
deleted file mode 100644
index a3071626..00000000
--- a/ui/src/App.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import React, {useEffect} from "react";
-
-import Navigationbar from "./components/Navigation";
-import Login from "./components/Login";
-import UserList from "./components/UserList";
-import UserProfile from "./components/UserProfile";
-import Home from "./components/Home";
-import Documents from "./components/Documents";
-import NoMatch from "./components/NoMatch";
-
-import { BrowserRouter as Router, Route, Switch } from "react-router-dom";
-import { AuthProvider } from "./common/useAuthContext";
-import { PrivateRoute } from "./components/PrivateRoute";
-import CodeGenerator from "./components/CodeGenerator";
-import ResetPassword from "./components/ResetPassword";
-import Role from "./common/Role";
-import apiService from "./services/api.service";
-import { ToastContainer } from 'react-toastify';
-import 'react-toastify/dist/ReactToastify.css';
-import "bootstrap/dist/css/bootstrap.min.css";
-
-export default function App() {
- useEffect(() => {
- apiService.checkLogin()
- }, [])
- return (
- <>
-
-
-
-
-
-
-
- >
- );
-}
diff --git a/ui/src/components/CodeGenerator.js b/ui/src/components/CodeGenerator.js
deleted file mode 100644
index 4349f778..00000000
--- a/ui/src/components/CodeGenerator.js
+++ /dev/null
@@ -1,31 +0,0 @@
-import React, {useState} from "react";
-import apiservice from "../services/api.service"
-
-
-export default function CodeGenerator() {
-
- const [code, setCode] = useState("")
- const [error, setError] = useState("")
-
- const newCode = () => {
- setCode("")
- apiservice.getCode()
- .then(x => {
- setCode(x)
- }).catch(e => {
- setError(e)
- })
- }
-
- if (error) {
- return {error.message}
;
- }
-
- const style = {textAlign:"center", height:"5em"}
- return (
- <>
- Generate Code
-
{code}
- >
- );
-}
diff --git a/ui/src/components/Documents/Tree.js b/ui/src/components/Documents/Tree.js
deleted file mode 100644
index 2c02edf3..00000000
--- a/ui/src/components/Documents/Tree.js
+++ /dev/null
@@ -1,181 +0,0 @@
-import { React, useEffect, useState } from 'react';
-import { Treebeard } from 'react-treebeard';
-import apiservice from "../../services/api.service"
-
-//TODO: poc only
-const treeStyle = {
- tree: {
- base: {
- listStyle: 'none',
- // backgroundColor: 'white',
- padding: 0,
-
- // color: 'white',
- fontFamily: '"Helvetica Neue", "Open Sans", Arial, sans-serif',
- fontSize: '1.3rem'
- },
- node: {
- base: {
- position: 'relative',
- border:1,
- borderStyle:'solid',
- borderColor:'black',
- margin:5
- },
- activeLink: {
- background: 'blue'
-
- },
- toggle: {
- base: {
- position: 'relative',
- display: 'inline-block',
- verticalAlign: 'top',
- marginLeft: '5px',
- height: '13px',
- width: '14px'
- },
- wrapper: {
- // position: 'absolute',
- verticalAlign: 'bottom',
- // top: '50%',
- // left: '50%',
- // margin: '-7px 0 0 -7px',
- margin: '0px 0px 0 0',
- height: '14px'
- },
- height: 14,
- width: 14,
- arrow: {
- // fill: 'rgb(35,31,32)',
- strokeWidth: 1
- }
- },
- header: {
- base: {
- display: 'inline-block',
- verticalAlign: 'top',
- // color: 'rgb(35,31,32)'
- },
- connector: {
- width: '2px',
- height: '12px',
- // borderLeft: 'solid 2px black',
- // borderBottom: 'solid 2px black',
- position: 'absolute',
- // top: '0px',
- // left: '-21px'
- },
- title: {
- lineHeight: '24px',
- // verticalAlign: 'middle',
- padding: '5px'
- }
- },
- subtree: {
- listStyle: 'none',
- border:2,
- borderColor: 'red',
- paddingLeft: '19px'
- },
- loading: {
- color: '#E2C089'
- }
- }
- }
-}
-const TreeExample = (props) => {
- const [data, setData] = useState({
- loading: true
- });
- const [downloadError, setDownloadError] = useState();
- const [dwn, setDownloadUrl] = useState();
- const [cursor, setCursor] = useState(false);
- const {onFileSelected} = props
-
- const loadDocs = () =>
- apiservice.listDocument()
- .then(docs => {
- setData({
- loading:false,
- docs: docs.Entries
- })
- })
- .catch(e => {
- setData({
- loading: false,
- error: e
- })
- })
-
- useEffect(() => {
- loadDocs()
- },[props.counter])
-
- useEffect(()=>{
- document.addEventListener("contextmenu", (event)=>{
- })
- });
-
- if (data.loading) {
- return Loading...
;
- }
- if (data.error) {
- return {data.error.message}
;
- }
-
- if (data.docs && !data.docs.length) {
- return No documents
;
- }
- const onToggle = (node, toggled) => {
- if (cursor) {
- cursor.active = false;
- }
- node.active = true;
- console.log(node.id)
- if (node.children) {
- node.toggled = toggled;
- setDownloadUrl(null);
- if (onFileSelected) {
- onFileSelected(null);
- }
- props.onFolderChanged(node.id);
- } else {
- //TODO: another quick poc hack
- setDownloadUrl({id:node.id, name:node.name})
- if (onFileSelected) {
- onFileSelected(node.id);
- }
- }
- setCursor(node);
- setData(Object.assign({}, data))
- }
-
- const onDownloadClick = () => {
- setDownloadError(null)
- const {id, name} = dwn
- apiservice.download(id)
- .then(blob => {
- var url = window.URL.createObjectURL(blob)
- var a = document.createElement('a')
- a.href = url
- a.download = name+ '.pdf'
- document.body.appendChild(a)
- a.click()
- a.remove()
- })
- .catch(e => {
- setDownloadError('cant download ' + e)
- })
-
- }
-
- return (
-
- { dwn &&
Download {dwn.name} }
- { downloadError &&
{downloadError}
}
-
-
- )
-}
-export default TreeExample;
diff --git a/ui/src/components/Documents/Upload.js b/ui/src/components/Documents/Upload.js
deleted file mode 100644
index 3a5edc83..00000000
--- a/ui/src/components/Documents/Upload.js
+++ /dev/null
@@ -1,83 +0,0 @@
-import React, { useMemo, useState } from 'react';
-import { useDropzone } from 'react-dropzone';
-
-import apiservice from "../../services/api.service";
-
-const baseStyle = {
- flex: 1,
- display: 'flex',
- flexDirection: 'column',
- alignItems: 'center',
- padding: '20px',
- borderWidth: 2,
- borderRadius: 2,
- borderColor: '#eeeeee',
- borderStyle: 'dashed',
- backgroundColor: '#fafafa',
- color: '#bdbdbd',
- outline: 'none',
- transition: 'border .24s ease-in-out'
-};
-
-const activeStyle = {
- borderColor: '#2196f3'
-};
-
-const acceptStyle = {
- borderColor: '#00e676'
-};
-
-const rejectStyle = {
- borderColor: '#ff1744'
-};
-
-export default function StyledDropzone(props) {
- const [uploading, setUploading] = useState(false);
- const [lasterror, setLastError] = useState();
- const uploadFolder = props.uploadFolder;
- var onDrop = async (acceptedFiles) => {
- try {
- setUploading(true);
- await apiservice.upload(uploadFolder, acceptedFiles)
- setLastError(null)
- props.filesUploaded()
- } catch (e) {
- setLastError(e)
- console.error(e)
- }
- finally{
- setUploading(false);
- }
- console.log('done')
- }
- const {
- getRootProps,
- getInputProps,
- isDragActive,
- isDragAccept,
- isDragReject
- } = useDropzone({ accept: 'application/pdf, application/zip, application/epub+zip', onDropAccepted: onDrop });
-
- const style = useMemo(() => ({
- ...baseStyle,
- ...(isDragActive ? activeStyle : {}),
- ...(isDragAccept ? acceptStyle : {}),
- ...(isDragReject ? rejectStyle : {})
- }), [
- isDragActive,
- isDragReject,
- isDragAccept
- ]);
-
- if (!uploading) {
- return (
-
-
-
Drag 'n' drop some files here, or click to select files to upload
-
{lasterror}
-
- )
- } else {
- return Uploading...
- }
-}
diff --git a/ui/src/components/Documents/index.js b/ui/src/components/Documents/index.js
deleted file mode 100644
index 5ced9bbd..00000000
--- a/ui/src/components/Documents/index.js
+++ /dev/null
@@ -1,73 +0,0 @@
-import Upload from "./Upload";
-import Tree from "./Tree";
-import { useState, useRef } from "react";
-import { Container, Row, Col, Button } from "react-bootstrap";
-import { Document, Page } from "react-pdf";
-import constants from "../../common/constants";
-
-export default function DocumentList() {
- const [counter, setCounter] = useState(0);
- const [folder, setFolder] = useState("");
- const [file, setFile] = useState(null);
- const [page, setPage] = useState(1);
- const [pages, setPages] = useState(1);
- const containerRef = useRef(null);
-
- const callback = () => {
- //TODO: really hacky, just to force the tree to update
- setCounter(counter + 1);
- };
- const setParent = (f) => {
- console.log("setting folder" + f);
- setFolder(f);
- };
-
- const onFileSelected = (f) => {
- setFile(`${constants.ROOT_URL}/documents/${f}`);
- };
- const onLoadSuccess = (pdf) => {
- setPage(1);
- setPages(pdf.numPages);
- };
- const onPrev = () => {
- console.log(containerRef.current.offsetWidth);
- setPage((p) => {
- return Math.max(p - 1, 1);
- });
- };
- const onNext = () => {
- setPage((p) => {
- return Math.min(p + 1, pages);
- });
- };
-
- return (
-
-
-
-
-
-
-
- Prev
- Next
-
- Page: {page} of {pages}
-
- {file && (
-
-
-
- )}
-
-
-
- );
-}
diff --git a/ui/src/components/Home.tsx b/ui/src/components/Home.tsx
deleted file mode 100644
index 210a3c58..00000000
--- a/ui/src/components/Home.tsx
+++ /dev/null
@@ -1,24 +0,0 @@
-import React from "react";
-import Container from "react-bootstrap/Container";
-
-const Home = () => {
- return (
-
-
-
Welcome to your own rm cloud
-
About
-
This is still a work in progress
-
TIPS
-
-
- you can use rmapi (https://github.com/juruen/rmapi) for managing
- files, just specify the URL of your instance with the RMAPI_HOST
- variable (RMAPI_HOST=https//rmfakeclud.example.com rmapi)
-
-
-
-
- );
-};
-
-export default Home;
diff --git a/ui/src/components/Login/index.js b/ui/src/components/Login/index.js
deleted file mode 100644
index 355f3822..00000000
--- a/ui/src/components/Login/index.js
+++ /dev/null
@@ -1,64 +0,0 @@
-import React, { useState } from "react";
-import { useAuthState } from "../../common/useAuthContext";
-import { loginUser } from "../../common/actions";
-import styles from "./Login.module.css";
-import { useHistory } from "react-router";
-
-const Login = () => {
- let history = useHistory();
- const [email, setEmail] = useState("");
- const [password, setPassword] = useState("");
-
- const { state, dispatch } = useAuthState(); //read the values of loading and errorMessage from context
- const { errorMessage, loading } = state;
-
- const handleLogin = async (e) => {
- e.preventDefault();
-
- let payload = { email, password };
- try {
- await loginUser(dispatch, payload);
- history.push("/"); //TODO: usenavigate or return redirect
- } catch (error) {
- console.log(error);
- }
- };
-
- return (
-
-
- {errorMessage ?
{errorMessage}
: null}
-
-
-
- );
-};
-
-export default Login;
diff --git a/ui/src/components/NameTag.js b/ui/src/components/NameTag.js
new file mode 100644
index 00000000..e0359e73
--- /dev/null
+++ b/ui/src/components/NameTag.js
@@ -0,0 +1,13 @@
+import { BsChevronRight } from "react-icons/bs";
+import { Button } from "react-bootstrap";
+
+export default function NameTag({ node, onSelect }) {
+ if (node.parent) {
+ return (<>
+
+ {!node.parent.isRoot && }
+ onSelect(node)}>{node.data.name}
+ >)
+ }
+ return <>>
+}
\ No newline at end of file
diff --git a/ui/src/components/Navigation.js b/ui/src/components/Navigation.js
index f699c851..e7a6633f 100644
--- a/ui/src/components/Navigation.js
+++ b/ui/src/components/Navigation.js
@@ -7,50 +7,42 @@ import { NavLink } from "react-router-dom";
const NavigationBar = () => {
const { state:{user}, dispatch } = useAuthState();
- function isAdmin(user) {
- return user && user.Roles && user.Roles[0] === "Admin";
- }
-
function handleLogout(e) {
logout(dispatch);
}
return (
-
-
- rmfakecloud
-
- {user && (
- <>
-
-
- {" "}
-
-
- Documents
-
-
- {isAdmin(user) && (
+
+
+
+
+ rmfakecloud
+
+
+
+ {user && (
+ <>
+
+
+ {" "}
- Users
+
+ Documents
+
- )}
-
- Code
-
-
-
-
- >
- )}
-
+
+
+
+ >
+ )}
+
);
};
diff --git a/ui/src/components/ResetPassword.js b/ui/src/components/ResetPassword.js
deleted file mode 100644
index 708419d4..00000000
--- a/ui/src/components/ResetPassword.js
+++ /dev/null
@@ -1,132 +0,0 @@
-import React, { useState } from "react";
-import apiservice from "../services/api.service";
-import Form from "react-bootstrap/Form";
-import Button from "react-bootstrap/Button";
-import { useAuthState } from "../common/useAuthContext";
-import PasswordField from "./PasswordField";
-import { logout } from "../common/actions";
-
-export default function OwnUserProfile() {
- const { state:{ user }, dispatch } = useAuthState();
- const userId = user.UserID
-
- const [formErrors, setFormErrors] = useState({});
- const [resetPasswordForm, setResetPasswordForm] = useState({
- userid: userId,
- currentPassword: null,
- newPassword: null,
- confirmNewPassword: null,
- });
-
- function handleChange({ target }) {
- console.log(resetPasswordForm)
- setResetPasswordForm({ ...resetPasswordForm, [target.name]: target.value });
- }
-
- function formIsValid() {
- const _errors = {};
-
- // if (!resetPasswordForm.email) _errors.email = "email is required";
- if (!resetPasswordForm.currentPassword)
- _errors.currentPassword = "currentPassword id is required";
- if (!resetPasswordForm.newPassword)
- _errors.newPassword = "newPassword is required";
- if (!resetPasswordForm.confirmNewPassword)
- _errors.confirmNewPassword = "confirm new password is required";
- if (resetPasswordForm.confirmNewPassword !== resetPasswordForm.newPassword)
- _errors.confirmNewPassword =
- "confirm new password must match new password";
-
- setFormErrors(_errors);
-
- return Object.keys(_errors).length === 0;
- }
-
- function handleSubmit(event) {
- event.preventDefault();
-
- if (!formIsValid()) return;
-
- apiservice.resetPassword(resetPasswordForm)
- .then(r => {
- if (r.ok) {
- logout(dispatch)
-
- return
- }
- if (r.status === 400) {
- return r.json()
- }
- throw new Error("unknown error: " + r.status)
- })
- .then(j => {
- if (j && j.error){
- setFormErrors(j)
- return
- } else {
- setFormErrors({error:"invalid data"})
- }
- })
- .catch(e => {
- console.log(e)
- setFormErrors({
- error:e.toString()
- })
- })
-
- }
-
- return (
-
- Old Password
-
- {formErrors.currentPassword && (
- {formErrors.currentPassword}
- )}
-
-
- New Password
-
- {formErrors.newPassword && (
- {formErrors.newPassword}
- )}
-
-
- Confirm Password
-
- {formErrors.confirmNewPassword && (
-
- {formErrors.confirmNewPassword}
-
- )}
-
- {formErrors.error && (
-
- {formErrors.error}
-
- )}
-
-
- Save
-
-
- );
-}
diff --git a/ui/src/components/Spinner.js b/ui/src/components/Spinner.js
index 1cbc8b0e..880ffa0c 100644
--- a/ui/src/components/Spinner.js
+++ b/ui/src/components/Spinner.js
@@ -4,9 +4,6 @@ import { Card, Spinner as BSpinner } from "react-bootstrap";
export default function Spinner() {
return (
-
+
New User
diff --git a/ui/src/components/PasswordField.js b/ui/src/pages/Admin/PasswordField.js
similarity index 81%
rename from ui/src/components/PasswordField.js
rename to ui/src/pages/Admin/PasswordField.js
index aed8c2ce..f50443ae 100644
--- a/ui/src/components/PasswordField.js
+++ b/ui/src/pages/Admin/PasswordField.js
@@ -1,5 +1,5 @@
import React, { useState, useRef, useEffect } from "react";
-import { FormControl, InputGroup, Button } from "react-bootstrap";
+import { Form, InputGroup, Button } from "react-bootstrap";
import { FaEye } from "react-icons/fa";
function PasswordField(props) {
@@ -10,9 +10,7 @@ function PasswordField(props) {
function showHide(e) {
e.preventDefault();
e.stopPropagation();
-
setInputType(inputType === "text" ? "password" : "text");
-
inputEl.current.focus();
}
@@ -29,17 +27,16 @@ function PasswordField(props) {
return (
-
-
-
-
-
-
+
+
+
);
}
diff --git a/ui/src/pages/Admin/ResetPassword.js b/ui/src/pages/Admin/ResetPassword.js
new file mode 100644
index 00000000..35a08ae6
--- /dev/null
+++ b/ui/src/pages/Admin/ResetPassword.js
@@ -0,0 +1,136 @@
+import React, { useState } from "react";
+import Form from "react-bootstrap/Form";
+import Button from "react-bootstrap/Button";
+
+import apiservice from "../../services/api.service";
+import { useAuthState } from "../../common/useAuthContext";
+import PasswordField from "./PasswordField";
+import { logout } from "../../common/actions";
+
+export default function OwnUserProfile() {
+ const { state:{ user }, dispatch } = useAuthState();
+ const userId = user.UserID
+
+ const [formErrors, setFormErrors] = useState({});
+ const [resetPasswordForm, setResetPasswordForm] = useState({
+ userid: userId,
+ currentPassword: "",
+ newPassword: "",
+ confirmNewPassword: "",
+ });
+
+ function handleChange({ target }) {
+ console.log(resetPasswordForm)
+ setResetPasswordForm({ ...resetPasswordForm, [target.name]: target.value });
+ }
+
+ function formIsValid() {
+ const _errors = {};
+
+ // if (!resetPasswordForm.email) _errors.email = "email is required";
+ if (!resetPasswordForm.currentPassword)
+ _errors.currentPassword = "currentPassword id is required";
+ if (!resetPasswordForm.newPassword)
+ _errors.newPassword = "newPassword is required";
+ if (!resetPasswordForm.confirmNewPassword)
+ _errors.confirmNewPassword = "confirm new password is required";
+ if (resetPasswordForm.confirmNewPassword !== resetPasswordForm.newPassword)
+ _errors.confirmNewPassword =
+ "confirm new password must match new password";
+
+ setFormErrors(_errors);
+
+ return Object.keys(_errors).length === 0;
+ }
+
+ function handleSubmit(event) {
+ event.preventDefault();
+
+ if (!formIsValid()) return;
+
+ apiservice.resetPassword(resetPasswordForm)
+ .then(r => {
+ if (r.ok) {
+ logout(dispatch)
+
+ return
+ }
+ if (r.status === 400) {
+ return r.json()
+ }
+ throw new Error("unknown error: " + r.status)
+ })
+ .then(j => {
+ if (j && j.error){
+ setFormErrors(j)
+ return
+ } else {
+ setFormErrors({error:"invalid data"})
+ }
+ })
+ .catch(e => {
+ console.log(e)
+ setFormErrors({
+ error:e.toString()
+ })
+ })
+ }
+
+ return (
+ <>
+ Reset password
+
+ Old Password
+
+ {formErrors.currentPassword && (
+ {formErrors.currentPassword}
+ )}
+
+
+ New Password
+
+ {formErrors.newPassword && (
+ {formErrors.newPassword}
+ )}
+
+
+ Confirm Password
+
+ {formErrors.confirmNewPassword && (
+
+ {formErrors.confirmNewPassword}
+
+ )}
+
+ {formErrors.error && (
+
+ {formErrors.error}
+
+ )}
+
+
+ Save
+
+
+ >
+ );
+}
diff --git a/ui/src/components/UserList.js b/ui/src/pages/Admin/UserList.js
similarity index 73%
rename from ui/src/components/UserList.js
rename to ui/src/pages/Admin/UserList.js
index bfaa658b..c93907de 100644
--- a/ui/src/components/UserList.js
+++ b/ui/src/pages/Admin/UserList.js
@@ -1,16 +1,19 @@
import React, {useState} from "react";
-import useFetch from "../hooks/useFetch";
-import Spinner from "./Spinner";
-import {Alert, Button, Card, Modal, Table} from "react-bootstrap";
+import { toast } from "react-toastify";
+import { Alert, Button, Modal, Table } from "react-bootstrap";
+
+import useFetch from "../../hooks/useFetch";
+import Spinner from "../../components/Spinner";
import UserProfileModal from "./UserProfileModal";
import NewUserModal from "./NewUserModal";
-import apiService from "../services/api.service";
-import {formatDate} from "../common/date";
-import { toast } from "react-toastify";
+import apiService from "../../services/api.service";
+import { formatDate } from "../../common/date";
+
const userListUrl = "users";
const NewUser = 1;
const UpdateUser = 2;
+
export default function UserList() {
const [index, setIndex] = useState(false);
const { data: userList, error, loading } = useFetch(`${userListUrl}`, index);
@@ -40,10 +43,10 @@ export default function UserList() {
if (error) {
return (
-
- An Error Occurred
- {`Error ${error.status}: ${error.statusText}`}
-
+
+ An Error Occurred
+ {`Error ${error.status}: ${error.statusText}`}
+
);
}
@@ -71,7 +74,7 @@ export default function UserList() {
await apiService.deleteuser(id)
refresh()
} catch(e){
- toast.error('Error:'+ e)
+ toast.error('Error:'+ e)
}
}
// const handleSave = async e => {
@@ -84,7 +87,7 @@ export default function UserList() {
// });
// hide()
// refresh()
- //
+ //
// } catch (e) {
// setFormErrors({ error: e.toString()});
// }
@@ -95,19 +98,18 @@ export default function UserList() {
// }
return (
-
- User List
-
+ <>
+ Users
+
-
- #
+
+ #
UserId
- Email
- Name
- Created At
+ Email
+ Name
+ Created At
New User
-
+
{userList.map((x, index) => (
@@ -122,12 +124,13 @@ export default function UserList() {
))}
+
-
+
-
+
-
+ >
);
}
diff --git a/ui/src/components/UserProfile.js b/ui/src/pages/Admin/UserProfile.js
similarity index 90%
rename from ui/src/components/UserProfile.js
rename to ui/src/pages/Admin/UserProfile.js
index 0032dbe4..4d0b1d52 100644
--- a/ui/src/components/UserProfile.js
+++ b/ui/src/pages/Admin/UserProfile.js
@@ -1,19 +1,19 @@
import React, { useState, useEffect } from "react";
import { useHistory } from "react-router-dom";
-
-import NoMatch from "./NoMatch";
+import { toast } from "react-toastify";
+//import { useParams } from "react-router-dom";
import Form from "react-bootstrap/Form";
import Button from "react-bootstrap/Button";
-import Spinner from "./Spinner";
-import useFetch from "../hooks/useFetch";
-import apiService from "../services/api.service";
-import { toast } from "react-toastify";
-import { useParams } from "react-router-dom";
+
+import NoMatch from "../404";
+import Spinner from "../../components/Spinner";
+import useFetch from "../../hooks/useFetch";
+import apiService from "../../services/api.service";
const userListUrl = "users";
-export default function UserProfile() {
- const { userid } = useParams();
+export default function UserProfile(userid) {
+ //const { userid } = useParams();
const { data: user, loading, error } = useFetch(`${userListUrl}/${userid}`);
const history = useHistory();
diff --git a/ui/src/components/UserProfileModal.js b/ui/src/pages/Admin/UserProfileModal.js
similarity index 94%
rename from ui/src/components/UserProfileModal.js
rename to ui/src/pages/Admin/UserProfileModal.js
index 367fb83f..0244b4f6 100644
--- a/ui/src/components/UserProfileModal.js
+++ b/ui/src/pages/Admin/UserProfileModal.js
@@ -1,7 +1,7 @@
import React, { useState } from "react";
import Form from "react-bootstrap/Form";
import { Button, Card } from "react-bootstrap";
-import apiService from "../services/api.service";
+import apiService from "../../services/api.service";
import { Alert } from "react-bootstrap";
@@ -51,17 +51,12 @@ export default function UserProfileModal(params) {
if (!user) return null;
return (