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,