Compare commits

...

6 Commits

Author SHA1 Message Date
Tudor Stanciu f6fbdd94f7 @flare/tuitio-client-react 2023-02-15 01:25:18 +02:00
Tudor Stanciu 4ad8c1657d license 2023-02-14 01:45:11 +02:00
Tudor Stanciu 79c71f86b8 login with enter key 2023-02-14 01:44:07 +02:00
Tudor Stanciu 7f6056baf4 @flare/tuitio-react-client 2023-02-14 01:36:06 +02:00
Tudor Stanciu e1c4b2ca04 removed REACT_APP_NETWORK_RESURRECTOR_SERVER_URL 2023-02-07 00:45:03 +02:00
Tudor Stanciu 3f94695d79 tuitio 2023-02-05 00:44:52 +02:00
16 changed files with 434 additions and 507 deletions

9
.env
View File

@ -1,9 +1,8 @@
#REACT_APP_IDENTITY_AUTHENTICATION_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password}
#REACT_APP_TUITIO_URL=http://localhost:5063/identity/authenticate?UserName={username}&Password={password}
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/identity-server-api/identity/authenticate?UserName={username}&Password={password}
REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064
#REACT_APP_NETWORK_RESURRECTOR_SERVER_URL=http://localhost:5062
#REACT_APP_NETWORK_RESURRECTOR_SERVER_URL=https://lab.code-rove.com/network-resurrector-server-api
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
#REACT_APP_NETWORK_RESURRECTOR_API_URL=http://localhost:5064
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
#600000 milliseconds = 10 minutes
REACT_APP_MACHINE_PING_INTERVAL=600000

View File

@ -1,5 +1,5 @@
PUBLIC_URL=/network-resurrector/
REACT_APP_IDENTITY_AUTHENTICATION_URL=https://lab.code-rove.com/tuitio/identity/authenticate?UserName={username}&Password={password}
REACT_APP_TUITIO_URL=https://lab.code-rove.com/tuitio
REACT_APP_NETWORK_RESURRECTOR_API_URL=https://lab.code-rove.com/network-resurrector-api
#900000 milliseconds = 15 minutes

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2020 Tudor Stanciu
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

694
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -13,7 +13,7 @@
},
"private": true,
"dependencies": {
"@flare/js-utils": "^1.0.2",
"@flare/tuitio-client-react": "^1.0.0",
"@material-ui/core": "^4.11.2",
"@material-ui/icons": "^4.11.2",
"@testing-library/jest-dom": "^5.11.6",

View File

@ -2,7 +2,7 @@ import React from "react";
import ApplicationStepper from "./stepper/ApplicationStepper";
import Switcher from "./Switcher";
import { makeStyles } from "@material-ui/core/styles";
import { useAuthorizationToken } from "../../hooks";
import { useTuitioToken } from "@flare/tuitio-client-react";
import LoginContainer from "../../features/login/components/LoginContainer";
const useStyles = makeStyles(() => ({
@ -16,7 +16,7 @@ const useStyles = makeStyles(() => ({
const Main = () => {
const classes = useStyles();
const { validateToken } = useAuthorizationToken();
const { validate: validateToken } = useTuitioToken();
const tokenIsValid = validateToken();
return (

View File

@ -1,4 +1,4 @@
import React, { useState, useCallback } from "react";
import React, { useState, useMemo } from "react";
import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles";
import {
@ -18,35 +18,29 @@ import LoginComponent from "./LoginComponent";
import { useTranslation } from "react-i18next";
import { useToast } from "../../../hooks";
import styles from "../styles";
import { useTuitioUser } from "@flare/tuitio-client-react";
const useStyles = makeStyles(styles);
const LoggedInComponent = ({
credentials,
token,
onChange,
onLogin,
onLogout
}) => {
const LoggedInComponent = ({ credentials, onChange, onLogin, onLogout }) => {
const classes = useStyles();
const { t } = useTranslation();
const [expanded, setExpanded] = useState(false);
const { info } = useToast();
const { lastLoginDate, userName } = useTuitioUser();
const handleExpandLogin = () => {
setExpanded(!expanded);
};
const getTokenValidFrom = useCallback(() => {
const tokenValidFrom = token?.validFrom;
if (tokenValidFrom) {
const valueForDisplay = t("LONG_DATE", { date: tokenValidFrom });
const loginDate = useMemo(() => {
if (lastLoginDate) {
const valueForDisplay = t("LONG_DATE", { date: lastLoginDate });
return valueForDisplay;
}
return "N/A";
}, [token, t]);
}, [lastLoginDate, t]);
const handleLogin = async () => {
const result = await onLogin();
@ -65,15 +59,11 @@ const LoggedInComponent = ({
<AccountBox />
</Avatar>
}
title={
<strong>
{t("Login.Hello", { username: credentials.userName })}
</strong>
}
title={<strong>{t("Login.Hello", { username: userName })}</strong>}
subheader={
<Tooltip title={t("Login.AuthenticationDate")}>
<Typography variant="caption" display="block">
{getTokenValidFrom()}
{loginDate}
</Typography>
</Tooltip>
}
@ -117,7 +107,6 @@ const LoggedInComponent = ({
LoggedInComponent.propTypes = {
credentials: PropTypes.object.isRequired,
token: PropTypes.object,
onChange: PropTypes.func.isRequired,
onLogin: PropTypes.func.isRequired,
onLogout: PropTypes.func.isRequired

View File

@ -43,6 +43,9 @@ const LoginComponent = ({ credentials, onChange, onLogin }) => {
className={classes.field}
onChange={onChange("password")}
value={credentials.password}
onKeyDown={e => {
if (e.key === "Enter") onLogin();
}}
/>
</CardContent>
<CardActions>

View File

@ -1,59 +1,49 @@
import React, { useContext } from "react";
import React, { useState } from "react";
import LoginCard from "./LoginCard";
import { authenticate, invalidate } from "../../../utils/identity";
import {
ApplicationStateContext,
ApplicationDispatchContext
} from "../../../state/ApplicationContexts";
import { useToast, useAuthorizationToken } from "../../../hooks";
import { useToast } from "../../../hooks";
import { useTranslation } from "react-i18next";
import LoggedInComponent from "./LoggedInComponent";
import { useTuitioClient, useTuitioToken } from "@flare/tuitio-client-react";
const LoginContainer = () => {
const state = useContext(ApplicationStateContext);
const dispatchActions = useContext(ApplicationDispatchContext);
const [credentials, setCredentials] = useState({
userName: "",
password: ""
});
const { error } = useToast();
const { t } = useTranslation();
const { tokenIsValid, invalidateToken, getToken } = useAuthorizationToken();
const { login, logout } = useTuitioClient({
onLoginFailed: response => error(t("Login.IncorrectCredentials")),
onLoginError: err => error(err.message)
});
const { valid: tokenIsValid } = useTuitioToken();
const handleChange = prop => event => {
dispatchActions.onCredentialsChange(prop, event.target.value);
setCredentials(prev => ({ ...prev, [prop]: event.target.value }));
};
const handleLogin = async () => {
const { userName, password } = state.credentials;
try {
const response = await authenticate(userName, password);
if (response.status === "SUCCESS") {
dispatchActions.onAuthorizationTokenChange(response.token);
return response.token;
} else if (response.status === "BAD_CREDENTIALS") {
error(t("Login.IncorrectCredentials"));
}
} catch (err) {
error(err.message);
}
const handleLogin = () => {
const { userName, password } = credentials;
return login(userName, password);
};
const handleLogout = () => {
invalidate();
invalidateToken();
logout();
};
return (
<>
{tokenIsValid ? (
<LoggedInComponent
credentials={state.credentials}
token={getToken()}
credentials={credentials}
onChange={handleChange}
onLogin={handleLogin}
onLogout={handleLogout}
/>
) : (
<LoginCard
credentials={state.credentials}
credentials={credentials}
onChange={handleChange}
onLogin={handleLogin}
/>

View File

@ -1,2 +1 @@
export { useToast } from "./useToast";
export { useAuthorizationToken } from "./useAuthorizationToken";

View File

@ -1,29 +0,0 @@
import { useContext } from "react";
import {
ApplicationStateContext,
ApplicationDispatchContext
} from "../state/ApplicationContexts";
export const useAuthorizationToken = () => {
const state = useContext(ApplicationStateContext);
const dispatchActions = useContext(ApplicationDispatchContext);
const getToken = () => state.security.authorization.token;
const validateToken = () => {
const token = getToken();
if (!token) {
return false;
}
const valid = new Date(token.validUntil) >= new Date();
return valid;
};
const tokenIsValid = validateToken();
const invalidateToken = () => {
dispatchActions.onAuthorizationTokenChange(null);
};
return { getToken, validateToken, tokenIsValid, invalidateToken };
};

View File

@ -4,11 +4,14 @@ import "./index.css";
import "./utils/i18n";
import App from "./components/App";
import { BrowserRouter as Router } from "react-router-dom";
import { TuitioProvider } from "@flare/tuitio-client-react";
ReactDOM.render(
<Router basename={process.env.PUBLIC_URL || ""}>
<Suspense fallback={<div>Loading...</div>}>
<App />
<TuitioProvider tuitioUrl={process.env.REACT_APP_TUITIO_URL}>
<App />
</TuitioProvider>
</Suspense>
</Router>,
document.getElementById("root")

View File

@ -1,20 +1,4 @@
import { localStorage } from "@flare/js-utils";
import { storageKeys } from "../utils/identity";
const { getItem } = localStorage;
const token = getItem(storageKeys.TOKEN);
const userName = getItem(storageKeys.USER);
export const initialState = {
credentials: {
userName: userName || "",
password: ""
},
security: {
authorization: {
token
}
},
network: {
machines: Object.assign([], { loaded: false }),
test: ""

View File

@ -1,15 +1,5 @@
export function reducer(state, action) {
switch (action.type) {
case "onCredentialsChange": {
const { prop, value } = action.payload;
return {
...state,
credentials: {
...state.credentials,
[prop]: value
}
};
}
case "onNetworkChange": {
const { prop, value } = action.payload;
return {
@ -20,19 +10,6 @@ export function reducer(state, action) {
}
};
}
case "onAuthorizationTokenChange": {
const { token } = action.payload;
return {
...state,
security: {
...state.security,
authorization: {
...state.security.authorization,
token
}
}
};
}
default: {
return state;
}
@ -40,10 +17,6 @@ export function reducer(state, action) {
}
export const dispatchActions = dispatch => ({
onCredentialsChange: (prop, value) =>
dispatch({ type: "onCredentialsChange", payload: { prop, value } }),
onNetworkChange: (prop, value) =>
dispatch({ type: "onNetworkChange", payload: { prop, value } }),
onAuthorizationTokenChange: token =>
dispatch({ type: "onAuthorizationTokenChange", payload: { token } })
dispatch({ type: "onNetworkChange", payload: { prop, value } })
});

View File

@ -1,12 +1,9 @@
import axios from "axios";
import i18next from "i18next";
import { localStorage } from "@flare/js-utils";
import { storageKeys } from "./identity";
const { getItem } = localStorage;
import { fetch as fetchTuitioData } from "@flare/tuitio-client";
function getHeaders() {
const token = getItem(storageKeys.TOKEN);
const { token } = fetchTuitioData();
const language = i18next.language;
return {

View File

@ -1,37 +0,0 @@
import { request } from "./axios";
import { localStorage } from "@flare/js-utils";
const { setItem, getItem, removeItem } = localStorage;
const storageKeys = {
TOKEN: "AUTHORIZATION_TOKEN",
USER: "USER_NAME"
};
const authenticate = async (userName, password) => {
const urlTemplate = process.env.REACT_APP_IDENTITY_AUTHENTICATION_URL;
const url = urlTemplate
.replace("{username}", userName)
.replace("{password}", password);
const options = {
method: "post"
};
const response = await request(url, options);
if (response.status === "SUCCESS") {
setItem(storageKeys.TOKEN, response.token);
setItem(storageKeys.USER, userName);
}
return response;
};
const invalidate = () => {
const token = getItem(storageKeys.TOKEN);
if (token) {
removeItem(storageKeys.TOKEN);
removeItem(storageKeys.USER);
}
};
export { storageKeys, authenticate, invalidate };