diff --git a/.gitignore b/.gitignore index 646a83ae4..83aa23052 100644 --- a/.gitignore +++ b/.gitignore @@ -132,3 +132,7 @@ storybook-static public/locales* .VSCodeCounter + +# editor backup/swap files +*.swp +*.bak diff --git a/public/static/locales/en/dashboard.json b/public/static/locales/en/dashboard.json index 2076a6041..5c49ed2a0 100644 --- a/public/static/locales/en/dashboard.json +++ b/public/static/locales/en/dashboard.json @@ -35,10 +35,13 @@ "goToVice": "Go to analysis", "instantLaunches": "Instant Launches", "integratedBy": "Integrated by", + "ipAddress": "IP Address", "launchAction": "Launch", "launchAria": "launch", "learnMore": "Learn More", "login": "Login", + "loginTime": "Login Time", + "loginsTableError": "Unable to get recent logins.", "loginSignUp": "Login or sign-up to access additional features.", "newsFeed": "News", "noAnalysesStats": "Looks like you haven't run any analyses yet.", diff --git a/src/components/dashboard/dashboardItem/LoginTable.js b/src/components/dashboard/dashboardItem/LoginTable.js new file mode 100644 index 000000000..02b0ea786 --- /dev/null +++ b/src/components/dashboard/dashboardItem/LoginTable.js @@ -0,0 +1,85 @@ +/** + * + * @author mian + * + * A table of recent logins for the user. + * + */ + +import React from "react"; +import { useQuery } from "react-query"; + +import { logins, LOGINS_QUERY_KEY } from "serviceFacades/users"; +import ErrorTypographyWithDialog from "components/error/ErrorTypographyWithDialog"; +import { useTranslation } from "i18n"; +import { + formatDate, + getFormattedDistance, +} from "components/utils/DateFormatter"; +import { + useTheme, + Skeleton, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Paper, +} from "@mui/material"; + +export default function LoginsTable() { + const { t } = useTranslation(["dashboard", "common"]); + + const theme = useTheme(); + const { status, data, error } = useQuery({ + queryKey: [LOGINS_QUERY_KEY], + queryFn: () => logins({ limit: 5 }), + }); + + if (status === "error") { + return ( +
+ +
+ ); + } + if (status === "loading") { + return ; + } + return ( + + + + + {t("loginTime")} + {t("ipAddress")} + + + + {data?.logins.map((row, index) => ( + + + {formatDate(row["login_time"])} +   + + ( + {t("common:timestamp", { + timestamp: getFormattedDistance( + row["login_time"] / 1000 + ), + })} + ) + + + {row["ip_address"]} + + ))} + +
+
+ ); +} diff --git a/src/components/dashboard/dashboardItem/ResourceUsageItem.js b/src/components/dashboard/dashboardItem/ResourceUsageItem.js index 15833b668..4f2109091 100644 --- a/src/components/dashboard/dashboardItem/ResourceUsageItem.js +++ b/src/components/dashboard/dashboardItem/ResourceUsageItem.js @@ -19,6 +19,7 @@ import { import DataConsumption from "./DataConsumption"; import AnalysesStats from "./AnalysesStats"; +import LoginTable from "./LoginTable"; import CPUConsumption from "./CPUConsumption"; import ExternalLink from "components/utils/ExternalLink"; import ErrorTypographyWithDialog from "components/error/ErrorTypographyWithDialog"; @@ -145,11 +146,16 @@ export default function ResourceUsageItem(props) { )} - + + + + + + )} diff --git a/src/server/api/user.js b/src/server/api/user.js index 269a2dba9..d50c9f8c5 100644 --- a/src/server/api/user.js +++ b/src/server/api/user.js @@ -45,6 +45,16 @@ export default function userRouter() { }) ); + logger.info("adding the GET /logins handler"); + api.get( + "/logins", + auth.authnTokenMiddleware, + terrainHandler({ + method: "GET", + pathname: "/secured/logins", + }) + ); + logger.info("adding the POST /preferences handler"); api.post( "/preferences", diff --git a/src/serviceFacades/users.js b/src/serviceFacades/users.js index 08527e05f..763a2302e 100644 --- a/src/serviceFacades/users.js +++ b/src/serviceFacades/users.js @@ -16,6 +16,7 @@ const WEBHOOKS_TOPICS_QUERY_KEY = "fetchHookTopics"; const WEBHOOK_TEST_KEY = "testWebhook"; const USER_PORTAL_QUERY_KEY = "fetchUserPortalStatus"; const USER_PORTAL_DETAILS_QUERY_KEY = "fetchUserPortalDetails;"; +const LOGINS_QUERY_KEY = "logins"; const getUserInfo = ({ userIds }) => { const userQuery = userIds.join("&username="); @@ -41,6 +42,15 @@ function bootstrap() { }); } +function logins({ limit }) { + return callApi({ + endpoint: `/api/logins`, + params: { limit }, + method: "GET", + credentials: "include", + }); +} + function savePreferences({ preferences, webhooks }) { let promises = []; @@ -198,10 +208,12 @@ export { WEBHOOKS_TOPICS_QUERY_KEY, WEBHOOKS_TYPES_QUERY_KEY, WEBHOOK_TEST_KEY, + LOGINS_QUERY_KEY, getUserInfo, getUserPortalDetails, getUserProfile, bootstrap, + logins, savePreferences, resetToken, getRedirectURIs,