mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0a056d69ff |
No files matched your search
@@ -2,7 +2,6 @@ name: build and push
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- "feature/**"
|
||||
- main
|
||||
tags:
|
||||
- "**"
|
||||
|
||||
@@ -26,28 +26,23 @@ import TextWithTooltip from "@components/ui/TextWithTooltip";
|
||||
import { IconCloudLock, IconInfoCircle } from "@tabler/icons-react";
|
||||
import useFetchApi from "@utils/api";
|
||||
import dayjs from "dayjs";
|
||||
import { isEmpty, trim } from "lodash";
|
||||
import { trim } from "lodash";
|
||||
import {
|
||||
Cpu,
|
||||
FlagIcon,
|
||||
Globe,
|
||||
History,
|
||||
MapPin,
|
||||
MonitorSmartphoneIcon,
|
||||
NetworkIcon,
|
||||
PencilIcon,
|
||||
TerminalSquare,
|
||||
} from "lucide-react";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
import { toASCII } from "punycode";
|
||||
import React, { useMemo, useState } from "react";
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
import { useSWRConfig } from "swr";
|
||||
import RoundedFlag from "@/assets/countries/RoundedFlag";
|
||||
import CircleIcon from "@/assets/icons/CircleIcon";
|
||||
import NetBirdIcon from "@/assets/icons/NetBirdIcon";
|
||||
import PeerIcon from "@/assets/icons/PeerIcon";
|
||||
import { useCountries } from "@/contexts/CountryProvider";
|
||||
import PeerProvider, { usePeer } from "@/contexts/PeerProvider";
|
||||
import RoutesProvider from "@/contexts/RoutesProvider";
|
||||
import { useHasChanges } from "@/hooks/useHasChanges";
|
||||
@@ -144,7 +139,7 @@ function PeerOverview() {
|
||||
<CircleIcon
|
||||
active={peer.connected}
|
||||
size={12}
|
||||
className={"mb-[3px] shrink-0"}
|
||||
className={"mb-[3px]"}
|
||||
/>
|
||||
<TextWithTooltip text={name} maxChars={30} />
|
||||
|
||||
@@ -296,12 +291,6 @@ function PeerOverview() {
|
||||
}
|
||||
|
||||
function PeerInformationCard({ peer }: { peer: Peer }) {
|
||||
const { isLoading, getRegionByPeer } = useCountries();
|
||||
|
||||
const countryText = useMemo(() => {
|
||||
return getRegionByPeer(peer);
|
||||
}, [getRegionByPeer, peer]);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<Card.List>
|
||||
@@ -315,44 +304,6 @@ function PeerInformationCard({ peer }: { peer: Peer }) {
|
||||
value={peer.ip}
|
||||
/>
|
||||
|
||||
<Card.ListItem
|
||||
label={
|
||||
<>
|
||||
<NetworkIcon size={16} />
|
||||
Public IP-Address
|
||||
</>
|
||||
}
|
||||
value={peer.connection_ip}
|
||||
/>
|
||||
|
||||
<Card.ListItem
|
||||
label={
|
||||
<>
|
||||
<FlagIcon size={16} />
|
||||
Region
|
||||
</>
|
||||
}
|
||||
tooltip={false}
|
||||
value={
|
||||
isEmpty(peer.country_code) ? (
|
||||
"Unknown"
|
||||
) : (
|
||||
<>
|
||||
{isLoading ? (
|
||||
<Skeleton width={140} />
|
||||
) : (
|
||||
<div className={"flex gap-2 items-center"}>
|
||||
<div className={"border-0 border-nb-gray-800 rounded-full"}>
|
||||
<RoundedFlag country={peer.country_code} size={12} />
|
||||
</div>
|
||||
{countryText}
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}
|
||||
/>
|
||||
|
||||
<Card.ListItem
|
||||
label={
|
||||
<>
|
||||
@@ -396,7 +347,6 @@ function PeerInformationCard({ peer }: { peer: Peer }) {
|
||||
")"
|
||||
}
|
||||
/>
|
||||
|
||||
<Card.ListItem
|
||||
label={
|
||||
<>
|
||||
|
||||
@@ -48,7 +48,7 @@ export default function PostureChecksPage() {
|
||||
</Paragraph>
|
||||
<Paragraph>
|
||||
Learn more about
|
||||
<InlineLink href={"https://docs.netbird.io/how-to/manage-posture-checks"} target={"_blank"}>
|
||||
<InlineLink href={"#"} target={"_blank"}>
|
||||
Posture Checks
|
||||
<ExternalLinkIcon size={12} />
|
||||
</InlineLink>
|
||||
|
||||
@@ -30,7 +30,6 @@ type CardListItemProps = {
|
||||
value: React.ReactNode;
|
||||
className?: string;
|
||||
copy?: boolean;
|
||||
tooltip?: boolean;
|
||||
};
|
||||
|
||||
function CardListItem({
|
||||
@@ -38,7 +37,6 @@ function CardListItem({
|
||||
value,
|
||||
className,
|
||||
copy = false,
|
||||
tooltip = true,
|
||||
}: CardListItemProps) {
|
||||
const [, copyToClipBoard] = useCopyToClipboard(value as string);
|
||||
|
||||
@@ -59,11 +57,7 @@ function CardListItem({
|
||||
copy && copyToClipBoard(`${label} has been copied to clipboard.`)
|
||||
}
|
||||
>
|
||||
{tooltip ? (
|
||||
<TextWithTooltip text={value as string} maxChars={40} />
|
||||
) : (
|
||||
value
|
||||
)}
|
||||
<TextWithTooltip text={value as string} maxChars={40} />
|
||||
{copy && <Copy size={13} />}
|
||||
</div>
|
||||
</li>
|
||||
|
||||
@@ -28,16 +28,12 @@ export default function CopyToClipboardText({ children, message }: Props) {
|
||||
|
||||
{copied ? (
|
||||
<CheckIcon
|
||||
className={
|
||||
"text-nb-gray-100 opacity-0 group-hover:opacity-100 shrink-0"
|
||||
}
|
||||
className={"text-nb-gray-100 opacity-0 group-hover:opacity-100"}
|
||||
size={12}
|
||||
/>
|
||||
) : (
|
||||
<CopyIcon
|
||||
className={
|
||||
"text-nb-gray-100 opacity-0 group-hover:opacity-100 shrink-0"
|
||||
}
|
||||
className={"text-nb-gray-100 opacity-0 group-hover:opacity-100"}
|
||||
size={12}
|
||||
/>
|
||||
)}
|
||||
|
||||
@@ -50,7 +50,6 @@ export default function FullTooltip({
|
||||
className={cn(
|
||||
isAction ? "cursor-pointer" : "cursor-default",
|
||||
"inline-flex items-center gap-2 dark:text-neutral-300 text-neutral-500 hover:text-neutral-100 transition-all hover:bg-nb-gray-800/60 py-2 px-3 rounded-md",
|
||||
className,
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
import Skeleton from "react-loading-skeleton";
|
||||
|
||||
export default function SkeletonPeerDetail() {
|
||||
return (
|
||||
<div className={"w-full mt-6 p-default"}>
|
||||
<div className={"flex flex-wrap w-full justify-between max-w-6xl "}>
|
||||
<Skeleton height={24} width={300} className={"rounded-md"} />
|
||||
</div>
|
||||
<div className={"flex flex-wrap w-full justify-between mt-4 max-w-6xl "}>
|
||||
<Skeleton height={42} width={400} className={"rounded-md"} />
|
||||
<div className={"flex gap-3"}>
|
||||
<Skeleton height={42} width={80} className={"rounded-md"} />
|
||||
<Skeleton height={42} width={120} className={"rounded-md"} />
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={
|
||||
"flex flex-wrap w-full justify-between mt-6 max-w-6xl gap-10"
|
||||
}
|
||||
>
|
||||
<Skeleton
|
||||
height={400}
|
||||
width={"100%"}
|
||||
className={"rounded-md"}
|
||||
containerClassName={"flex-1 "}
|
||||
/>
|
||||
<Skeleton
|
||||
height={300}
|
||||
width={"100%"}
|
||||
className={"rounded-md opacity-30"}
|
||||
containerClassName={"flex-1 "}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -18,6 +18,8 @@ export function DataTableRowsPerPage<TData>({
|
||||
disabled,
|
||||
}: DataTablePaginationProps<TData>) {
|
||||
const [open, setOpen] = React.useState(false);
|
||||
const [isHovered, setIsHovered] = React.useState(false);
|
||||
const textColorClass = isHovered ? "text-white" : "text-nb-gray-300";
|
||||
|
||||
return (
|
||||
<>
|
||||
@@ -28,16 +30,18 @@ export function DataTableRowsPerPage<TData>({
|
||||
role="combobox"
|
||||
aria-expanded={open}
|
||||
disabled={disabled}
|
||||
onMouseEnter={() => setIsHovered(true)}
|
||||
onMouseLeave={() => setIsHovered(false)}
|
||||
className="w-[200px] justify-between"
|
||||
>
|
||||
<RowsIcon size={15} className={"text-nb-gray-300"} />
|
||||
<RowsIcon size={15} className={textColorClass} />
|
||||
<div>
|
||||
<span className={"text-white"}>
|
||||
<span className={textColorClass}>
|
||||
{table.getState().pagination.pageSize}
|
||||
</span>
|
||||
<span className={"text-nb-gray-300"}> rows per page</span>
|
||||
<span className={textColorClass}> rows per page</span>
|
||||
</div>
|
||||
<ChevronDown className="h-4 w-4 opacity-50" />
|
||||
<ChevronDown className={`h-4 w-4 opacity-50 ${textColorClass}`} />
|
||||
</Button>
|
||||
</PopoverTrigger>
|
||||
<PopoverContent className="w-[200px] p-0" sideOffset={7}>
|
||||
@@ -56,18 +60,15 @@ export function DataTableRowsPerPage<TData>({
|
||||
className={cn(
|
||||
"cursor-pointer",
|
||||
"flex gap-2 px-2 py-1.5 my-1 mx-1 rounded-md items-center hover:dark:bg-nb-gray-800 text-nb-gray-400 hover:text-white",
|
||||
table.getState().pagination.pageSize === val
|
||||
? "text-white"
|
||||
: "",
|
||||
table.getState().pagination.pageSize === val ? "text-white" : "",
|
||||
)}
|
||||
>
|
||||
<Check
|
||||
size={15}
|
||||
className={cn(
|
||||
"text-white shrink-0",
|
||||
table.getState().pagination.pageSize === val
|
||||
? "opacity-100"
|
||||
: "opacity-0",
|
||||
"shrink-0",
|
||||
table.getState().pagination.pageSize === val ? "opacity-100 text-white" : "opacity-0",
|
||||
textColorClass,
|
||||
)}
|
||||
/>
|
||||
{val}
|
||||
|
||||
@@ -2,16 +2,19 @@ import {
|
||||
SelectDropdown,
|
||||
SelectOption,
|
||||
} from "@components/select/SelectDropdown";
|
||||
import useFetchApi from "@utils/api";
|
||||
import { createElement, useMemo } from "react";
|
||||
import RoundedFlag from "@/assets/countries/RoundedFlag";
|
||||
import { useCountries } from "@/contexts/CountryProvider";
|
||||
import { Country } from "@/interfaces/Country";
|
||||
|
||||
type Props = {
|
||||
value: string;
|
||||
onChange: (value: string) => void;
|
||||
};
|
||||
export const CountrySelector = ({ value, onChange }: Props) => {
|
||||
const { countries, isLoading } = useCountries();
|
||||
const { data: countries, isLoading } = useFetchApi<Country[]>(
|
||||
"/locations/countries",
|
||||
);
|
||||
|
||||
const countryList = useMemo(() => {
|
||||
return countries?.map((country) => {
|
||||
|
||||
@@ -27,14 +27,11 @@ export default function GroupBadge({
|
||||
className={cn("transition-all group whitespace-nowrap", className)}
|
||||
onClick={onClick}
|
||||
>
|
||||
<FolderGit2 size={12} className={"shrink-0"} />
|
||||
<FolderGit2 size={12} />
|
||||
<TextWithTooltip text={group.name} maxChars={20} />
|
||||
{children}
|
||||
{showX && (
|
||||
<XIcon
|
||||
size={12}
|
||||
className={"cursor-pointer group-hover:text-white shrink-0"}
|
||||
/>
|
||||
<XIcon size={12} className={"cursor-pointer group-hover:text-white"} />
|
||||
)}
|
||||
</Badge>
|
||||
);
|
||||
|
||||
@@ -10,7 +10,7 @@ export default function LoginExpiredBadge({ loginExpired }: Props) {
|
||||
<Tooltip delayDuration={1}>
|
||||
<TooltipTrigger>
|
||||
<Badge variant={"red"} className={"px-3"}>
|
||||
<AlertTriangle size={13} className={"mr-1"} />
|
||||
<AlertTriangle size={14} className={"mr-1"} />
|
||||
Login required
|
||||
</Badge>
|
||||
</TooltipTrigger>
|
||||
|
||||
@@ -24,12 +24,11 @@ export default function TextWithTooltip({
|
||||
<FullTooltip
|
||||
disabled={charCount <= maxChars || hideTooltip}
|
||||
interactive={false}
|
||||
className={"truncate w-full"}
|
||||
content={
|
||||
<div className={"max-w-xs break-all whitespace-normal"}>{text}</div>
|
||||
}
|
||||
>
|
||||
<span className={cn(className, "truncate")}>
|
||||
<span className={cn(className)}>
|
||||
{charCount > maxChars ? text && `${text.slice(0, maxChars)}...` : text}
|
||||
</span>
|
||||
</FullTooltip>
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
import useFetchApi from "@utils/api";
|
||||
import React, { useCallback } from "react";
|
||||
import { Country } from "@/interfaces/Country";
|
||||
import { Peer } from "@/interfaces/Peer";
|
||||
|
||||
type Props = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const CountryContext = React.createContext(
|
||||
{} as {
|
||||
countries: Country[] | undefined;
|
||||
isLoading: boolean;
|
||||
getRegionByPeer: (peer: Peer) => string;
|
||||
},
|
||||
);
|
||||
|
||||
export default function CountryProvider({ children }: Props) {
|
||||
const { data: countries, isLoading } = useFetchApi<Country[]>(
|
||||
"/locations/countries",
|
||||
false,
|
||||
false,
|
||||
);
|
||||
|
||||
const getRegionByPeer = useCallback(
|
||||
(peer: Peer) => {
|
||||
if (!countries) return "Unknown";
|
||||
const country = countries.find(
|
||||
(c) => c.country_code === peer.country_code,
|
||||
);
|
||||
if (!country) return "Unknown";
|
||||
if (!peer.city_name) return country.country_name;
|
||||
return `${country.country_name}, ${peer.city_name}`;
|
||||
},
|
||||
[countries],
|
||||
);
|
||||
|
||||
return (
|
||||
<CountryContext.Provider value={{ countries, isLoading, getRegionByPeer }}>
|
||||
{children}
|
||||
</CountryContext.Provider>
|
||||
);
|
||||
}
|
||||
|
||||
export const useCountries = () => {
|
||||
return React.useContext(CountryContext);
|
||||
};
|
||||
@@ -12,12 +12,11 @@ const GroupContext = React.createContext(
|
||||
refresh: () => void;
|
||||
dropdownOptions: Group[];
|
||||
setDropdownOptions: React.Dispatch<React.SetStateAction<Group[]>>;
|
||||
isLoading: boolean;
|
||||
},
|
||||
);
|
||||
|
||||
export default function GroupsProvider({ children }: Props) {
|
||||
const { data: groups, mutate, isLoading } = useFetchApi<Group[]>("/groups");
|
||||
const { data: groups, mutate } = useFetchApi<Group[]>("/groups");
|
||||
const [dropdownOptions, setDropdownOptions] = useState<Group[]>([]);
|
||||
|
||||
const refresh = () => {
|
||||
@@ -26,13 +25,7 @@ export default function GroupsProvider({ children }: Props) {
|
||||
|
||||
return (
|
||||
<GroupContext.Provider
|
||||
value={{
|
||||
groups,
|
||||
refresh,
|
||||
dropdownOptions,
|
||||
setDropdownOptions,
|
||||
isLoading,
|
||||
}}
|
||||
value={{ groups, refresh, dropdownOptions, setDropdownOptions }}
|
||||
>
|
||||
{children}
|
||||
</GroupContext.Provider>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { notify } from "@components/Notification";
|
||||
import SkeletonPeerDetail from "@components/skeletons/SkeletonPeerDetail";
|
||||
import { useApiCall } from "@utils/api";
|
||||
import React, { useMemo } from "react";
|
||||
import { useSWRConfig } from "swr";
|
||||
@@ -28,13 +27,12 @@ const PeerContext = React.createContext(
|
||||
) => Promise<Peer>;
|
||||
openSSHDialog: () => Promise<boolean>;
|
||||
deletePeer: () => void;
|
||||
isLoading: boolean;
|
||||
},
|
||||
);
|
||||
|
||||
export default function PeerProvider({ children, peer }: Props) {
|
||||
const user = usePeerUser(peer);
|
||||
const { peerGroups, isLoading } = usePeerGroups(peer);
|
||||
const peerGroups = usePeerGroups(peer);
|
||||
const peerRequest = useApiCall<Peer>("/peers");
|
||||
const { confirm } = useDialog();
|
||||
const { mutate } = useSWRConfig();
|
||||
@@ -96,22 +94,12 @@ export default function PeerProvider({ children, peer }: Props) {
|
||||
});
|
||||
};
|
||||
|
||||
return !isLoading ? (
|
||||
return (
|
||||
<PeerContext.Provider
|
||||
value={{
|
||||
peer,
|
||||
peerGroups,
|
||||
user,
|
||||
update,
|
||||
openSSHDialog,
|
||||
deletePeer,
|
||||
isLoading,
|
||||
}}
|
||||
value={{ peer, peerGroups, user, update, openSSHDialog, deletePeer }}
|
||||
>
|
||||
{children}
|
||||
</PeerContext.Provider>
|
||||
) : (
|
||||
<SkeletonPeerDetail />
|
||||
);
|
||||
}
|
||||
|
||||
@@ -120,9 +108,9 @@ export default function PeerProvider({ children, peer }: Props) {
|
||||
* @param peer
|
||||
*/
|
||||
export const usePeerGroups = (peer?: Peer) => {
|
||||
const { groups, isLoading } = useGroups();
|
||||
const { groups } = useGroups();
|
||||
|
||||
const peerGroups = useMemo(() => {
|
||||
return useMemo(() => {
|
||||
if (!peer) return [];
|
||||
const peerGroups = groups?.filter((group) => {
|
||||
const foundGroup = group.peers?.find((p) => {
|
||||
@@ -133,8 +121,6 @@ export const usePeerGroups = (peer?: Peer) => {
|
||||
});
|
||||
return peerGroups || [];
|
||||
}, [groups, peer]);
|
||||
|
||||
return { peerGroups, isLoading };
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -20,7 +20,4 @@ export interface Peer {
|
||||
login_expired: boolean;
|
||||
login_expiration_enabled: boolean;
|
||||
approval_required: boolean;
|
||||
city_name: string;
|
||||
country_code: string;
|
||||
connection_ip: string;
|
||||
}
|
||||
@@ -9,7 +9,6 @@ export interface PostureCheck {
|
||||
nb_version_check?: NetBirdVersionCheck;
|
||||
os_version_check?: OperatingSystemVersionCheck;
|
||||
geo_location_check?: GeoLocationCheck;
|
||||
peer_network_range_check?: PeerNetworkRangeCheck;
|
||||
};
|
||||
policies?: Policy[];
|
||||
active?: boolean;
|
||||
@@ -48,11 +47,6 @@ export interface GeoLocation {
|
||||
city_name: string;
|
||||
}
|
||||
|
||||
export interface PeerNetworkRangeCheck {
|
||||
ranges: string[];
|
||||
action: "allow" | "deny";
|
||||
}
|
||||
|
||||
export const windowsKernelVersions: SelectOption[] = [
|
||||
{ value: "5.0", label: "Windows 2000" },
|
||||
{ value: "5.1", label: "Windows XP" },
|
||||
|
||||
@@ -12,7 +12,6 @@ import React from "react";
|
||||
import ApplicationProvider, {
|
||||
useApplicationContext,
|
||||
} from "@/contexts/ApplicationProvider";
|
||||
import CountryProvider from "@/contexts/CountryProvider";
|
||||
import GroupsProvider from "@/contexts/GroupsProvider";
|
||||
import UsersProvider from "@/contexts/UsersProvider";
|
||||
import Navigation from "@/layouts/Navigation";
|
||||
@@ -27,9 +26,7 @@ export default function DashboardLayout({
|
||||
<ApplicationProvider>
|
||||
<UsersProvider>
|
||||
<GroupsProvider>
|
||||
<CountryProvider>
|
||||
<DashboardPageContent>{children}</DashboardPageContent>
|
||||
</CountryProvider>
|
||||
<DashboardPageContent>{children}</DashboardPageContent>
|
||||
</GroupsProvider>
|
||||
</UsersProvider>
|
||||
</ApplicationProvider>
|
||||
|
||||
@@ -30,7 +30,7 @@ export default function useGroupHelper({ initial = [], peer }: Props) {
|
||||
}, [groups, initial]);
|
||||
|
||||
const [selectedGroups, setSelectedGroups] = useState<Group[]>(initialGroups);
|
||||
const { peerGroups } = usePeerGroups(peer);
|
||||
const peerGroups = usePeerGroups(peer);
|
||||
|
||||
const save = async () => {
|
||||
return Promise.all(getAllGroupCalls()).then((groups) => {
|
||||
|
||||
@@ -9,7 +9,7 @@ type Props = {
|
||||
};
|
||||
export default function usePeerRoutes({ peer }: Props) {
|
||||
const { data: routes } = useFetchApi<Route[]>("/routes");
|
||||
const { peerGroups } = usePeerGroups(peer);
|
||||
const peerGroups = usePeerGroups(peer);
|
||||
|
||||
return useMemo(() => {
|
||||
if (!routes) return undefined;
|
||||
|
||||
Loaded 20 of 34 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user