From 45ab083f96d9e5c7ea901a1878ca48404459ad0b Mon Sep 17 00:00:00 2001
From: Michal Bajer <michal.bajer@fujitsu.com>
Date: Thu, 20 Jun 2024 16:32:40 +0200
Subject: [PATCH] feat(ledger-browser): refactor home page - Remove status
 apps, its functionality has been moved to the home page. - Add status
 components for persistence apps. - Add home page that contains cards for each
 configured app. Clicking on it   naviagtes to specific app, clicking on
 Status button shows status component. - Remove app drawer, replace it with a
 button that navigates to root path   (i.e. the home app, navigation between
 apps is handled here). - Remove all the remaining dead and legacy code, apply
 small structure upgrades. - Since this PR removes all old code, and all the
 current code was written by   me and Tomasz, I've also removed previous
 inactive package contributors.

Depends on #3320

Signed-off-by: Michal Bajer <michal.bajer@fujitsu.com>
---
 packages/cacti-ledger-browser/package.json    |  10 --
 .../main/typescript/CactiLedgerBrowserApp.tsx |  20 +--
 .../src/main/typescript/apps/cacti/index.tsx  |  20 ---
 .../apps/cacti/pages/status-page.tsx          |  48 ------
 .../src/main/typescript/apps/cacti/queries.ts |   5 -
 .../src/main/typescript/apps/eth/index.tsx    |  17 +-
 .../CertificateDetailsBox.tsx                 |   2 +-
 .../src/main/typescript/apps/fabric/index.tsx |  17 +-
 .../TransactionDetails/TranactionInfoCard.tsx |   2 +-
 .../src/main/typescript/common/config.tsx     |   7 +-
 .../common/hook/use-persistence-app-status.ts |  31 ++++
 .../src/main/typescript/common/queries.ts     |  41 +++++
 .../main/typescript/common/supabase-types.ts  |   8 +
 .../main/typescript/common/token-standards.ts |   4 -
 .../src/main/typescript/common/types/app.ts   |  24 ++-
 .../components/Layout/HeaderBar.tsx           |  42 +----
 .../PersistencePluginStatus.tsx               |  78 +++++++++
 .../typescript/components/WelcomePage.tsx     |  12 --
 .../components/ui/Button.module.css           |  77 ---------
 .../main/typescript/components/ui/Button.tsx  |  22 ---
 .../components/ui/CardWrapper.module.css      |  85 ----------
 .../typescript/components/ui/CardWrapper.tsx  | 150 -----------------
 .../typescript/components/ui/CustomTable.css  |  53 ------
 .../components/ui/CustomTable.module.css      | 137 ----------------
 .../typescript/components/ui/CustomTable.tsx  | 127 ---------------
 .../EmptyTablePlaceholder.module.css          |   7 -
 .../EmptyTablePlaceholder.tsx                 |   9 --
 .../components/ui/Pagination.module.css       |  51 ------
 .../typescript/components/ui/Pagination.tsx   |  63 --------
 .../components/ui/Search.module.css           |  43 -----
 .../main/typescript/components/ui/Search.tsx  |  38 -----
 .../components/ui/StackedRowItems.tsx         |   0
 .../src/main/typescript/main.tsx              |   3 +
 .../main/typescript/pages/home/AppCard.tsx    | 151 ++++++++++++++++++
 .../main/typescript/pages/home/HomePage.tsx   |  31 ++++
 .../src/main/typescript/theme.ts              |   3 +
 36 files changed, 414 insertions(+), 1024 deletions(-)
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/apps/cacti/index.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/apps/cacti/pages/status-page.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/apps/cacti/queries.ts
 create mode 100644 packages/cacti-ledger-browser/src/main/typescript/common/hook/use-persistence-app-status.ts
 create mode 100644 packages/cacti-ledger-browser/src/main/typescript/common/queries.ts
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/common/token-standards.ts
 create mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/PersistencePluginStatus/PersistencePluginStatus.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/WelcomePage.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.module.css
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.module.css
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.css
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.module.css
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.module.css
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.module.css
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.tsx
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.module.css
 delete mode 100644 packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.tsx
 rename packages/cacti-ledger-browser/src/main/typescript/{apps/fabric => }/components/ui/StackedRowItems.tsx (100%)
 create mode 100644 packages/cacti-ledger-browser/src/main/typescript/pages/home/AppCard.tsx
 create mode 100644 packages/cacti-ledger-browser/src/main/typescript/pages/home/HomePage.tsx

diff --git a/packages/cacti-ledger-browser/package.json b/packages/cacti-ledger-browser/package.json
index d4b857f5ae..cf570032a4 100644
--- a/packages/cacti-ledger-browser/package.json
+++ b/packages/cacti-ledger-browser/package.json
@@ -38,16 +38,6 @@
       "name": "Tomasz Awramski",
       "email": "tomasz.awramski@fujitsu.com",
       "url": "https://www.fujitsu.com/global/"
-    },
-    {
-      "name": "Eryk Baranowski",
-      "email": "eryk.baranowski@fujitsu.com",
-      "url": "https://www.fujitsu.com/global/"
-    },
-    {
-      "name": "Barnaba Pawelczak",
-      "email": "barnaba.pawelczak@fujitsu.com",
-      "url": "https://www.fujitsu.com/global/"
     }
   ],
   "scripts": {
diff --git a/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx b/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx
index 05862d55bf..12e43e24dd 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/CactiLedgerBrowserApp.tsx
@@ -7,7 +7,7 @@ import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
 import { themeOptions } from "./theme";
 import ContentLayout from "./components/Layout/ContentLayout";
 import HeaderBar from "./components/Layout/HeaderBar";
-import WelcomePage from "./components/WelcomePage";
+import HomePage from "./pages/home/HomePage";
 import { AppConfig, AppListEntry } from "./common/types/app";
 import { patchAppRoutePath } from "./common/utils";
 import { NotificationProvider } from "./common/context/NotificationContext";
@@ -22,8 +22,8 @@ type AppConfigProps = {
 function getAppList(appConfig: AppConfig[]) {
   const appList: AppListEntry[] = appConfig.map((app) => {
     return {
-      path: app.path,
-      name: app.name,
+      path: app.options.path,
+      name: app.appName,
     };
   });
 
@@ -43,12 +43,12 @@ function getHeaderBarRoutes(appConfig: AppConfig[]) {
 
   const headerRoutesConfig = appConfig.map((app) => {
     return {
-      key: app.path,
-      path: `${app.path}/*`,
+      key: app.options.path,
+      path: `${app.options.path}/*`,
       element: (
         <HeaderBar
           appList={appList}
-          path={app.path}
+          path={app.options.path}
           menuEntries={app.menuEntries}
         />
       ),
@@ -68,12 +68,12 @@ function getHeaderBarRoutes(appConfig: AppConfig[]) {
 function getContentRoutes(appConfig: AppConfig[]) {
   const appRoutes: RouteObject[] = appConfig.map((app) => {
     return {
-      key: app.path,
-      path: app.path,
+      key: app.options.path,
+      path: app.options.path,
       children: app.routes.map((route) => {
         return {
           key: route.path,
-          path: patchAppRoutePath(app.path, route.path),
+          path: patchAppRoutePath(app.options.path, route.path),
           element: route.element,
           children: route.children,
         };
@@ -84,7 +84,7 @@ function getContentRoutes(appConfig: AppConfig[]) {
   // Include landing / welcome page
   appRoutes.push({
     index: true,
-    element: <WelcomePage />,
+    element: <HomePage />,
   });
 
   return useRoutes([
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/index.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/index.tsx
deleted file mode 100644
index b87f2e2e38..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/index.tsx
+++ /dev/null
@@ -1,20 +0,0 @@
-import { AppConfig } from "../../common/types/app";
-import StatusPage from "./pages/status-page";
-
-const appConfig: AppConfig = {
-  name: "Status",
-  path: "/cacti",
-  menuEntries: [
-    {
-      title: "Plugin Status",
-      url: "/",
-    },
-  ],
-  routes: [
-    {
-      element: <StatusPage />,
-    },
-  ],
-};
-
-export default appConfig;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/pages/status-page.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/pages/status-page.tsx
deleted file mode 100644
index ed71b44257..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/pages/status-page.tsx
+++ /dev/null
@@ -1,48 +0,0 @@
-import CardWrapper from "../../../components/ui/CardWrapper";
-import { useQuery } from "@tanstack/react-query";
-import { persistencePluginStatusQuery } from "../queries";
-
-function StatusPage() {
-  const { isSuccess, isError, data, error } = useQuery(
-    persistencePluginStatusQuery(),
-  );
-
-  if (isError) {
-    console.error("Data fetch error:", error);
-  }
-
-  return (
-    <div>
-      <CardWrapper
-        columns={
-          {
-            schema: [
-              { display: "Name", objProp: ["name"] },
-              { display: "Instance ID", objProp: ["last_instance_id"] },
-              { display: "Status", objProp: ["is_schema_initialized"] },
-              { display: "Created at", objProp: ["created_at"] },
-              { display: "Connected at", objProp: ["last_connected_at"] },
-            ],
-          } as any
-        }
-        data={
-          isSuccess
-            ? (data as any).map((p: any) => {
-                return {
-                  ...p,
-                  is_schema_initialized: p.is_schema_initialized
-                    ? "Setup complete"
-                    : "No schema",
-                };
-              })
-            : []
-        }
-        title={"Persistence Plugins"}
-        display={"All"}
-        trimmed={false}
-      ></CardWrapper>
-    </div>
-  );
-}
-
-export default StatusPage;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/queries.ts b/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/queries.ts
deleted file mode 100644
index 321dce4abf..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/apps/cacti/queries.ts
+++ /dev/null
@@ -1,5 +0,0 @@
-import { supabaseQueryTable } from "../../common/supabase-client";
-
-export function persistencePluginStatusQuery() {
-  return supabaseQueryTable("plugin_status");
-}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx
index 026aa83122..9ee731b528 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/apps/eth/index.tsx
@@ -1,12 +1,19 @@
-import { AppConfig } from "../../common/types/app";
 import Dashboard from "./pages/Dashboard/Dashboard";
 import Blocks from "./pages/Blocks/Blocks";
 import Transactions from "./pages/Transactions/Transactions";
 import Accounts from "./pages/Accounts/Accounts";
+import { AppConfig } from "../../common/types/app";
+import { usePersistenceAppStatus } from "../../common/hook/use-persistence-app-status";
+import PersistencePluginStatus from "../../components/PersistencePluginStatus/PersistencePluginStatus";
 
 const ethConfig: AppConfig = {
-  name: "Ethereum",
-  path: "/eth",
+  appName: "Ethereum Browser",
+  options: {
+    instanceName: "Ethereum",
+    description:
+      "Applicaion for browsing Ethereum ledger blocks, transactions and tokens. Requires Ethereum persistence plugin to work correctly.",
+    path: "/eth",
+  },
   menuEntries: [
     {
       title: "Dashboard",
@@ -34,6 +41,10 @@ const ethConfig: AppConfig = {
       element: <Accounts />,
     },
   ],
+  useAppStatus: () => usePersistenceAppStatus("PluginPersistenceEthereum"),
+  StatusComponent: (
+    <PersistencePluginStatus pluginName="PluginPersistenceEthereum" />
+  ),
 };
 
 export default ethConfig;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/components/CertificateDetails/CertificateDetailsBox.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/components/CertificateDetails/CertificateDetailsBox.tsx
index df79eac865..b7d61dc634 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/components/CertificateDetails/CertificateDetailsBox.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/components/CertificateDetails/CertificateDetailsBox.tsx
@@ -4,7 +4,7 @@ import TextField from "@mui/material/TextField";
 import { styled } from "@mui/material/styles";
 
 import { FabricCertificate } from "../../fabric-supabase-types";
-import StackedRowItems from "../ui/StackedRowItems";
+import StackedRowItems from "../../../../components/ui/StackedRowItems";
 
 const ListHeaderTypography = styled(Typography)(({ theme }) => ({
   color: theme.palette.secondary.main,
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx
index e534033c98..437410665c 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/index.tsx
@@ -1,13 +1,20 @@
-import { AppConfig } from "../../common/types/app";
 import Dashboard from "./pages/Dashboard/Dashboard";
 import Blocks from "./pages/Blocks/Blocks";
 import Transactions from "./pages/Transactions/Transactions";
 import { Outlet } from "react-router-dom";
 import TransactionDetails from "./pages/TransactionDetails/TransactionDetails";
+import { AppConfig } from "../../common/types/app";
+import { usePersistenceAppStatus } from "../../common/hook/use-persistence-app-status";
+import PersistencePluginStatus from "../../components/PersistencePluginStatus/PersistencePluginStatus";
 
 const fabricConfig: AppConfig = {
-  name: "Fabric",
-  path: "/fabric",
+  appName: "Hyperledger Fabric Browser",
+  options: {
+    instanceName: "Fabric",
+    description:
+      "Applicaion for browsing Hyperledger Fabric ledger blocks and transactions. Requires Fabric persistence plugin to work correctly.",
+    path: "/fabric",
+  },
   menuEntries: [
     {
       title: "Dashboard",
@@ -41,6 +48,10 @@ const fabricConfig: AppConfig = {
       ],
     },
   ],
+  useAppStatus: () => usePersistenceAppStatus("PluginPersistenceFabric"),
+  StatusComponent: (
+    <PersistencePluginStatus pluginName="PluginPersistenceFabric" />
+  ),
 };
 
 export default fabricConfig;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/pages/TransactionDetails/TranactionInfoCard.tsx b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/pages/TransactionDetails/TranactionInfoCard.tsx
index 055874a698..c1be9680dd 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/pages/TransactionDetails/TranactionInfoCard.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/pages/TransactionDetails/TranactionInfoCard.tsx
@@ -6,7 +6,7 @@ import Skeleton from "@mui/material/Skeleton";
 
 import { FabricTransaction } from "../../fabric-supabase-types";
 import ShortenedTypography from "../../../../components/ui/ShortenedTypography";
-import StackedRowItems from "../../components/ui/StackedRowItems";
+import StackedRowItems from "../../../../components/ui/StackedRowItems";
 
 const ListHeaderTypography = styled(Typography)(({ theme }) => ({
   color: theme.palette.secondary.main,
diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx b/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx
index a7df619503..102724e64e 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/common/config.tsx
@@ -1,10 +1,5 @@
-import cactiGuiConfig from "../apps/cacti/index";
 import ethereumGuiConfig from "../apps/eth";
 import fabricAppConfig from "../apps/fabric";
 import { AppConfig } from "./types/app";
 
-export const appConfig: AppConfig[] = [
-  cactiGuiConfig,
-  ethereumGuiConfig,
-  fabricAppConfig,
-];
+export const appConfig: AppConfig[] = [ethereumGuiConfig, fabricAppConfig];
diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/hook/use-persistence-app-status.ts b/packages/cacti-ledger-browser/src/main/typescript/common/hook/use-persistence-app-status.ts
new file mode 100644
index 0000000000..e2dd346b5a
--- /dev/null
+++ b/packages/cacti-ledger-browser/src/main/typescript/common/hook/use-persistence-app-status.ts
@@ -0,0 +1,31 @@
+import React from "react";
+import { useQuery } from "@tanstack/react-query";
+import { GetStatusResponse } from "../types/app";
+import { useNotification } from "../context/NotificationContext";
+import { persistencePluginStatus } from "../queries";
+
+/**
+ * Return status of given persistence plugin from the database.
+ *
+ * @param pluginName name of the plugin (as set by the persistence plugin itself)
+ */
+export function usePersistenceAppStatus(pluginName: string): GetStatusResponse {
+  const { isError, isPending, data, error } = useQuery(
+    persistencePluginStatus(pluginName),
+  );
+  const { showNotification } = useNotification();
+
+  React.useEffect(() => {
+    isError &&
+      showNotification(`Could get ${pluginName} status: ${error}`, "error");
+  }, [isError]);
+
+  return {
+    isPending,
+    isInitialized: data?.is_schema_initialized ?? false,
+    status: {
+      severity: "info",
+      message: "Unknown",
+    },
+  };
+}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/queries.ts b/packages/cacti-ledger-browser/src/main/typescript/common/queries.ts
new file mode 100644
index 0000000000..11eff447eb
--- /dev/null
+++ b/packages/cacti-ledger-browser/src/main/typescript/common/queries.ts
@@ -0,0 +1,41 @@
+import { createClient } from "@supabase/supabase-js";
+import { queryOptions } from "@tanstack/react-query";
+import { PluginStatus } from "./supabase-types";
+
+const supabaseQueryKey = "supabase";
+const supabaseUrl = "http://localhost:8000";
+const supabaseKey =
+  "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyAgCiAgICAicm9sZSI6ICJhbm9uIiwKICAgICJpc3MiOiAic3VwYWJhc2UtZGVtbyIsCiAgICAiaWF0IjogMTY0MTc2OTIwMCwKICAgICJleHAiOiAxNzk5NTM1NjAwCn0.dc_X5iR_VP_qT0zsiyj_I_OZ2T9FtRU2BBNWN8Bu4GE";
+
+export const supabase = createClient(supabaseUrl, supabaseKey);
+
+/**
+ * Get persistence plugin status from the database using it's name.
+ */
+export function persistencePluginStatus(name: string) {
+  const tableName = "plugin_status";
+
+  return queryOptions({
+    queryKey: [supabaseQueryKey, tableName, name],
+    queryFn: async () => {
+      const { data, error } = await supabase
+        .from(tableName)
+        .select()
+        .match({ name });
+
+      if (error) {
+        throw new Error(
+          `Could not get persistence plugin status with name ${name}: ${error.message}`,
+        );
+      }
+
+      if (data.length !== 1) {
+        throw new Error(
+          `Invalid response when persistence plugin status with name ${name}: ${data}`,
+        );
+      }
+
+      return data.pop() as PluginStatus;
+    },
+  });
+}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts b/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts
index 373e985e86..465a7701f4 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts
+++ b/packages/cacti-ledger-browser/src/main/typescript/common/supabase-types.ts
@@ -108,3 +108,11 @@ export interface TokenERC20 {
   total_supply: number;
   token_address: string;
 }
+
+export interface PluginStatus {
+  name: string;
+  last_instance_id: string;
+  is_schema_initialized: boolean;
+  created_at: string;
+  last_connected_at: string;
+}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/token-standards.ts b/packages/cacti-ledger-browser/src/main/typescript/common/token-standards.ts
deleted file mode 100644
index 65ad5a514c..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/common/token-standards.ts
+++ /dev/null
@@ -1,4 +0,0 @@
-export const STANDARDS = {
-  erc20: "ERC20",
-  erc721: "ERC721",
-};
diff --git a/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts b/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts
index fa30b3ed2d..3374b557aa 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts
+++ b/packages/cacti-ledger-browser/src/main/typescript/common/types/app.ts
@@ -1,3 +1,4 @@
+import React from "react";
 import { RouteObject } from "react-router-dom";
 
 export interface AppListEntry {
@@ -10,9 +11,28 @@ export interface AppConfigMenuEntry {
   url: string;
 }
 
-export interface AppConfig {
-  name: string;
+export interface AppStatus {
+  severity: "success" | "info" | "warning" | "error";
+  message: string;
+}
+
+export interface GetStatusResponse {
+  isPending: boolean;
+  isInitialized: boolean;
+  status: AppStatus;
+}
+
+export interface AppConfigOptions {
+  instanceName: string;
+  description: string | undefined;
   path: string;
+}
+
+export interface AppConfig {
+  appName: string;
+  options: AppConfigOptions;
   menuEntries: AppConfigMenuEntry[];
   routes: RouteObject[];
+  useAppStatus: () => GetStatusResponse;
+  StatusComponent: React.ReactElement;
 }
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx
index 00482c9cf8..7f23912c14 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/components/Layout/HeaderBar.tsx
@@ -4,14 +4,9 @@ import AppBar from "@mui/material/AppBar";
 import Box from "@mui/material/Box";
 import Toolbar from "@mui/material/Toolbar";
 import IconButton from "@mui/material/IconButton";
-import MenuIcon from "@mui/icons-material/Menu";
+import AppsIcon from "@mui/icons-material/Apps";
 import Button from "@mui/material/Button";
 import Tooltip from "@mui/material/Tooltip";
-import Drawer from "@mui/material/Drawer";
-import List from "@mui/material/List";
-import ListItem from "@mui/material/ListItem";
-import ListItemButton from "@mui/material/ListItemButton";
-import ListItemText from "@mui/material/ListItemText";
 import { AppConfigMenuEntry, AppListEntry } from "../../common/types/app";
 import { patchAppRoutePath } from "../../common/utils";
 
@@ -21,31 +16,7 @@ type HeaderBarProps = {
   menuEntries?: AppConfigMenuEntry[];
 };
 
-const HeaderBar: React.FC<HeaderBarProps> = ({
-  appList,
-  path,
-  menuEntries,
-}) => {
-  const [isAppSelectOpen, setIsAppSelectOpen] = React.useState(false);
-
-  const AppSelectDrawer = (
-    <Box
-      sx={{ width: 250 }}
-      role="presentation"
-      onClick={() => setIsAppSelectOpen(false)}
-    >
-      <List>
-        {appList.map((app) => (
-          <ListItem key={app.name} disablePadding>
-            <ListItemButton component={RouterLink} to={app.path}>
-              <ListItemText primary={app.name} />
-            </ListItemButton>
-          </ListItem>
-        ))}
-      </List>
-    </Box>
-  );
-
+const HeaderBar: React.FC<HeaderBarProps> = ({ path, menuEntries }) => {
   return (
     <AppBar position="static" sx={{ paddingX: 2 }}>
       <Toolbar disableGutters>
@@ -56,9 +27,10 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
             color="inherit"
             aria-label="select-application-button"
             sx={{ mr: 2 }}
-            onClick={() => setIsAppSelectOpen(true)}
+            component={RouterLink}
+            to={"/"}
           >
-            <MenuIcon />
+            <AppsIcon />
           </IconButton>
         </Tooltip>
 
@@ -77,10 +49,6 @@ const HeaderBar: React.FC<HeaderBarProps> = ({
           </Box>
         )}
       </Toolbar>
-
-      <Drawer open={isAppSelectOpen} onClose={() => setIsAppSelectOpen(false)}>
-        {AppSelectDrawer}
-      </Drawer>
     </AppBar>
   );
 };
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/PersistencePluginStatus/PersistencePluginStatus.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/PersistencePluginStatus/PersistencePluginStatus.tsx
new file mode 100644
index 0000000000..520dc81d5c
--- /dev/null
+++ b/packages/cacti-ledger-browser/src/main/typescript/components/PersistencePluginStatus/PersistencePluginStatus.tsx
@@ -0,0 +1,78 @@
+import React from "react";
+import { useQuery } from "@tanstack/react-query";
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+import CircularProgress from "@mui/material/CircularProgress";
+
+import StackedRowItems from "../ui/StackedRowItems";
+import { persistencePluginStatus } from "../../common/queries";
+import { useNotification } from "../../common/context/NotificationContext";
+
+type DateTimeStringProps = {
+  dateString: string | undefined;
+};
+
+function DateTimeString({ dateString }: DateTimeStringProps) {
+  const date = dateString ? new Date(dateString) : new Date();
+
+  return <Typography>{date.toLocaleString()}</Typography>;
+}
+
+type PersistencePluginStatusProps = {
+  pluginName: string;
+};
+
+/**
+ * Box that fetches and displays persistence plugin status from the database.
+ */
+export default function PersistencePluginStatus({
+  pluginName,
+}: PersistencePluginStatusProps) {
+  const { isError, isPending, data, error } = useQuery(
+    persistencePluginStatus(pluginName),
+  );
+  const { showNotification } = useNotification();
+
+  React.useEffect(() => {
+    isError &&
+      showNotification(`Could get ${pluginName} status: ${error}`, "error");
+  }, [isError]);
+
+  return (
+    <Box>
+      {isPending && (
+        <CircularProgress
+          style={{
+            position: "absolute",
+            top: "50%",
+            left: "50%",
+            zIndex: 9999,
+          }}
+        />
+      )}
+      <Typography variant="h5">Persistence Plugin Status</Typography>
+      <StackedRowItems>
+        <Typography>Plugin Name:</Typography>
+        <Typography>{data?.name}</Typography>
+      </StackedRowItems>
+      <StackedRowItems>
+        <Typography>Instance ID:</Typography>
+        <Typography>{data?.last_instance_id}</Typography>
+      </StackedRowItems>
+      <StackedRowItems>
+        <Typography>Is Schema Initialized:</Typography>
+        <Typography>
+          {data?.is_schema_initialized ? "True" : "False"}
+        </Typography>
+      </StackedRowItems>
+      <StackedRowItems>
+        <Typography>Created At:</Typography>
+        <DateTimeString dateString={data?.created_at} />
+      </StackedRowItems>
+      <StackedRowItems>
+        <Typography>Last Connected At:</Typography>
+        <DateTimeString dateString={data?.last_connected_at} />
+      </StackedRowItems>
+    </Box>
+  );
+}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/WelcomePage.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/WelcomePage.tsx
deleted file mode 100644
index 27c5d249d2..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/WelcomePage.tsx
+++ /dev/null
@@ -1,12 +0,0 @@
-import Card from "@mui/material/Card";
-
-const WelcomePage: React.FC = () => {
-  return (
-    <Card elevation={0} sx={{ textAlign: "center" }}>
-      <h1>Cacti Ledger Browser</h1>
-      <h3>Select an application to start from top-left menu</h3>
-    </Card>
-  );
-};
-
-export default WelcomePage;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.module.css
deleted file mode 100644
index eab9eabd84..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.module.css
+++ /dev/null
@@ -1,77 +0,0 @@
-.button {
-    color: rgb(14, 48, 23);
-    background-color: rgb(248, 248, 250);
-    height: 2.5rem;
-    display: flex;
-    align-items: center;
-    justify-content: center;
-    width: max-content;
-    min-width: 100px;
-    padding: 10px;
-    border: 1px solid rgb(32, 133, 77);
-    font-family: 'Roboto';
-    border-radius: 10px;
-}
-
-.button:hover {
-    background-color: rgb(219, 219, 224);
-    transform: scale(1.01);
-    cursor: pointer;
-}
-
-.button-primary {
-    background-color: rgb(244, 247, 245);
-    color:rgb(14, 44, 14);
-    border-radius: 5px;
-    width:150px;
-}
-
-.button-primary:hover {    background-color: rgb(226, 253, 219);}
-
-.button-warn {    background-color: rgb(155, 22, 13);}
-
-.button-warn:hover { background-color: rgb(114, 22, 16);}
-
-.button-menu{
-    border:none;
-    background: transparent;
-    height: 100%;
-    transition: background-color 0.5s ease-out;
-    position:relative;    
-    border-radius: 0;
-    
-}
-.button-menu:hover{
-color:rgb(0, 0, 0);
-background-color: rgb(243, 242, 242);
-}
-.button-menu:hover:after {
-    content: '';
-    display: block;
-    position: absolute;
-    left: 0;
-    right: 0;
-    bottom: 1px;
-    width: 100%;
-    height: 1px;
-    border-bottom: 2px solid green;
-
-}
-
-.button-link {
-    background: transparent;
-    border:none;
-    height: min-content;
-    color:rgb(64, 64, 228);
-}
-
-.button-link:hover{
-    background: transparent;
-    color:rgb(78, 78, 236);
-}
-
-@media (max-width: 1699px) {
-    .button-primary {
-        font-size: 1rem;
-    }
-}
\ No newline at end of file
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.tsx
deleted file mode 100644
index 1d7360b057..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Button.tsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import styles from "./Button.module.css";
-
-function Button(props: any) {
-  type ObjectKey = keyof typeof styles;
-  const buttonTypeStyle = `button-${props.type}` as ObjectKey;
-
-  const handleClick = (e: { stopPropagation: () => void; }) => {
-    e.stopPropagation();
-    props.onClick();
-  };
-
-  return (
-    <button
-      onClick={handleClick}
-      className={styles.button + " " + styles[buttonTypeStyle]}
-    >
-      {props.children}
-    </button>
-  );
-}
-
-export default Button;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.module.css
deleted file mode 100644
index d20ba47158..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.module.css
+++ /dev/null
@@ -1,85 +0,0 @@
-.wrapper {
-  background-color: rgb(253, 253, 253);
-  padding: 1rem;
-  border-radius: 10px;
-  border: 1px solid rgb(233, 236, 233);
-  height: fit-content;
-}
-
-.wrapper-half-width {
-  width: 50%;
-}
-
-.wrapper-full-width {
-  width: 100%;
-}
-
-.wrapper-cards {
-  width: 100%;
-  display: flex;
-  justify-content: center;
-  padding: 1rem;
-}
-
-.wrapper-title {
-  margin-top: .5rem;
-  display: flex;
-  gap:5px;
-  align-items: center;
-  font-weight: 700;
-  font-size: 1.2rem;
-  color: rgb(9, 75, 9);
-}
-
-.wrapper-btns {
-  display: flex;
-  justify-content: flex-end;
-  padding-right: 1rem;
-}
-
-.wrapper-header {
-  width: 100%;
-  display: flex;
-  justify-content: space-between;
-  padding: 0 1rem;
-}
-
-.wrapper-columns {
-  display: flex;
-  justify-content: space-around;
-  background-color: rgb(243, 239, 239);
-  align-items: center;
-  border-radius: 10px;
-  border: 1px solid rgb(233, 236, 233);
-  height: 50px;
-}
-
-.wrapper-columns span {
-  display: flex;
-  width: 150px;
-}
-
-.wrapper-search {
-  display: flex;
-  gap: 5px;
-}
-
-@media (max-width: 1699px) {
-  .wrapper {
-    width: 100%;
-  }
-
-  .wrapper-header {
-    padding-left: 0;
-    padding-right: 0;
-  }
-
-  .wrapper-cards {
-    flex-direction: column;
-    padding: 1rem 0;
-  }
-
-  .wrapper-title svg {
-    margin-bottom: -3px;
-  }
-}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.tsx
deleted file mode 100644
index 5bb19315c5..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CardWrapper.tsx
+++ /dev/null
@@ -1,150 +0,0 @@
-import Button from "./Button";
-import Search from "./Search";
-
-import CustomTable from "./CustomTable";
-
-import Pagination from "./Pagination";
-import EmptyTablePlaceholder from "./EmptyTablePlaceholder/EmptyTablePlaceholder";
-import styles from "./CardWrapper.module.css";
-
-import { useLocation, useNavigate } from "react-router-dom";
-import { useEffect, useState } from "react";
-
-const pageSize: number = 6;
-
-function CardWrapper(props: any) {
-  const location = useLocation();
-  const path = location.pathname.split("/");
-  const navigate = useNavigate();
-  const [searchKey, setSearchKey] = useState("");
-  let filteredData = props.data;
-  const [paginatedData, setPaginatedData] = useState<any[]>([]);
-  const [currentPage, setCurrentPage] = useState<number>(1);
-  const [totalPages, setTotalPages] = useState<number>(1);
-  const [viewport, setViewport] = useState("");
-
-  const handleGoToPage = (pageNumber: number) => {
-    if (pageNumber < 1 || pageNumber > totalPages) return;
-    setCurrentPage(pageNumber);
-  };
-
-  const handleNextPage = () => {
-    if (currentPage === totalPages) return;
-    setCurrentPage((prev) => prev + 1);
-  };
-
-  const handlePrevPage = () => {
-    if (currentPage === 1) return;
-    setCurrentPage((prev) => prev - 1);
-  };
-
-  const filterData = () => {
-    const { filters, data } = props;
-    if (searchKey.length === 0) {
-      filteredData = data;
-      return;
-    }
-    const newData = data.filter((row: any) => {
-      let isMatch: boolean = false;
-      filters?.forEach((property: string | number) => {
-        if (row[property]?.toString().toLowerCase().includes(searchKey)) {
-          isMatch = true;
-        }
-      });
-      return isMatch;
-    });
-    filteredData = newData;
-  };
-
-  const handleSearch = () => {
-    filterData();
-    if (props.getSearchValue) {
-      props.getSearchValue(searchKey);
-    }
-  };
-
-  useEffect(() => {
-    const screenResized = () =>
-      setViewport(window.innerWidth <= 1699 ? "small" : "wide");
-    screenResized();
-    window.addEventListener("resize", screenResized, true);
-    return () => {
-      window.removeEventListener("resize", screenResized, true);
-    };
-  }, []);
-
-  useEffect(() => {
-    if (filteredData.length <= pageSize) {
-      setPaginatedData(filteredData);
-    } else {
-      const firstEl = currentPage * pageSize - pageSize;
-      setPaginatedData(filteredData.slice(firstEl, firstEl + pageSize));
-    }
-  }, [currentPage, filteredData]);
-
-  useEffect(() => {
-    const pageNum = Math.ceil(filteredData.length / pageSize);
-    setTotalPages(pageNum);
-  }, [filteredData]);
-
-  return (
-    <section
-      className={`${styles["wrapper"]} ${
-        props.display === "small"
-          ? styles["wrapper-half-width"]
-          : styles["wrapper-full-width"]
-      }`}
-    >
-      <header className={styles["wrapper-header"]}>
-        <span className={styles["wrapper-title"]}>{props.title}</span>
-        {props.trimmed && viewport === "small" && (
-          <Button
-            type={"primary"}
-            onClick={() => navigate(`/${path[1]}/${props.title.toLowerCase()}`)}
-          >
-            View all
-          </Button>
-        )}
-        {props.filters && (
-          <div className={styles["wrapper-search"]}>
-            <Search
-              onKeyUp={(e: any) => setSearchKey(e)}
-              type="text"
-              placeholder="Type to search"
-            />
-            <Button onClick={handleSearch}>Search</Button>
-          </div>
-        )}
-      </header>
-      <div className={styles["wrapper-cards"]}>
-        {props?.columns && props.data?.length > 0 && (
-          <CustomTable cols={props.columns} data={paginatedData} />
-        )}
-        {props?.data?.length === 0 && <EmptyTablePlaceholder />}
-      </div>
-      <div className={styles["wrapper-btns"]}>
-        {" "}
-        {props.trimmed && viewport === "wide" && (
-          <Button
-            type={"primary"}
-            onClick={() => navigate(`/${path[1]}/${props.title.toLowerCase()}`)}
-          >
-            View all
-          </Button>
-        )}
-      </div>
-
-      {!props.trimmed && (
-        <Pagination
-          current={currentPage}
-          total={totalPages}
-          goToPage={handleGoToPage}
-          goNextPage={handleNextPage}
-          goPrevPage={handlePrevPage}
-        />
-      )}
-    </section>
-  );
-}
-
-export default CardWrapper;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.css b/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.css
deleted file mode 100644
index d94c8789f0..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.css
+++ /dev/null
@@ -1,53 +0,0 @@
-table {
-    border-collapse: separate;
-    border-spacing: 0;
-    width: 100%;
-  }
-  
-  tbody tr {
-    background-color: rgb(248, 248, 248);
-    border: 1px solid rgb(219, 241, 232);
-    border-radius: 10px;
-  }
-  
-  tbody tr:hover {
-    cursor: pointer;
-    background-color: rgb(235, 240, 237);
-  }
-  
-  th {
-    background-color: rgb(240, 235, 235);
-    border-style: none;
-    border-bottom: solid 1px rgb(223, 218, 218);
-    padding: 10px;
-  }
-  
-  td {
-    min-height: 2rem;
-    border-style: none;
-    border-bottom: solid 4px rgb(255, 255, 255);
-    padding: 1.5rem .5rem;
-    text-align: center;
-  }
-  
-  tr {
-    min-height: 20rem;
-    background-color: rgb(90, 103, 116);
-    padding: 1rem;
-  }
-  
-  tr:first-child th:first-child {
-    border-top-left-radius: 10px;
-  }
-  
-  tr:first-child th:last-child {
-    border-top-right-radius: 10px;
-  }
-  
-  tr:last-child td:first-child {
-    border-bottom-left-radius: 10px;
-  }
-  
-  tr:last-child td:last-child {
-    border-bottom-right-radius: 10px;
-  }
\ No newline at end of file
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.module.css
deleted file mode 100644
index 888d6db07b..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.module.css
+++ /dev/null
@@ -1,137 +0,0 @@
-.custom-table {
-  border-collapse: separate;
-  border-spacing: 0;
-  width: 100%;
-}
-
-.custom-table tbody tr {
-  background-color: rgb(248, 248, 248);
-  border: 1px solid rgb(219, 241, 232);
-  border-radius: 10px;
-}
-
-.custom-table tbody tr:hover {
-  cursor: pointer;
-  background-color: rgb(235, 240, 237);
-}
-
-.custom-table th {
-  background-color: rgb(240, 235, 235);
-  border-style: none;
-  border-bottom: solid 1px rgb(155, 153, 153);
-  padding: 10px;
-}
-
-.custom-table td {
-  min-height: 2rem;
-  border-style: none;
-  border-bottom: solid 2px rgb(255, 255, 255);
-  padding: 1.5rem 0.5rem;
-  text-align: center;
-}
-
-.custom-table tr {
-  min-height: 20rem;
-  background-color: rgb(90, 103, 116);
-  padding: 1rem;
-}
-
-.custom-table tr:first-child th:first-child {
-  border-top-left-radius: 10px;
-}
-
-.custom-table tr:first-child th:last-child {
-  border-top-right-radius: 10px;
-}
-
-.custom-table tr:last-child td:first-child {
-  border-bottom-left-radius: 10px;
-}
-
-.custom-table tr:last-child td:last-child {
-  border-bottom-right-radius: 10px;
-}
-
-@media (max-width: 1699px) {
-  table {
-    width: 100%;
-    margin-bottom: 0.75rem;
-    table-layout: fixed;
-  }
-
-  table:hover td:nth-child(even) {
-    cursor: pointer;
-    background-color: rgb(235, 240, 237);
-  }
-
-  td {
-    position: relative;
-    overflow: hidden;
-    white-space: nowrap;
-    text-overflow: ellipsis;
-    padding: 0.75rem;
-  }
-
-  .table-rwd {
-    border: solid 1px rgb(223, 218, 218);
-    overflow: hidden;
-  }
-
-  .table-rwd td {
-    border-bottom: solid 2px rgb(253, 253, 253);
-  }
-
-  .table-rwd-heading {
-    background-color: rgb(240, 235, 235);
-    border-style: none;
-    padding: 0.5rem;
-    width: 140px;
-    font-weight: 700;
-    font-size: 0.9rem;
-    border-right: solid 1px rgb(223, 218, 218);
-  }
-
-  .table-rwd:first-child {
-    border-top-left-radius: 10px;
-  }
-
-  .table-rwd:first-child {
-    border-top-right-radius: 10px;
-  }
-
-  .table-rwd:last-child {
-    border-bottom-left-radius: 10px;
-  }
-
-  .table-rwd:last-child {
-    border-bottom-right-radius: 10px;
-  }
-
-  .table-rwd tr:last-child > td:nth-last-of-type(2) {
-    border-bottom: 0;
-  }
-
-  .table-rwd tr:last-child > td:last-of-type {
-    border-bottom: 0;
-  }
-
-  .table-rwd td:last-child {
-    text-align: left;
-  }
-
-  tr:first-child th:first-child {
-    border-top-left-radius: 0;
-  }
-
-  tr:first-child th:last-child {
-    border-top-right-radius: 0;
-  }
-
-  tr:last-child td:first-child {
-    border-bottom-left-radius: 0;
-  }
-
-  tr:last-child td:last-child {
-    border-bottom-right-radius: 0;
-  }
-}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.tsx
deleted file mode 100644
index e3feb54943..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/CustomTable.tsx
+++ /dev/null
@@ -1,127 +0,0 @@
-import EmptyTablePlaceholder from "./EmptyTablePlaceholder/EmptyTablePlaceholder";
-import styles from "./CustomTable.module.css";
-import {
-  useState,
-  useEffect,
-  ReactElement,
-  JSXElementConstructor,
-  ReactNode,
-  ReactPortal,
-} from "react";
-import { TableProperty } from "../../common/supabase-types";
-
-function CustomTable(props: any) {
-  const [viewport, setViewport] = useState("");
-
-  useEffect(() => {
-    const screenResized = () =>
-      setViewport(window.innerWidth <= 1699 ? "small" : "wide");
-    screenResized();
-    window.addEventListener("resize", screenResized, true);
-    return () => {
-      window.removeEventListener("resize", screenResized, true);
-    };
-  }, [viewport]);
-
-  const getObjPropVal = (objProp: any[], row: any) => {
-    if (objProp.length === 1) return row[objProp[0]];
-    else {
-      return objProp.map((prop) => (
-        <>
-          {row[prop]}
-          <br></br>
-        </>
-      ));
-    }
-  };
-
-  const handleRowClick = (row: any) => {
-    props.cols.onClick.action(row[props.cols.onClick.prop]);
-  };
-
-  return (
-    <>
-      {props.data.length === 0 ? (
-        <EmptyTablePlaceholder />
-      ) : (
-        <>
-          {viewport === "wide" && (
-            <table className={styles["custom-table"]}>
-              <thead>
-                <tr>
-                  {props.cols.schema.map((col: any) => (
-                    <th>{col.display}</th>
-                  ))}
-                </tr>
-              </thead>
-              <tbody>
-                {props.data.map((row: any) => {
-                  return (
-                    <tr>
-                      {props.cols.schema.map((col: TableProperty) => (
-                        <td onClick={() => handleRowClick(row)}>
-                          {getObjPropVal(col.objProp, row)}
-                        </td>
-                      ))}
-                    </tr>
-                  );
-                })}
-              </tbody>
-            </table>
-          )}
-
-          {viewport === "small" && (
-            <>
-              {props.data.map((row: any) => {
-                return (
-                  <table
-                    className={`${styles["custom-table"]} ${styles["table-rwd"]}`}
-                    onClick={() => handleRowClick(row)}
-                  >
-                    <tbody>
-                      {props.cols.schema.map(
-                        (
-                          heading: {
-                            display:
-                              | string
-                              | number
-                              | boolean
-                              | ReactElement<
-                                  any,
-                                  string | JSXElementConstructor<any>
-                                >
-                              | Iterable<ReactNode>
-                              | ReactPortal
-                              | null
-                              | undefined;
-                          },
-                          idx: string | number,
-                        ) => {
-                          return (
-                            <tr>
-                              <td className={styles["table-rwd-heading"]}>
-                                {heading.display}
-                              </td>
-                              <td>
-                                {getObjPropVal(
-                                  props.cols.schema[idx].objProp,
-                                  row,
-                                )}
-                              </td>
-                            </tr>
-                          );
-                        },
-                      )}
-                    </tbody>
-                  </table>
-                );
-              })}
-            </>
-          )}
-        </>
-      )}
-    </>
-  );
-}
-
-export default CustomTable;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.module.css
deleted file mode 100644
index 8d9bbfa661..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.module.css
+++ /dev/null
@@ -1,7 +0,0 @@
-.placeholder-container {
-    display: flex;
-    justify-content: center;
-    font-size: 2rem;
-    font-weight: bold;
-    color: rgb(9, 75, 9);
-}
\ No newline at end of file
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.tsx
deleted file mode 100644
index f788bcd3ab..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/EmptyTablePlaceholder/EmptyTablePlaceholder.tsx
+++ /dev/null
@@ -1,9 +0,0 @@
-import styles from "./EmptyTablePlaceholder.module.css";
-
-function EmptyTablePlaceholder() {
-  return (
-    <div className={styles["placeholder-container"]}>No data available</div>
-  );
-}
-
-export default EmptyTablePlaceholder;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.module.css
deleted file mode 100644
index 0632e5e845..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.module.css
+++ /dev/null
@@ -1,51 +0,0 @@
-.pagination {
-  width: 100%;
-  padding: 1rem;
-  justify-content: flex-end;
-  display: flex;
-  align-items: center;
-  gap: 10px;
-}
-
-.pagination-counter {
-  height: 2.5rem;
-  display: flex;
-  align-items: center;
-  padding: 0 1rem;
-  border-radius: 10px;
-  border: 1px solid rgb(204, 206, 205);
-}
-
-.pagination-jump {
-  display: flex;
-  gap: 10px;
-  padding: 9px 1rem;
-  background-color: rgb(233, 229, 229);
-  border-radius: 10px;
-}
-
-input {
-  border-radius: 10px;
-  border: 1px solid rgb(54, 51, 224);
-  padding: 0 0.5rem;
-  width: 7rem;
-  text-align: center;
-  font-size: 1rem;
-}
-
-@media (max-width: 1699px) {
-  .pagination {
-    padding: 0;
-    justify-content: center;
-    position: relative;
-  }
-
-  .pagination button {
-    min-width: 85px;
-  }
-
-  .pagination-jump {
-    position: absolute;
-    top: 2.75rem;
-  }
-}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.tsx
deleted file mode 100644
index 671f847bd5..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Pagination.tsx
+++ /dev/null
@@ -1,63 +0,0 @@
-import { useState } from "react";
-import Button from "./Button";
-import KeyboardArrowLeftIcon from "@mui/icons-material/KeyboardArrowLeft";
-import KeyboardArrowRightIcon from "@mui/icons-material/KeyboardArrowRight";
-import KeyboardDoubleArrowLeftIcon from "@mui/icons-material/KeyboardDoubleArrowLeft";
-import KeyboardDoubleArrowRightIcon from "@mui/icons-material/KeyboardDoubleArrowRight";
-import styles from "./Pagination.module.css";
-
-type pagination = {
-  current: number;
-  total: number;
-  goToPage: (pageNumber: number) => void;
-  goNextPage: () => void;
-  goPrevPage: () => void;
-};
-
-function Pagination(props: any) {
-  let inputRef: any;
-  const getInputValue = () =>
-    inputRef?.value ? inputRef.value : props.current;
-  const [goToPageVisible, setGoToPageVisible] = useState<boolean>(false);
-
-  return (
-    <div className={styles.pagination}>
-      <Button onClick={() => props.goToPage(1)}>
-        <KeyboardDoubleArrowLeftIcon />
-      </Button>
-      <Button onClick={() => props.goPrevPage()}>
-        <KeyboardArrowLeftIcon />
-      </Button>
-      <Button onClick={() => setGoToPageVisible((prev) => !prev)}>
-        {" "}
-        <span>
-          {props.current} / {props.total}
-        </span>
-      </Button>
-      {goToPageVisible === true && (
-        <div className={styles["pagination-jump"]}>
-          <input
-            ref={inputRef}
-            id="number"
-            type="number"
-            min={1}
-            max={props.total}
-            placeholder={"Page"}
-            disabled={inputRef?.value}
-          />
-          <Button onClick={() => props.goToPage(getInputValue())}>
-            Go to page
-          </Button>
-        </div>
-      )}
-      <Button onClick={() => props.goNextPage()}>
-        <KeyboardArrowRightIcon />
-      </Button>
-      <Button onClick={() => props.goToPage(props.total)}>
-        <KeyboardDoubleArrowRightIcon />
-      </Button>
-    </div>
-  );
-}
-
-export default Pagination;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.module.css b/packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.module.css
deleted file mode 100644
index 13b6c8e9ac..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.module.css
+++ /dev/null
@@ -1,43 +0,0 @@
-.input {
-  border: none;
-  border-radius: 10px;
-  width: 30rem;
-  height: 2.5rem;
-  background-color: rgb(240, 236, 236);
-  padding: 1rem;
-  font-size: 16px;
-}
-
-.input-wrapper {
-  position: relative;
-}
-
-.input-reset {
-  background: transparent;
-  position: absolute;
-  right: 0.5rem;
-  border: none;
-  height: 2.5rem;
-  width: 2.5rem;
-  font-size: 1.5rem;
-  cursor: pointer;
-}
-
-.input-reset:hover {
-  color: green;
-}
-.input-reset-icon {
-  pointer-events: none;
-}
-
-@media (max-width: 1699px) {
-  .input {
-    width: 12rem;
-    text-align: left;
-  }
-
-  .input-reset {
-    right: 0;
-    top: 0.1rem;
-  }
-}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.tsx
deleted file mode 100644
index a8f8ee77f1..0000000000
--- a/packages/cacti-ledger-browser/src/main/typescript/components/ui/Search.tsx
+++ /dev/null
@@ -1,38 +0,0 @@
-import React, { useState } from "react";
-import styles from "./Search.module.css";
-
-function Search(props: any) {
-  const [val, setValue] = useState<string>("");
-
-  const handleInput = (e: InputEvent | ClipboardEvent | React.FormEvent<any>) => {
-    const inputValue = (e.currentTarget as HTMLInputElement).value;
-    if (inputValue) {
-      setValue(inputValue);
-      props.onKeyUp(inputValue);
-    }
-  };
-
-  const handleReset = () => {
-    setValue("");
-    props.onKeyUp("");
-  };
-
-  return (
-    <div className={styles["input-wrapper"]}>
-      <input
-        className={styles["input"]}
-        type={props.type}
-        placeholder={props.placeholder}
-        maxLength={32}
-        value={val}
-        onInput={(e) => handleInput(e)}
-        onPaste={(e) => handleInput(e)}
-      />
-      <button className={styles["input-reset"]} onClick={handleReset}>
-        <i className={styles["input-reset-icon"]}>{/* <BiRegularReset /> */}</i>
-      </button>
-    </div>
-  );
-}
-
-export default Search;
diff --git a/packages/cacti-ledger-browser/src/main/typescript/apps/fabric/components/ui/StackedRowItems.tsx b/packages/cacti-ledger-browser/src/main/typescript/components/ui/StackedRowItems.tsx
similarity index 100%
rename from packages/cacti-ledger-browser/src/main/typescript/apps/fabric/components/ui/StackedRowItems.tsx
rename to packages/cacti-ledger-browser/src/main/typescript/components/ui/StackedRowItems.tsx
diff --git a/packages/cacti-ledger-browser/src/main/typescript/main.tsx b/packages/cacti-ledger-browser/src/main/typescript/main.tsx
index d35d7168b4..610c719003 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/main.tsx
+++ b/packages/cacti-ledger-browser/src/main/typescript/main.tsx
@@ -1,3 +1,6 @@
+// Needed to fix vite caching error of MUI - see https://github.com/vitejs/vite/issues/12423
+import "@mui/material/styles/styled";
+
 import * as React from "react";
 import * as ReactDOM from "react-dom/client";
 import { appConfig } from "./common/config";
diff --git a/packages/cacti-ledger-browser/src/main/typescript/pages/home/AppCard.tsx b/packages/cacti-ledger-browser/src/main/typescript/pages/home/AppCard.tsx
new file mode 100644
index 0000000000..5386824c74
--- /dev/null
+++ b/packages/cacti-ledger-browser/src/main/typescript/pages/home/AppCard.tsx
@@ -0,0 +1,151 @@
+import React from "react";
+import { useNavigate } from "react-router-dom";
+import { useTheme } from "@mui/material/styles";
+import Dialog from "@mui/material/Dialog";
+import DialogTitle from "@mui/material/DialogTitle";
+import DialogContent from "@mui/material/DialogContent";
+import Card from "@mui/material/Card";
+import CardActionArea from "@mui/material/CardActionArea";
+import CardActions from "@mui/material/CardActions";
+import CardContent from "@mui/material/CardContent";
+import CircularProgress from "@mui/material/CircularProgress";
+import Button from "@mui/material/Button";
+import Typography from "@mui/material/Typography";
+
+import { AppConfig, AppStatus } from "../../common/types/app";
+
+type StatusTextProps = {
+  status: AppStatus;
+};
+
+/**
+ * Application status text with color according to it's severity.
+ */
+function StatusText({ status }: StatusTextProps) {
+  const theme = useTheme();
+
+  return (
+    <span style={{ color: theme.palette[status.severity].main }}>
+      {status.message}
+    </span>
+  );
+}
+
+type InitializedTextProps = {
+  isInitialized: boolean;
+};
+
+/**
+ * Application initialization status text - `error` color if not initialized, `success` otherwise.
+ */
+function InitializedText({ isInitialized }: InitializedTextProps) {
+  let text = "No";
+  let textColor: "error" | "success" = "error";
+
+  if (isInitialized) {
+    text = "Yes";
+    textColor = "success";
+  }
+
+  return <StatusText status={{ severity: textColor, message: text }} />;
+}
+
+type StatusDialogButtonProps = {
+  statusComponent: React.ReactElement;
+};
+
+function StatusDialogButton({ statusComponent }: StatusDialogButtonProps) {
+  const [openDialog, setOpenDialog] = React.useState(false);
+
+  return (
+    <>
+      <Button onClick={() => setOpenDialog(true)}>Status</Button>
+      <Dialog
+        fullWidth
+        maxWidth="sm"
+        onClose={() => setOpenDialog(false)}
+        open={openDialog}
+      >
+        <DialogTitle color="primary">App Status</DialogTitle>
+        <DialogContent>{statusComponent}</DialogContent>
+      </Dialog>
+    </>
+  );
+}
+
+type AppCardProps = {
+  appConfig: AppConfig;
+};
+
+/**
+ * Application card component. Shows basic information and allows navigation to
+ * specific app on click. Has action for showing app status and configuration
+ * pop-ups.
+ */
+export default function AppCard({ appConfig }: AppCardProps) {
+  const navigate = useNavigate();
+  const theme = useTheme();
+  const status = appConfig.useAppStatus();
+
+  return (
+    <Card
+      variant="outlined"
+      sx={{
+        display: "flex",
+        flexDirection: "column",
+        width: 400,
+      }}
+    >
+      <CardActionArea
+        onClick={() => {
+          navigate(appConfig.options.path);
+        }}
+      >
+        <CardContent
+          sx={{
+            flex: 1,
+            paddingBottom: 1,
+          }}
+        >
+          <Typography variant="h5" component="div" color="secondary.main">
+            {appConfig.options.instanceName}
+          </Typography>
+          <Typography sx={{ mb: 1.5 }} color="text.secondary">
+            {appConfig.appName}
+          </Typography>
+          {appConfig.options.description && (
+            <Typography sx={{ mb: 1.5 }}>
+              {appConfig.options.description}
+            </Typography>
+          )}
+          <Typography>
+            Initialized:{" "}
+            {status.isPending ? (
+              <CircularProgress size={17} />
+            ) : (
+              <InitializedText isInitialized={status.isInitialized} />
+            )}
+          </Typography>
+          <Typography>
+            Status:{" "}
+            {status.isPending ? (
+              <CircularProgress size={17} />
+            ) : (
+              <StatusText status={status.status} />
+            )}
+          </Typography>
+        </CardContent>
+      </CardActionArea>
+      <CardActions
+        sx={{
+          marginTop: 0,
+          justifyContent: "right",
+          borderTop: 1,
+          borderColor: theme.palette.primary.main,
+        }}
+      >
+        <StatusDialogButton statusComponent={appConfig.StatusComponent} />
+      </CardActions>
+    </Card>
+  );
+}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/pages/home/HomePage.tsx b/packages/cacti-ledger-browser/src/main/typescript/pages/home/HomePage.tsx
new file mode 100644
index 0000000000..2539bcbb67
--- /dev/null
+++ b/packages/cacti-ledger-browser/src/main/typescript/pages/home/HomePage.tsx
@@ -0,0 +1,31 @@
+import Box from "@mui/material/Box";
+import Typography from "@mui/material/Typography";
+
+import { appConfig } from "../../common/config";
+import AppCard from "./AppCard";
+
+export default function HomePage() {
+  return (
+    <Box>
+      <Typography variant="h5" color="secondary">
+        Applications
+      </Typography>
+      <Box
+        display="flex"
+        flexWrap="wrap"
+        justifyContent="space-around"
+        gap={5}
+        padding={5}
+      >
+        {appConfig.map((a) => {
+          return (
+            <AppCard
+              key={`${a.appName}_${a.options.instanceName}`}
+              appConfig={a}
+            />
+          );
+        })}
+      </Box>
+    </Box>
+  );
+}
diff --git a/packages/cacti-ledger-browser/src/main/typescript/theme.ts b/packages/cacti-ledger-browser/src/main/typescript/theme.ts
index 3c8273ac2b..1a8ec83c1b 100644
--- a/packages/cacti-ledger-browser/src/main/typescript/theme.ts
+++ b/packages/cacti-ledger-browser/src/main/typescript/theme.ts
@@ -12,5 +12,8 @@ export const themeOptions: ThemeOptions = {
     warning: {
       main: "#D68C45",
     },
+    info: {
+      main: "#5D4037",
+    },
   },
 };