Compare commits

...

4 Commits

Author SHA1 Message Date
Eduard Gert
a04e3afccb Show "Never" when a user never logged in instead of a date (#335)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2024-02-16 12:15:32 +01:00
Eduard Gert
bca327e4cf Add better search for network-routes by group name (#336) 2024-02-16 12:15:14 +01:00
Maycon Santos
6c74506316 Add templates for bugs and for feature request (#333) 2024-02-14 13:43:27 +01:00
Eduard Gert
663d7ea58c Add check to call initial users only once in dev mode (#332)
Some checks failed
build and push / build_n_push (push) Has been cancelled
2024-02-13 15:11:37 +01:00
8 changed files with 140 additions and 13 deletions

View File

@@ -0,0 +1,44 @@
---
name: Bug/Issue report
about: Create a report to help us improve
title: ''
labels: ['needs-triage']
assignees: ''
---
**Describe the problem**
A clear and concise description of what the problem is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Are you using NetBird Cloud?**
Please specify whether you use NetBird Cloud or self-host NetBird's control plane.
**NetBird version**
`netbird version`
**NetBird status -d output:**
If applicable, add the `netbird status -d' command output.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.

View File

@@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ['feature-request','needs-triage']
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -252,6 +252,9 @@ function UserOverview({ user }: Props) {
function UserInformationCard({ user }: { user: User }) {
const isServiceUser = user.is_service_user || false;
const neverLoggedIn = dayjs(user.last_login).isBefore(
dayjs().subtract(1000, "years"),
);
return (
<Card>
@@ -307,10 +310,12 @@ function UserInformationCard({ user }: { user: User }) {
</>
}
value={
dayjs(user.last_login).format("D MMMM, YYYY [at] h:mm A") +
" (" +
dayjs().to(user.last_login) +
")"
neverLoggedIn
? "Never"
: dayjs(user.last_login).format("D MMMM, YYYY [at] h:mm A") +
" (" +
dayjs().to(user.last_login) +
")"
}
/>
</>

View File

@@ -3,7 +3,7 @@ import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useApiCall } from "@utils/api";
import { useIsMd } from "@utils/responsive";
import { getLatestNetbirdRelease } from "@utils/version";
import React, { useContext, useEffect, useMemo, useState } from "react";
import React, { useContext, useEffect, useMemo, useRef, useState } from "react";
import { useLocalStorage } from "@/hooks/useLocalStorage";
import { User } from "@/interfaces/User";
import type { NetbirdRelease } from "@/interfaces/Version";
@@ -31,12 +31,16 @@ export default function ApplicationProvider({ children }: Props) {
const isMd = useIsMd();
const userRequest = useApiCall<User[]>("/users", true);
const [show, setShow] = useState(false);
const requestCalled = useRef(false);
useEffect(() => {
userRequest
.get()
.then(() => setShow(true))
.catch(() => setShow(true));
if (!requestCalled.current) {
userRequest
.get()
.then(() => setShow(true))
.catch(() => setShow(true));
requestCalled.current = true;
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

View File

@@ -12,6 +12,7 @@ export interface Route {
peer_groups?: string[];
routesGroups?: string[];
groupedRoutes?: GroupedRoute[];
group_names?: string[];
}
export interface GroupedRoute {
@@ -22,5 +23,6 @@ export interface GroupedRoute {
high_availability_count: number;
is_using_route_groups: boolean;
routes?: Route[];
group_names?: string[];
description?: string;
}

View File

@@ -43,6 +43,12 @@ export const GroupedRouteTableColumns: ColumnDef<GroupedRoute>[] = [
accessorKey: "enabled",
sortingFn: "basic",
},
{
id: "group_names",
accessorFn: (row) => {
return row.group_names?.map((name) => name).join(", ");
},
},
{
accessorKey: "network",
header: ({ column }) => {
@@ -121,10 +127,11 @@ export default function NetworkRoutesTable({
setSorting={setSorting}
columns={GroupedRouteTableColumns}
data={groupedRoutes}
searchPlaceholder={"Search by network, range or name..."}
searchPlaceholder={"Search by network, range, name or groups..."}
columnVisibility={{
enabled: false,
description: false,
group_names: false,
}}
renderExpandedRow={(row) => {
const data = cloneDeep(row);

View File

@@ -41,6 +41,19 @@ export default function useGroupedRoutes({ routes }: Props) {
const countEnabledPeers = peerRoutes.filter((r) => r.enabled).length;
const allPeers = countPeersOfGroup + countEnabledPeers;
// Get the group names for better search results
const peerGroupNames =
groupPeerRoute?.peer_groups?.map((id) => {
return groups?.find((g) => g.id == id)?.name || "";
}) || [];
const routeGroups = routes.map((r) => r.groups).flat();
const distributionGroupNames = routeGroups.map((group) => {
return groups?.find((g) => g.id == group)?.name || "";
});
const allGroupNames = [...peerGroupNames, ...distributionGroupNames];
results.push({
id,
enabled: routes.find((r) => r.enabled) != undefined,
@@ -50,6 +63,7 @@ export default function useGroupedRoutes({ routes }: Props) {
is_using_route_groups: !!groupPeerRoute,
description: groupPeerRoute ? groupPeerRoute?.description : undefined,
routes: routes,
group_names: allGroupNames,
});
});
return results;

View File

@@ -1,7 +1,8 @@
import { DataTable } from "@components/table/DataTable";
import DataTableHeader from "@components/table/DataTableHeader";
import { ColumnDef, SortingState } from "@tanstack/react-table";
import React, { useState } from "react";
import React, { useMemo, useState } from "react";
import { useGroups } from "@/contexts/GroupsProvider";
import { GroupedRoute, Route } from "@/interfaces/Route";
import RouteActionCell from "@/modules/routes/RouteActionCell";
import RouteActiveCell from "@/modules/routes/RouteActiveCell";
@@ -39,7 +40,6 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
),
cell: ({ row }) => <RouteActiveCell route={row.original} />,
},
{
id: "groups",
accessorFn: (r) => r.groups?.length,
@@ -50,6 +50,12 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
},
cell: ({ row }) => <RouteDistributionGroupsCell route={row.original} />,
},
{
id: "group_names",
accessorFn: (row) => {
return row.group_names?.map((name) => name).join(", ");
},
},
{
accessorKey: "id",
header: "",
@@ -58,6 +64,8 @@ export const RouteTableColumns: ColumnDef<Route>[] = [
];
export default function RouteTable({ row }: Props) {
const { groups } = useGroups();
// Default sorting state of the table
const [sorting, setSorting] = useState<SortingState>([
{
@@ -74,6 +82,26 @@ export default function RouteTable({ row }: Props) {
const [currentRow, setCurrentRow] = useState<Route>();
const [currentCellClicked, setCurrentCellClicked] = useState("");
const data = useMemo(() => {
if (!row.routes) return [];
// Get the group names for better search results
return row.routes.map((route) => {
const distributionGroupNames =
route.groups?.map((id) => {
return groups?.find((g) => g.id === id)?.name || "";
}) || [];
const peerGroupNames =
route.peer_groups?.map((id) => {
return groups?.find((g) => g.id === id)?.name || "";
}) || [];
const allGroupNames = [...distributionGroupNames, ...peerGroupNames];
return {
...route,
group_names: allGroupNames,
} as Route;
});
}, [row.routes, groups]);
return (
<>
{editModal && currentRow && (
@@ -92,6 +120,9 @@ export default function RouteTable({ row }: Props) {
text={"Network Routes"}
manualPagination={true}
sorting={sorting}
columnVisibility={{
group_names: false,
}}
onRowClick={(row, cell) => {
setCurrentRow(row.original);
setEditModal(true);
@@ -99,7 +130,7 @@ export default function RouteTable({ row }: Props) {
}}
setSorting={setSorting}
columns={RouteTableColumns}
data={row.routes}
data={data}
/>
</>
);