Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
"@raystack/proton": "^0.1.0-434b8aec0c95625a6633f4e890be311d3e0fefef",
"@stitches/react": "^1.2.8",
"@tanstack/react-query": "^5.83.0",
"@tanstack/react-query-devtools": "^5.90.2",
"@tanstack/react-table": "^8.9.3",
"@tanstack/table-core": "^8.21.3",
"axios": "^1.8.4",
Expand Down
20 changes: 20 additions & 0 deletions ui/pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 3 additions & 3 deletions ui/src/assets/icons/cpu-chip.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ui/src/assets/images/service-user.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
28 changes: 14 additions & 14 deletions ui/src/components/Sidebar/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import PlansIcon from "~/assets/icons/plans.svg?react";
import WebhooksIcon from "~/assets/icons/webhooks.svg?react";
import PreferencesIcon from "~/assets/icons/preferences.svg?react";
import AdminsIcon from "~/assets/icons/admins.svg?react";
import CpuChipIcon from "~/assets/icons/cpu-chip.svg?react";
import { AppContext } from "~/contexts/App";
import { MoonIcon, SunIcon } from "@radix-ui/react-icons";
import { Link, useLocation } from "react-router-dom";
Expand All @@ -46,6 +47,11 @@ const navigationItems: NavigationItemsTypes[] = [
to: `/users`,
icon: <UserIcon />,
},
{
name: "Audit Logs",
to: `/audit-logs`,
icon: <CpuChipIcon />,
},
{
name: "Invoices",
to: `/invoices`,
Expand Down Expand Up @@ -132,21 +138,19 @@ export default function IAMSidebar() {
</Text>
</Sidebar.Header>
<Sidebar.Main>
{navigationItems.map((nav) => {
{navigationItems.map(nav => {
return nav?.subItems?.length ? (
<Sidebar.Group
label={nav.name}
key={nav.name}
className={styles["sidebar-group"]}
>
{nav.subItems?.map((subItem) => (
className={styles["sidebar-group"]}>
{nav.subItems?.map(subItem => (
<Sidebar.Item
leadingIcon={subItem.icon}
key={subItem.name}
active={isActive(subItem.to)}
data-test-id={`admin-ui-sidebar-navigation-cell-${subItem.name}`}
as={<Link to={subItem?.to ?? ""} />}
>
as={<Link to={subItem?.to ?? ""} />}>
{subItem.name}
</Sidebar.Item>
))}
Expand All @@ -157,8 +161,7 @@ export default function IAMSidebar() {
key={nav.name}
active={isActive(nav.to)}
data-test-id={`admin-ui-sidebar-navigation-cell-${nav.name}`}
as={<Link to={nav?.to ?? ""} />}
>
as={<Link to={nav?.to ?? ""} />}>
{nav.name}
</Sidebar.Item>
);
Expand Down Expand Up @@ -203,22 +206,19 @@ function UserDropdown() {
leadingIcon={
<Avatar src={user?.avatar} fallback={userInital} size={3} />
}
data-test-id="frontier-sdk-sidebar-logout"
>
data-test-id="frontier-sdk-sidebar-logout">
{user?.email}
</Sidebar.Item>
</DropdownMenu.Trigger>
<DropdownMenu.Content>
<DropdownMenu.Item
onClick={toggleTheme}
data-test-id="admin-ui-toggle-theme"
>
data-test-id="admin-ui-toggle-theme">
{themeData.icon} {themeData.label}
</DropdownMenu.Item>
<DropdownMenu.Item
onClick={() => logoutMutation.mutate({})}
data-test-id="admin-ui-logout-btn"
>
data-test-id="admin-ui-logout-btn">
Logout
</DropdownMenu.Item>
</DropdownMenu.Content>
Expand Down
6 changes: 3 additions & 3 deletions ui/src/contexts/ConnectProvider.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { ReactQueryDevtools } from "@tanstack/react-query-devtools";
import type { ReactNode } from "react";
import { TransportProvider } from "@connectrpc/connect-query";
import { jsonTransport as transport } from "~/connect/transport";
Expand All @@ -20,9 +21,8 @@ interface ConnectProviderProps {
export function ConnectProvider({ children }: ConnectProviderProps) {
return (
<QueryClientProvider client={queryClient}>
<TransportProvider transport={transport}>
{children}
</TransportProvider>
<TransportProvider transport={transport}>{children}</TransportProvider>
<ReactQueryDevtools initialIsOpen={false} />
</QueryClientProvider>
);
}
Expand Down
131 changes: 131 additions & 0 deletions ui/src/pages/audit-logs/list/columns.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import {
Avatar,
Badge,
DataTableColumnDef,
Flex,
getAvatarColor,
Text,
} from "@raystack/apsara";
import dayjs from "dayjs";
import styles from "./list.module.css";
import {
AuditRecord,
AuditRecordActor,
AuditRecordResource,
} from "@raystack/proton/frontier";
import {
isNullTimestamp,
TimeStamp,
timestampToDate,
} from "~/utils/connect-timestamp";
import {
getActionBadgeColor,
getAuditLogActorName,
isAuditLogActorServiceUser,
} from "../util";
import serviceUserIcon from "~/assets/images/service-user.jpg";
import { OrganizationCell } from "./organization-cell";
import { ComponentPropsWithoutRef } from "react";

interface getColumnsOptions {
groupCountMap: Record<string, Record<string, number>>;
}

export const getColumns = ({
groupCountMap,
}: getColumnsOptions): DataTableColumnDef<AuditRecord, unknown>[] => {
return [
{
accessorKey: "actor",
header: "Actor",
classNames: {
cell: styles["name-column"],
header: styles["name-column"],
},
cell: ({ getValue }) => {
const value = getValue() as AuditRecordActor;
const name = getAuditLogActorName(value);
const isServiceUser = isAuditLogActorServiceUser(value);

return (
<Flex gap={4} align="center">
<Avatar
size={3}
fallback={name?.[0]?.toUpperCase()}
color={getAvatarColor(value?.id ?? "")}
radius="full"
src={isServiceUser ? serviceUserIcon : undefined}
/>
<Text size="regular">{name}</Text>
</Flex>
);
},
},
{
accessorKey: "orgId",
header: "Organization",
classNames: {
cell: styles["org-column"],
header: styles["org-column"],
},
cell: ({ getValue }) => {
return <OrganizationCell id={getValue() as string} />;
},
enableColumnFilter: true,
},
{
accessorKey: "event",
header: "Action",
cell: ({ getValue }) => {
const value = getValue() as string;
const color = getActionBadgeColor(value) as ComponentPropsWithoutRef<
typeof Badge
>["variant"];
return <Badge variant={color}>{value}</Badge>;
},
enableColumnFilter: true,
enableSorting: true,
},
{
accessorKey: "resource",
header: "Resource",
cell: ({ getValue }) => {
const value = getValue() as AuditRecordResource;
return (
<Flex gap={1} direction="column" className={styles.capitalize}>
<Text size="small" weight="medium">
{value.name}
</Text>
<Text size="small" variant="secondary">
{value.type.toLowerCase()}
</Text>
</Flex>
);
},
},
{
accessorKey: "occurredAt",
header: "Timestamp",
filterType: "date",
cell: ({ getValue }) => {
const value = getValue() as TimeStamp;
if (isNullTimestamp(value)) {
return <Text>-</Text>;
}
const date = dayjs(timestampToDate(value));
return (
<Flex gap={1} direction="column">
<Text size="small" weight="medium">
{date.format("DD MMM YYYY")}
</Text>
<Text size="small" variant="secondary">
{date.format("hh:mm A")}
</Text>
</Flex>
);
},
enableHiding: true,
enableSorting: true,
},
];
};
1 change: 1 addition & 0 deletions ui/src/pages/audit-logs/list/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { AuditLogsList } from "./list";
63 changes: 63 additions & 0 deletions ui/src/pages/audit-logs/list/list.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
.navbar {
padding: var(--rs-space-4) var(--rs-space-7);
border-bottom: 0.5px solid var(--rs-color-border-base-primary);
background: var(--rs-color-background-base-primary);
display: flex;
align-items: center;
justify-content: space-between;
}

.table {
height: auto;
}

.table-empty {
height: 100%;
}

.empty-state {
height: 100%;
}

.empty-state-subheading {
max-width: 360px;
text-wrap: auto;
}

.name-column {
padding-left: var(--rs-space-7);
max-width: 200px;
}
.org-column {
max-width: 200px;
}

.country-column {
max-width: 150px;
}

.table-wrapper {
flex: 1;
height: 100%;
}

.table-header {
z-index: 2;
}

.side-panel {
position: sticky;
top: 0;
}

.capitalize {
text-transform: capitalize;
}

.table-content-container {
width: 100%;
height: 100%;
max-height: calc(100vh - 90px);
overflow: scroll;
position: relative;
}
Loading
Loading