Skip to content

feat(site): add connection log page #18708

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: ethan/populate-connection-log-count
Choose a base branch
from
Open
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
8 changes: 8 additions & 0 deletions site/src/api/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1802,6 +1802,14 @@ class ApiMethods {
return response.data;
};

getConnectionLogs = async (
options: TypesGen.ConnectionLogsRequest,
): Promise<TypesGen.ConnectionLogResponse> => {
const url = getURLWithSearchParams("/api/v2/connectionlog", options);
const response = await this.axios.get(url);
return response.data;
};

getTemplateDAUs = async (
templateId: string,
): Promise<TypesGen.DAUsResponse> => {
Expand Down
24 changes: 24 additions & 0 deletions site/src/api/queries/connectionlog.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { API } from "api/api";
import type { ConnectionLogResponse } from "api/typesGenerated";
import { useFilterParamsKey } from "components/Filter/Filter";
import type { UsePaginatedQueryOptions } from "hooks/usePaginatedQuery";

export function paginatedConnectionLogs(
searchParams: URLSearchParams,
): UsePaginatedQueryOptions<ConnectionLogResponse, string> {
return {
searchParams,
queryPayload: () => searchParams.get(useFilterParamsKey) ?? "",
queryKey: ({ payload, pageNumber }) => {
return ["connectionLogs", payload, pageNumber] as const;
},
queryFn: ({ payload, limit, offset }) => {
return API.getConnectionLogs({
offset,
limit,
q: payload,
});
},
prefetch: false,
};
}
5 changes: 3 additions & 2 deletions site/src/components/Filter/UserFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,14 +82,15 @@ export type UserFilterMenu = ReturnType<typeof useUserFilterMenu>;

interface UserMenuProps {
menu: UserFilterMenu;
placeholder?: string;
width?: number;
}

export const UserMenu: FC<UserMenuProps> = ({ menu, width }) => {
export const UserMenu: FC<UserMenuProps> = ({ menu, width, placeholder }) => {
return (
<SelectFilter
label="Select user"
placeholder="All users"
placeholder={placeholder ?? "All users"}
emptyText="No users found"
options={menu.searchOptions}
onSelect={menu.selectOption}
Expand Down
43 changes: 43 additions & 0 deletions site/src/components/StatusPill/StatusPill.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import { Pill } from "components/Pill/Pill";
import {
Tooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from "components/Tooltip/Tooltip";
import type { FC } from "react";
import { httpStatusColor } from "utils/http";

interface StatusPillProps {
code: number;
isHttpCode: boolean;
label?: string;
}

export const StatusPill: FC<StatusPillProps> = ({
code,
isHttpCode,
label,
}) => {
const pill = (
<Pill
className="text-[10px] h-5 px-2.5 font-semibold"
type={
isHttpCode ? httpStatusColor(code) : code === 0 ? "success" : "error"
}
>
{code.toString()}
</Pill>
);
if (!label) {
return pill;
}
return (
<TooltipProvider>
<Tooltip delayDuration={150}>
<TooltipTrigger asChild>{pill}</TooltipTrigger>
<TooltipContent>{label}</TooltipContent>
</Tooltip>
</TooltipProvider>
);
};
15 changes: 15 additions & 0 deletions site/src/modules/dashboard/Navbar/DeploymentDropdown.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,22 @@ interface DeploymentDropdownProps {
canViewDeployment: boolean;
canViewOrganizations: boolean;
canViewAuditLog: boolean;
canViewConnectionLog: boolean;
canViewHealth: boolean;
}

export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
canViewDeployment,
canViewOrganizations,
canViewAuditLog,
canViewConnectionLog,
canViewHealth,
}) => {
const theme = useTheme();

if (
!canViewAuditLog &&
!canViewConnectionLog &&
!canViewOrganizations &&
!canViewDeployment &&
!canViewHealth
Expand Down Expand Up @@ -59,6 +62,7 @@ export const DeploymentDropdown: FC<DeploymentDropdownProps> = ({
canViewDeployment={canViewDeployment}
canViewOrganizations={canViewOrganizations}
canViewAuditLog={canViewAuditLog}
canViewConnectionLog={canViewConnectionLog}
canViewHealth={canViewHealth}
/>
</PopoverContent>
Expand All @@ -71,6 +75,7 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
canViewOrganizations,
canViewAuditLog,
canViewHealth,
canViewConnectionLog,
}) => {
const popover = usePopover();

Expand Down Expand Up @@ -108,6 +113,16 @@ const DeploymentDropdownContent: FC<DeploymentDropdownProps> = ({
Audit Logs
</MenuItem>
)}
{canViewConnectionLog && (
<MenuItem
component={NavLink}
to="/connectionlog"
css={styles.menuItem}
onClick={onPopoverClose}
>
Connection Logs
</MenuItem>
)}
{canViewHealth && (
<MenuItem
component={NavLink}
Expand Down
10 changes: 10 additions & 0 deletions site/src/modules/dashboard/Navbar/MobileMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ type MobileMenuPermissions = {
canViewDeployment: boolean;
canViewOrganizations: boolean;
canViewAuditLog: boolean;
canViewConnectionLog: boolean;
canViewHealth: boolean;
};

Expand Down Expand Up @@ -192,6 +193,7 @@ const AdminSettingsSub: FC<MobileMenuPermissions> = ({
canViewDeployment,
canViewOrganizations,
canViewAuditLog,
canViewConnectionLog,
canViewHealth,
}) => {
const [open, setOpen] = useState(false);
Expand Down Expand Up @@ -237,6 +239,14 @@ const AdminSettingsSub: FC<MobileMenuPermissions> = ({
<Link to="/audit">Audit logs</Link>
</DropdownMenuItem>
)}
{canViewConnectionLog && (
<DropdownMenuItem
asChild
className={cn(itemStyles.default, itemStyles.sub)}
>
<Link to="/connectionlog">Connection logs</Link>
</DropdownMenuItem>
)}
{canViewHealth && (
<DropdownMenuItem
asChild
Expand Down
3 changes: 3 additions & 0 deletions site/src/modules/dashboard/Navbar/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ export const Navbar: FC = () => {
const canViewHealth = permissions.viewDebugInfo;
const canViewAuditLog =
featureVisibility.audit_log && permissions.viewAnyAuditLog;
const canViewConnectionLog =
featureVisibility.connection_log && permissions.viewAnyConnectionLog;

return (
<NavbarView
Expand All @@ -34,6 +36,7 @@ export const Navbar: FC = () => {
canViewOrganizations={canViewOrganizations}
canViewHealth={canViewHealth}
canViewAuditLog={canViewAuditLog}
canViewConnectionLog={canViewConnectionLog}
proxyContextValue={proxyContextValue}
/>
);
Expand Down
4 changes: 4 additions & 0 deletions site/src/modules/dashboard/Navbar/NavbarView.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ describe("NavbarView", () => {
canViewOrganizations
canViewHealth
canViewAuditLog
canViewConnectionLog
/>,
);
const workspacesLink =
Expand All @@ -50,6 +51,7 @@ describe("NavbarView", () => {
canViewOrganizations
canViewHealth
canViewAuditLog
canViewConnectionLog
/>,
);
const templatesLink =
Expand All @@ -67,6 +69,7 @@ describe("NavbarView", () => {
canViewOrganizations
canViewHealth
canViewAuditLog
canViewConnectionLog
/>,
);
const deploymentMenu = await screen.findByText("Admin settings");
Expand All @@ -85,6 +88,7 @@ describe("NavbarView", () => {
canViewOrganizations
canViewHealth
canViewAuditLog
canViewConnectionLog
/>,
);
const deploymentMenu = await screen.findByText("Admin settings");
Expand Down
4 changes: 4 additions & 0 deletions site/src/modules/dashboard/Navbar/NavbarView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ interface NavbarViewProps {
canViewDeployment: boolean;
canViewOrganizations: boolean;
canViewAuditLog: boolean;
canViewConnectionLog: boolean;
canViewHealth: boolean;
proxyContextValue?: ProxyContextValue;
}
Expand All @@ -44,6 +45,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewOrganizations,
canViewHealth,
canViewAuditLog,
canViewConnectionLog,
proxyContextValue,
}) => {
const webPush = useWebpushNotifications();
Expand Down Expand Up @@ -73,6 +75,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
canViewOrganizations={canViewOrganizations}
canViewDeployment={canViewDeployment}
canViewHealth={canViewHealth}
canViewConnectionLog={canViewConnectionLog}
/>
</div>

Expand Down Expand Up @@ -124,6 +127,7 @@ export const NavbarView: FC<NavbarViewProps> = ({
supportLinks={supportLinks}
onSignOut={onSignOut}
canViewAuditLog={canViewAuditLog}
canViewConnectionLog={canViewConnectionLog}
canViewOrganizations={canViewOrganizations}
canViewDeployment={canViewDeployment}
canViewHealth={canViewHealth}
Expand Down
7 changes: 7 additions & 0 deletions site/src/modules/permissions/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,13 @@ export const permissionChecks = {
},
action: "read",
},
viewAnyConnectionLog: {
object: {
resource_type: "connection_log",
any_org: true,
},
action: "read",
},
viewDebugInfo: {
object: {
resource_type: "debug_info",
Expand Down
15 changes: 11 additions & 4 deletions site/src/pages/AuditPage/AuditFilter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,10 +82,17 @@ export const useActionFilterMenu = ({
value,
onChange,
}: Pick<UseFilterMenuOptions, "value" | "onChange">) => {
const actionOptions: SelectFilterOption[] = AuditActions.map((action) => ({
value: action,
label: capitalize(action),
}));
const actionOptions: SelectFilterOption[] = AuditActions
// TODO(ethanndickson): Logs with these action types are no longer produced.
// Until we remove them from the database and API, we shouldn't suggest them
// in the filter dropdown.
.filter(
(action) => !["connect", "disconnect", "open", "close"].includes(action),
)
.map((action) => ({
value: action,
label: capitalize(action),
}));
return useFilterMenu({
onChange,
value,
Expand Down
41 changes: 2 additions & 39 deletions site/src/pages/AuditPage/AuditLogRow/AuditLogRow.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,13 @@ import Tooltip from "@mui/material/Tooltip";
import type { AuditLog } from "api/typesGenerated";
import { Avatar } from "components/Avatar/Avatar";
import { DropdownArrow } from "components/DropdownArrow/DropdownArrow";
import { Pill } from "components/Pill/Pill";
import { Stack } from "components/Stack/Stack";
import { StatusPill } from "components/StatusPill/StatusPill";
import { TimelineEntry } from "components/Timeline/TimelineEntry";
import { InfoIcon } from "lucide-react";
import { NetworkIcon } from "lucide-react";
import { type FC, useState } from "react";
import { Link as RouterLink } from "react-router-dom";
import type { ThemeRole } from "theme/roles";
import userAgentParser from "ua-parser-js";
import { AuditLogDescription } from "./AuditLogDescription/AuditLogDescription";
import { AuditLogDiff } from "./AuditLogDiff/AuditLogDiff";
Expand All @@ -22,21 +21,6 @@ import {
determineIdPSyncMappingDiff,
} from "./AuditLogDiff/auditUtils";

const httpStatusColor = (httpStatus: number): ThemeRole => {
// Treat server errors (500) as errors
if (httpStatus >= 500) {
return "error";
}

// Treat client errors (400) as warnings
if (httpStatus >= 400) {
return "warning";
}

// OK (200) and redirects (300) are successful
return "success";
};

interface AuditLogRowProps {
auditLog: AuditLog;
// Useful for Storybook
Expand Down Expand Up @@ -139,7 +123,7 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
</Stack>

<Stack direction="row" alignItems="center">
<StatusPill code={auditLog.status_code} />
<StatusPill isHttpCode={true} code={auditLog.status_code} />

{/* With multi-org, there is not enough space so show
everything in a tooltip. */}
Expand Down Expand Up @@ -243,19 +227,6 @@ export const AuditLogRow: FC<AuditLogRowProps> = ({
);
};

function StatusPill({ code }: { code: number }) {
const isHttp = code >= 100;

return (
<Pill
css={styles.statusCodePill}
type={isHttp ? httpStatusColor(code) : code === 0 ? "success" : "error"}
>
{code.toString()}
</Pill>
);
}

const styles = {
auditLogCell: {
padding: "0 !important",
Expand Down Expand Up @@ -311,14 +282,6 @@ const styles = {
width: "100%",
},

statusCodePill: {
fontSize: 10,
height: 20,
paddingLeft: 10,
paddingRight: 10,
fontWeight: 600,
},

deletedLabel: (theme) => ({
...(theme.typography.caption as CSSObject),
color: theme.palette.text.secondary,
Expand Down
Loading
Loading