Skip to content

Commit b5260d5

Browse files
feat(site): add connection log page (#18708)
This is the fourth PR for moving connection events out of the audit log. This PR adds `/connectionlog` to the frontend. This page is identical in structure to the audit log, but with different filters and contents. The connection log lists sessions, and the time they start. If we support tracking the end time of a session, and we've received a disconnect event for that session, the end timestamp is also included. Demo: https://github.com/user-attachments/assets/e0fff799-0ed6-45f7-a8c0-237839659ef9 <img width="346" alt="image" src="https://github.com/user-attachments/assets/6de29945-55c2-4fe5-9a4f-d42e476ded25" /> <img width="184" alt="image" src="https://github.com/user-attachments/assets/e83234bc-4d9d-4f71-b668-9256a600659c" /> Since the styling is identical to that of the audit log, I've continued to use MUI table components. When the audit log is migrated off MUI/restyled, this table can be too, relatively easily. Future PRs: - Write a query to delete old events from the audit log, call it from dbpurge. - Write documentation for the endpoint / feature
1 parent 1ee6b8d commit b5260d5

26 files changed

+1406
-45
lines changed

site/src/api/api.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1813,6 +1813,14 @@ class ApiMethods {
18131813
return response.data;
18141814
};
18151815

1816+
getConnectionLogs = async (
1817+
options: TypesGen.ConnectionLogsRequest,
1818+
): Promise<TypesGen.ConnectionLogResponse> => {
1819+
const url = getURLWithSearchParams("/api/v2/connectionlog", options);
1820+
const response = await this.axios.get(url);
1821+
return response.data;
1822+
};
1823+
18161824
getTemplateDAUs = async (
18171825
templateId: string,
18181826
): Promise<TypesGen.DAUsResponse> => {

site/src/api/queries/connectionlog.ts

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { API } from "api/api";
2+
import type { ConnectionLogResponse } from "api/typesGenerated";
3+
import { useFilterParamsKey } from "components/Filter/Filter";
4+
import type { UsePaginatedQueryOptions } from "hooks/usePaginatedQuery";
5+
6+
export function paginatedConnectionLogs(
7+
searchParams: URLSearchParams,
8+
): UsePaginatedQueryOptions<ConnectionLogResponse, string> {
9+
return {
10+
searchParams,
11+
queryPayload: () => searchParams.get(useFilterParamsKey) ?? "",
12+
queryKey: ({ payload, pageNumber }) => {
13+
return ["connectionLogs", payload, pageNumber] as const;
14+
},
15+
queryFn: ({ payload, limit, offset }) => {
16+
return API.getConnectionLogs({
17+
offset,
18+
limit,
19+
q: payload,
20+
});
21+
},
22+
prefetch: false,
23+
};
24+
}

site/src/components/Filter/UserFilter.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,14 +82,15 @@ export type UserFilterMenu = ReturnType<typeof useUserFilterMenu>;
8282

8383
interface UserMenuProps {
8484
menu: UserFilterMenu;
85+
placeholder?: string;
8586
width?: number;
8687
}
8788

88-
export const UserMenu: FC<UserMenuProps> = ({ menu, width }) => {
89+
export const UserMenu: FC<UserMenuProps> = ({ menu, width, placeholder }) => {
8990
return (
9091
<SelectFilter
9192
label="Select user"
92-
placeholder="All users"
93+
placeholder={placeholder ?? "All users"}
9394
emptyText="No users found"
9495
options={menu.searchOptions}
9596
onSelect={menu.selectOption}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
import { Pill } from "components/Pill/Pill";
2+
import {
3+
Tooltip,
4+
TooltipContent,
5+
TooltipProvider,
6+
TooltipTrigger,
7+
} from "components/Tooltip/Tooltip";
8+
import type { FC } from "react";
9+
import { httpStatusColor } from "utils/http";
10+
11+
interface StatusPillProps {
12+
code: number;
13+
isHttpCode: boolean;
14+
label?: string;
15+
}
16+
17+
export const StatusPill: FC<StatusPillProps> = ({
18+
code,
19+
isHttpCode,
20+
label,
21+
}) => {
22+
const pill = (
23+
<Pill
24+
className="text-[10px] h-5 px-2.5 font-semibold"
25+
type={
26+
isHttpCode ? httpStatusColor(code) : code === 0 ? "success" : "error"
27+
}
28+
>
29+
{code.toString()}
30+
</Pill>
31+
);
32+
if (!label) {
33+
return pill;
34+
}
35+
return (
36+
<TooltipProvider>
37+
<Tooltip delayDuration={150}>
38+
<TooltipTrigger asChild>{pill}</TooltipTrigger>
39+
<TooltipContent>{label}</TooltipContent>
40+
</Tooltip>
41+
</TooltipProvider>
42+
);
43+
};

site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,22 @@ interface DeploymentDropdownProps {
1616
canViewDeployment: boolean;
1717
canViewOrganizations: boolean;
1818
canViewAuditLog: boolean;
19+
canViewConnectionLog: boolean;
1920
canViewHealth: boolean;
2021
}
2122

2223
export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
2324
canViewDeployment,
2425
canViewOrganizations,
2526
canViewAuditLog,
27+
canViewConnectionLog,
2628
canViewHealth,
2729
}) => {
2830
const theme = useTheme();
2931

3032
if (
3133
!canViewAuditLog &&
34+
!canViewConnectionLog &&
3235
!canViewOrganizations &&
3336
!canViewDeployment &&
3437
!canViewHealth
@@ -59,6 +62,7 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
5962
canViewDeployment={canViewDeployment}
6063
canViewOrganizations={canViewOrganizations}
6164
canViewAuditLog={canViewAuditLog}
65+
canViewConnectionLog={canViewConnectionLog}
6266
canViewHealth={canViewHealth}
6367
/>
6468
</PopoverContent>
@@ -71,6 +75,7 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
7175
canViewOrganizations,
7276
canViewAuditLog,
7377
canViewHealth,
78+
canViewConnectionLog,
7479
}) => {
7580
const popover = usePopover();
7681

@@ -108,6 +113,16 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
108113
Audit Logs
109114
</MenuItem>
110115
)}
116+
{canViewConnectionLog && (
117+
<MenuItem
118+
component={NavLink}
119+
to="/connectionlog"
120+
css={styles.menuItem}
121+
onClick={onPopoverClose}
122+
>
123+
Connection Logs
124+
</MenuItem>
125+
)}
111126
{canViewHealth && (
112127
<MenuItem
113128
component={NavLink}

site/src/modules/dashboard/Navbar/MobileMenu.tsx

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ type MobileMenuPermissions = {
3737
canViewDeployment: boolean;
3838
canViewOrganizations: boolean;
3939
canViewAuditLog: boolean;
40+
canViewConnectionLog: boolean;
4041
canViewHealth: boolean;
4142
};
4243

@@ -192,6 +193,7 @@ const AdminSettingsSub: FC<MobileMenuPermissions> = ({
192193
canViewDeployment,
193194
canViewOrganizations,
194195
canViewAuditLog,
196+
canViewConnectionLog,
195197
canViewHealth,
196198
}) => {
197199
const [open, setOpen] = useState(false);
@@ -237,6 +239,14 @@ const AdminSettingsSub: FC<MobileMenuPermissions> = ({
237239
<Link to="/audit">Audit logs</Link>
238240
</DropdownMenuItem>
239241
)}
242+
{canViewConnectionLog && (
243+
<DropdownMenuItem
244+
asChild
245+
className={cn(itemStyles.default, itemStyles.sub)}
246+
>
247+
<Link to="/connectionlog">Connection logs</Link>
248+
</DropdownMenuItem>
249+
)}
240250
{canViewHealth && (
241251
<DropdownMenuItem
242252
asChild

site/src/modules/dashboard/Navbar/Navbar.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ export const Navbar: FC = () => {
2222
const canViewHealth = permissions.viewDebugInfo;
2323
const canViewAuditLog =
2424
featureVisibility.audit_log && permissions.viewAnyAuditLog;
25+
const canViewConnectionLog =
26+
featureVisibility.connection_log && permissions.viewAnyConnectionLog;
2527

2628
return (
2729
<NavbarView
@@ -34,6 +36,7 @@ export const Navbar: FC = () => {
3436
canViewOrganizations={canViewOrganizations}
3537
canViewHealth={canViewHealth}
3638
canViewAuditLog={canViewAuditLog}
39+
canViewConnectionLog={canViewConnectionLog}
3740
proxyContextValue={proxyContextValue}
3841
/>
3942
);

site/src/modules/dashboard/Navbar/NavbarView.test.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ describe("NavbarView", () => {
3333
canViewOrganizations
3434
canViewHealth
3535
canViewAuditLog
36+
canViewConnectionLog
3637
/>,
3738
);
3839
const workspacesLink =
@@ -50,6 +51,7 @@ describe("NavbarView", () => {
5051
canViewOrganizations
5152
canViewHealth
5253
canViewAuditLog
54+
canViewConnectionLog
5355
/>,
5456
);
5557
const templatesLink =
@@ -67,6 +69,7 @@ describe("NavbarView", () => {
6769
canViewOrganizations
6870
canViewHealth
6971
canViewAuditLog
72+
canViewConnectionLog
7073
/>,
7174
);
7275
const deploymentMenu = await screen.findByText("Admin settings");
@@ -85,6 +88,7 @@ describe("NavbarView", () => {
8588
canViewOrganizations
8689
canViewHealth
8790
canViewAuditLog
91+
canViewConnectionLog
8892
/>,
8993
);
9094
const deploymentMenu = await screen.findByText("Admin settings");

site/src/modules/dashboard/Navbar/NavbarView.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ interface NavbarViewProps {
2424
canViewDeployment: boolean;
2525
canViewOrganizations: boolean;
2626
canViewAuditLog: boolean;
27+
canViewConnectionLog: boolean;
2728
canViewHealth: boolean;
2829
proxyContextValue?: ProxyContextValue;
2930
}
@@ -44,6 +45,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
4445
canViewOrganizations,
4546
canViewHealth,
4647
canViewAuditLog,
48+
canViewConnectionLog,
4749
proxyContextValue,
4850
}) => {
4951
const webPush = useWebpushNotifications();
@@ -73,6 +75,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
7375
canViewOrganizations={canViewOrganizations}
7476
canViewDeployment={canViewDeployment}
7577
canViewHealth={canViewHealth}
78+
canViewConnectionLog={canViewConnectionLog}
7679
/>
7780
</div>
7881

@@ -124,6 +127,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
124127
supportLinks={supportLinks}
125128
onSignOut={onSignOut}
126129
canViewAuditLog={canViewAuditLog}
130+
canViewConnectionLog={canViewConnectionLog}
127131
canViewOrganizations={canViewOrganizations}
128132
canViewDeployment={canViewDeployment}
129133
canViewHealth={canViewHealth}

site/src/modules/permissions/index.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,6 +156,13 @@ export const permissionChecks = {
156156
},
157157
action: "read",
158158
},
159+
viewAnyConnectionLog: {
160+
object: {
161+
resource_type: "connection_log",
162+
any_org: true,
163+
},
164+
action: "read",
165+
},
159166
viewDebugInfo: {
160167
object: {
161168
resource_type: "debug_info",

0 commit comments

Comments
 (0)