Compare commits

...
Author SHA1 Message Date
Eduard Gert 2dc89631aa Revert workflow 2024-08-13 13:09:27 +02:00
Eduard Gert 3fa0aa8f00 Update workflow 2024-07-17 16:57:52 +02:00
Eduard Gert 3a132ef553 Fix type errors 2024-07-17 16:45:30 +02:00
Eduard Gert 40b2797f82 Fix type 2024-07-17 15:15:32 +02:00
Eduard Gert dedef3fa56 Update axa oidc library 2024-07-17 15:04:46 +02:00
Tom Hubrecht 121778c4a6 Fix package-lock.json (#401) 2024-07-12 10:35:31 +02:00
juliaroesschen d4102c5d04 fix typo in route update modal (#397) 2024-07-04 15:05:57 +02:00
pascal-fischer e78c35bdbe Fix DNS modal to allow one char domains (#393)
* update regex to allow one char domains in DNS routing modal

* update regex
2024-07-04 10:50:37 +02:00
juliaroesschen 6ebee98695 Fix typo in Network Routes dialogue (#395) 2024-07-04 10:48:49 +02:00
juliaroesschen f4b28d5f40 Fix typo in routes modal 2024-06-28 11:38:39 +02:00
Eduard Gert b4b6d9295b Add DNS routes (#390) 2024-06-17 09:32:55 +02:00
Maycon Santos 4898742ee9 Fix http://localhost:3000/ url validation case (#388)
* Fix http://localhost:3000/ url validation case

* adjust min regex occurrences
2024-06-12 18:18:14 +02:00
Eduard Gert 79164e9dd5 Add process posture check (#378)
* Add process posture check

* Add support for separate linux and mac paths
2024-06-12 16:32:10 +02:00
Eduard Gert 5caeab118b UX changes for modals and refactoring (#380) 2024-05-08 14:42:04 +02:00
59 changed files with 1836 additions and 646 deletions

No files matched your search

+3
View File
@@ -5,6 +5,9 @@ const nextConfig = {
unoptimized: true,
},
reactStrictMode: false,
env: {
APP_ENV: process.env.APP_ENV || "production",
},
};
module.exports = nextConfig;
+36 -80
View File
@@ -8,7 +8,7 @@
"name": "netbird-dashboard",
"version": "2.0.0",
"dependencies": {
"@axa-fr/react-oidc": "^5.14.0",
"@axa-fr/react-oidc": "^7.22.18",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
@@ -48,7 +48,7 @@
"framer-motion": "^10.16.4",
"ip-cidr": "^3.1.0",
"lodash": "^4.17.21",
"lucide-react": "^0.287.0",
"lucide-react": "^0.383.0",
"next": "13.5.5",
"next-themes": "^0.2.1",
"punycode": "^2.3.1",
@@ -93,16 +93,31 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/@axa-fr/react-oidc": {
"version": "5.14.2",
"resolved": "https://registry.npmjs.org/@axa-fr/react-oidc/-/react-oidc-5.14.2.tgz",
"integrity": "sha512-N+ssJlVtVHnsvlusMxY3zLPKCB+lGzeHIxWXUb0WY3uA7Z+jxx7A2m9W1kHbhYzHuihgA3rWIcdKsvtdkeKXwg==",
"node_modules/@axa-fr/oidc-client": {
"version": "7.22.18",
"resolved": "https://registry.npmjs.org/@axa-fr/oidc-client/-/oidc-client-7.22.18.tgz",
"integrity": "sha512-emXC5EfdopQyxHFNvhso05+x8x/2qJkk87MwbnGVE96dn5Hu9WTpn+mmj5ybVhKG7hizMmOtIQTgcDptnDGKHQ==",
"hasInstallScript": true,
"dependencies": {
"@openid/appauth": "1.3.1"
"@axa-fr/oidc-client-service-worker": "7.22.18"
}
},
"node_modules/@axa-fr/oidc-client-service-worker": {
"version": "7.22.18",
"resolved": "https://registry.npmjs.org/@axa-fr/oidc-client-service-worker/-/oidc-client-service-worker-7.22.18.tgz",
"integrity": "sha512-DMtERkFMSB3P2oyNqA/VWg0gtg3UccRo2YzZ2kmEvFQEUURhTgCMpp03Y0v9wWt6sPmt54hCGphoKfqLOjJj7w=="
},
"node_modules/@axa-fr/react-oidc": {
"version": "7.22.18",
"resolved": "https://registry.npmjs.org/@axa-fr/react-oidc/-/react-oidc-7.22.18.tgz",
"integrity": "sha512-bMLdQV8kpd4OYvl1Yz8KSeZuKVdAL2fS0I4V6Up271oVLpi4sPN+xuHr2j9J9ZOFdqp4bE1eC3IxVo/Q6Cihzg==",
"hasInstallScript": true,
"dependencies": {
"@axa-fr/oidc-client": "7.22.18",
"@axa-fr/oidc-client-service-worker": "7.22.18"
},
"peerDependencies": {
"react": "x",
"react-dom": "x"
"react": "^17.0.0 || ^18.0.0"
}
},
"node_modules/@babel/runtime": {
@@ -542,32 +557,6 @@
"node": ">= 8"
}
},
"node_modules/@openid/appauth": {
"version": "1.3.1",
"resolved": "https://registry.npmjs.org/@openid/appauth/-/appauth-1.3.1.tgz",
"integrity": "sha512-e54kpi219wES2ijPzeHe1kMnT8VKH8YeTd1GAn9BzVBmutz3tBgcG1y8a4pziNr4vNjFnuD4W446Ua7ELnNDiA==",
"dependencies": {
"@types/base64-js": "^1.3.0",
"@types/jquery": "^3.5.5",
"base64-js": "^1.5.1",
"follow-redirects": "^1.13.3",
"form-data": "^4.0.0",
"opener": "^1.5.2"
}
},
"node_modules/@openid/appauth/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@popperjs/core": {
"version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
@@ -1658,24 +1647,11 @@
"url": "https://github.com/sponsors/tannerlinsley"
}
},
"node_modules/@types/base64-js": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@types/base64-js/-/base64-js-1.3.2.tgz",
"integrity": "sha512-Q2Xn2/vQHRGLRXhQ5+BSLwhHkR3JVflxVKywH0Q6fVoAiUE8fFYL2pE5/l2ZiOiBDfA8qUqRnSxln4G/NFz1Sg=="
},
"node_modules/@types/crypto-js": {
"version": "4.2.2",
"resolved": "https://registry.npmjs.org/@types/crypto-js/-/crypto-js-4.2.2.tgz",
"integrity": "sha512-sDOLlVbHhXpAUAL0YHDUUwDZf3iN4Bwi4W6a0W0b+QcAezUbRtH4FVb+9J4h+XFPW7l/gQ9F8qC7P+Ec4k8QVQ=="
},
"node_modules/@types/jquery": {
"version": "3.5.29",
"resolved": "https://registry.npmjs.org/@types/jquery/-/jquery-3.5.29.tgz",
"integrity": "sha512-oXQQC9X9MOPRrMhPHHOsXqeQDnWeCDT3PelUIg/Oy8FAbzSZtFHRjc7IpbfFVmpLtJ+UOoywpRsuO5Jxjybyeg==",
"dependencies": {
"@types/sizzle": "*"
}
},
"node_modules/@types/json5": {
"version": "0.0.29",
"resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
@@ -1731,7 +1707,8 @@
"node_modules/@types/sizzle": {
"version": "2.3.8",
"resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.8.tgz",
"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg=="
"integrity": "sha512-0vWLNK2D5MT9dg0iOo8GlKguPAU02QjmZitPEsXRuJXU/OGIOt9vT9Fc26wtYuavLxtO45v9PGleoL9Z0k1LHg==",
"dev": true
},
"node_modules/@types/yauzl": {
"version": "2.10.3",
@@ -2187,7 +2164,8 @@
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
"dev": true
},
"node_modules/at-least-node": {
"version": "1.0.0",
@@ -2285,6 +2263,7 @@
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"dev": true,
"funding": [
{
"type": "github",
@@ -2927,6 +2906,7 @@
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
"integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
"dev": true,
"dependencies": {
"delayed-stream": "~1.0.0"
},
@@ -3170,6 +3150,7 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
@@ -4057,25 +4038,6 @@
"tailwindcss": "^3"
}
},
"node_modules/follow-redirects": {
"version": "1.15.5",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.5.tgz",
"integrity": "sha512-vSFWUON1B+yAw1VN4xMfxgn5fTUiaOzAJCKBwIIgT/+7CuGy9+r+5gITvP62j3RmaD5Ph65UaERdOSRGUzZtgw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/for-each": {
"version": "0.3.3",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz",
@@ -5351,9 +5313,9 @@
}
},
"node_modules/lucide-react": {
"version": "0.287.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.287.0.tgz",
"integrity": "sha512-auxP2bTGiMoELzX+6ItTeNzLmhGd/O+PHBsrXV2YwPXYCxarIFJhiMOSzFT9a1GWeYPSZtnWdLr79IVXr/5JqQ==",
"version": "0.383.0",
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.383.0.tgz",
"integrity": "sha512-13xlG0CQCJtzjSQYwwJ3WRqMHtRj3EXmLlorrARt7y+IHnxUCp3XyFNL1DfaGySWxHObDvnu1u1dV+0VMKHUSg==",
"peerDependencies": {
"react": "^16.5.1 || ^17.0.0 || ^18.0.0"
}
@@ -5396,6 +5358,7 @@
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
"integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
"dev": true,
"engines": {
"node": ">= 0.6"
}
@@ -5404,6 +5367,7 @@
"version": "2.1.35",
"resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
"integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
"dev": true,
"dependencies": {
"mime-db": "1.52.0"
},
@@ -5712,14 +5676,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/opener": {
"version": "1.5.2",
"resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz",
"integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==",
"bin": {
"opener": "bin/opener-bin.js"
}
},
"node_modules/optionator": {
"version": "0.9.3",
"resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+2 -2
View File
@@ -13,7 +13,7 @@
"cypress:open": "cypress open"
},
"dependencies": {
"@axa-fr/react-oidc": "^5.14.0",
"@axa-fr/react-oidc": "^7.22.18",
"@radix-ui/react-accordion": "^1.1.2",
"@radix-ui/react-checkbox": "^1.0.4",
"@radix-ui/react-collapsible": "^1.0.3",
@@ -53,7 +53,7 @@
"framer-motion": "^10.16.4",
"ip-cidr": "^3.1.0",
"lodash": "^4.17.21",
"lucide-react": "^0.287.0",
"lucide-react": "^0.383.0",
"next": "13.5.5",
"next-themes": "^0.2.1",
"punycode": "^2.3.1",
+6 -2
View File
@@ -23,6 +23,7 @@ import Separator from "@components/Separator";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import LoginExpiredBadge from "@components/ui/LoginExpiredBadge";
import TextWithTooltip from "@components/ui/TextWithTooltip";
import useRedirect from "@hooks/useRedirect";
import { IconCloudLock, IconInfoCircle } from "@tabler/icons-react";
import useFetchApi from "@utils/api";
import dayjs from "dayjs";
@@ -66,8 +67,11 @@ import PeerRoutesTable from "@/modules/peer/PeerRoutesTable";
export default function PeerPage() {
const queryParameter = useSearchParams();
const peerId = queryParameter.get("id");
const { data: peer } = useFetchApi<Peer>("/peers/" + peerId);
return peer ? (
const { data: peer, isLoading } = useFetchApi<Peer>("/peers/" + peerId, true);
useRedirect("/peers", false, !peerId);
return peer && !isLoading ? (
<PeerProvider peer={peer}>
<PeerOverview />
</PeerProvider>
+3
View File
@@ -10,6 +10,7 @@ import Paragraph from "@components/Paragraph";
import { PeerGroupSelector } from "@components/PeerGroupSelector";
import Separator from "@components/Separator";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import useRedirect from "@hooks/useRedirect";
import { IconCirclePlus, IconSettings2 } from "@tabler/icons-react";
import useFetchApi, { useApiCall } from "@utils/api";
import { generateColorFromString } from "@utils/helpers";
@@ -42,6 +43,8 @@ export default function UserPage() {
return users?.find((u) => u.id === userId);
}, [users, userId]);
useRedirect("/team/users", false, !userId);
return !isLoading && user ? (
<UserOverview user={user} />
) : (
+33 -7
View File
@@ -1,14 +1,40 @@
"use client";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useRouter } from "next/navigation";
import { useEffect } from "react";
import { useLocalStorage } from "@hooks/useLocalStorage";
import { useRedirect } from "@hooks/useRedirect";
import { useEffect, useState } from "react";
type Props = {
url: string;
queryParams?: string;
};
export default function NotFound() {
const router = useRouter();
useEffect(() => {
router.push("/peers");
});
const [mounted, setMounted] = useState(false);
const [tempQueryParams, setTempQueryParams] = useLocalStorage(
"netbird-query-params",
"",
);
const [queryParams, setQueryParams] = useState("");
return <FullScreenLoading />;
useEffect(() => {
setQueryParams(tempQueryParams);
setTempQueryParams("");
setMounted(true);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
return mounted ? (
<Redirect
url={window?.location?.pathname || "/"}
queryParams={queryParams}
/>
) : (
<FullScreenLoading />
);
}
const Redirect = ({ url, queryParams }: Props) => {
useRedirect("/peers" + (queryParams && `?${queryParams}`));
return <FullScreenLoading />;
};
+2
View File
@@ -16,6 +16,8 @@ export default function CircleIcon({
return (
<span
style={{ width: size + "px", height: size + "px" }}
data-cy="circle-icon"
data-cy-status={active ? "active" : "inactive"}
className={cn(
"rounded-full",
active
+3 -3
View File
@@ -1,10 +1,10 @@
"use client";
import { OidcProvider } from "@axa-fr/react-oidc";
import {
AuthorityConfiguration,
OidcConfiguration,
} from "@axa-fr/react-oidc/dist/vanilla/oidc";
OidcProvider,
} from "@axa-fr/react-oidc";
import FullScreenLoading from "@components/ui/FullScreenLoading";
import { useLocalStorage } from "@hooks/useLocalStorage";
import { useRedirect } from "@hooks/useRedirect";
@@ -30,7 +30,7 @@ const auth0AuthorityConfig: AuthorityConfiguration = {
revocation_endpoint: new URL("oauth/revoke", config.authority).href,
end_session_endpoint: new URL("v2/logout", config.authority).href,
userinfo_endpoint: new URL("userinfo", config.authority).href,
//issuer: new URL("", config.authority).href,
issuer: new URL("", config.authority).href,
};
const onEvent = (configurationName: any, eventName: any, data: any) => {
+8 -2
View File
@@ -5,14 +5,16 @@ import React, { forwardRef } from "react";
type Props = {
children: React.ReactNode;
disabled?: boolean;
className?: string;
};
function ButtonGroup({ children, disabled }: Props) {
function ButtonGroup({ children, disabled, className }: Props) {
return (
<div
className={cn(
"rounded-lg border-[1px] dark:border-nb-gray-900 border-neutral-200 overflow-hidden flex items-center justify-center shrink-0 border-separate",
disabled ? "opacity-100 !border-nb-gray-900/20" : "",
className,
)}
>
{children}
@@ -21,7 +23,10 @@ function ButtonGroup({ children, disabled }: Props) {
}
const ButtonGroupButton = forwardRef(
({ ...props }: ButtonProps, ref: React.ForwardedRef<HTMLButtonElement>) => {
(
{ className, ...props }: ButtonProps,
ref: React.ForwardedRef<HTMLButtonElement>,
) => {
return (
<Button
ref={ref}
@@ -31,6 +36,7 @@ const ButtonGroupButton = forwardRef(
className={cn(
"first:border-l-0 last:border-r-0 border-t-0 border-b-0 h-[41px]",
"!py-2.5 !px-4",
className,
)}
/>
);
+9 -4
View File
@@ -13,6 +13,7 @@ export interface InputProps
icon?: React.ReactNode;
error?: string;
errorTooltip?: boolean;
errorTooltipPosition?: "top" | "top-right";
}
const inputVariants = cva("", {
@@ -49,6 +50,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
maxWidthClass = "",
error,
errorTooltip = false,
errorTooltipPosition = "top",
...props
},
ref,
@@ -105,9 +107,12 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
</div>
{error && errorTooltip && (
<div
className={
"absolute right-0 top-2 h-[0px] w-full flex items-center pr-3 justify-center"
}
className={cn(
errorTooltipPosition == "top" &&
"absolute right-0 top-2 h-[0px] w-full flex items-center pr-3 justify-center",
errorTooltipPosition == "top-right" &&
"absolute -right-6 top-2 h-[0px] w-full flex items-center pr-3 justify-end",
)}
>
<FullTooltip
content={
@@ -120,7 +125,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
</div>
}
interactive={false}
align={"center"}
align={errorTooltipPosition == "top" ? "center" : "end"}
side={"top"}
keepOpen={true}
>
+30 -2
View File
@@ -1,4 +1,5 @@
import { CommandItem } from "@components/Command";
import FullTooltip from "@components/FullTooltip";
import { Popover, PopoverContent, PopoverTrigger } from "@components/Popover";
import { IconArrowBack } from "@tabler/icons-react";
import useFetchApi from "@utils/api";
@@ -62,8 +63,13 @@ export function NetworkRouteSelector({
const isSearching = search.length > 0;
const found =
dropdownOptions.filter((item) => {
const hasDomains = item?.domains ? item.domains.length > 0 : false;
const domains =
hasDomains && item?.domains ? item?.domains.join(" ") : "";
return (
item.network_id.includes(search) || item.network.includes(search)
item.network_id.includes(search) ||
item.network?.includes(search) ||
domains.includes(search)
);
}).length > 0;
return isSearching && !found;
@@ -117,6 +123,7 @@ export function NetworkRouteSelector({
>
{value.network}
</div>
<DomainList domains={value?.domains} />
</div>
) : (
<span>Select an existing network...</span>
@@ -208,7 +215,11 @@ export function NetworkRouteSelector({
return (
<CommandItem
key={option.network + option.network_id}
value={option.network + option.network_id}
value={
option.network +
option.network_id +
option?.domains?.join(", ")
}
onSelect={() => {
togglePeer(option);
setOpen(false);
@@ -226,6 +237,7 @@ export function NetworkRouteSelector({
>
{option.network}
</div>
<DomainList domains={option?.domains} />
</CommandItem>
);
})}
@@ -238,3 +250,19 @@ export function NetworkRouteSelector({
</Popover>
);
}
function DomainList({ domains }: { domains?: string[] }) {
const firstDomain = domains ? domains[0] : "";
return (
domains &&
domains.length > 0 && (
<FullTooltip
content={<div className={"text-xs max-w-sm"}>{domains.join(", ")}</div>}
>
<div className={"text-xs text-nb-gray-300"}>
{firstDomain} {domains.length > 1 && "+" + (domains.length - 1)}
</div>
</FullTooltip>
)
);
}
+1
View File
@@ -198,6 +198,7 @@ export function PeerGroupSelector({
<CommandList className={"w-full"}>
<div className={"relative"}>
<CommandInput
data-cy={"group-search-input"}
className={cn(
"min-h-[42px] w-full relative",
"border-b-0 border-t-0 border-r-0 border-l-0 border-neutral-200 dark:border-nb-gray-700 items-center",
+11 -4
View File
@@ -21,12 +21,19 @@ function SegmentedTabs({ value, onChange, children }: Props) {
);
}
function List({ children }: { children: React.ReactNode }) {
function List({
children,
className = "",
}: {
children: React.ReactNode;
className?: string;
}) {
return (
<TabsList
className={
"bg-nb-gray-930/70 p-1.5 rounded-t-lg flex justify-center gap-1 border border-b-0 border-nb-gray-900"
}
className={cn(
"bg-nb-gray-930/70 p-1.5 rounded-t-lg flex justify-center gap-1 border border-b-0 border-nb-gray-900",
className,
)}
>
{children}
</TabsList>
+4 -1
View File
@@ -75,7 +75,10 @@ const ModalContent = React.forwardRef<
<>
{children}
{showClose && (
<DialogPrimitive.Close className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400">
<DialogPrimitive.Close
data-cy={"modal-close"}
className="absolute right-4 top-4 rounded-sm opacity-70 ring-offset-white transition-opacity hover:opacity-100 focus:outline-none focus:ring-2 focus:ring-neutral-950 focus:ring-offset-2 disabled:pointer-events-none data-[state=open]:bg-neutral-100 data-[state=open]:text-neutral-500 dark:ring-offset-neutral-950 dark:focus:ring-neutral-300 dark:data-[state=open]:bg-neutral-800 dark:data-[state=open]:text-neutral-400"
>
<X className="h-4 w-4" />
<span className="sr-only">Close</span>
</DialogPrimitive.Close>
+11 -5
View File
@@ -10,6 +10,7 @@ interface Props extends IconVariant {
className?: string;
margin?: string;
truncate?: boolean;
children?: React.ReactNode;
}
export default function ModalHeader({
icon,
@@ -19,6 +20,7 @@ export default function ModalHeader({
className = "pb-6 px-8",
margin = "mt-0",
truncate = false,
children,
}: Props) {
return (
<div className={cn(className, "min-w-0")}>
@@ -26,11 +28,15 @@ export default function ModalHeader({
{icon && <SquareIcon color={color} icon={icon} />}
<div className={"min-w-0"}>
<h2 className={"text-lg my-0 leading-[1.5]"}>{title}</h2>
<Paragraph
className={cn("text-sm", margin, truncate && "!block truncate")}
>
{description}
</Paragraph>
{children ? (
<>{children}</>
) : (
<Paragraph
className={cn("text-sm", margin, truncate && "!block truncate")}
>
{description}
</Paragraph>
)}
</div>
</div>
</div>
@@ -28,9 +28,10 @@ export function DataTableRowsPerPage<TData>({
role="combobox"
aria-expanded={open}
disabled={disabled}
data-cy={"rows-per-page"}
className="w-[200px] justify-between"
>
<RowsIcon size={15} className={"text-nb-gray-300"} />
<RowsIcon size={15} className={"text-nb-gray-300 shrink-0"} />
<div>
<span className={"text-white"}>
{table.getState().pagination.pageSize}
@@ -47,6 +48,7 @@ export function DataTableRowsPerPage<TData>({
<CommandItem
key={val}
value={val.toString()}
data-cy={`rows-per-page-value`}
onSelect={(currentValue) => {
table.setPageSize(Number(currentValue));
setOpen(false);
+70
View File
@@ -0,0 +1,70 @@
import Badge from "@components/Badge";
import FullTooltip from "@components/FullTooltip";
import { GlobeIcon } from "lucide-react";
import * as React from "react";
type Props = {
domains: string[];
};
export const DomainListBadge = ({ domains }: Props) => {
const firstDomain = domains.length > 0 ? domains[0] : undefined;
return (
<DomainsTooltip domains={domains}>
<div className={"inline-flex items-center gap-2"}>
{firstDomain && (
<Badge variant={"gray"}>
<GlobeIcon size={10} />
{firstDomain}
</Badge>
)}
{domains && domains.length > 1 && (
<Badge variant={"gray"}>+ {domains.length - 1}</Badge>
)}
</div>
</DomainsTooltip>
);
};
export const DomainsTooltip = ({
domains,
children,
className,
}: {
domains: string[];
children: React.ReactNode;
className?: string;
}) => {
return (
<FullTooltip
interactive={false}
className={className}
content={
<div className={"flex flex-col gap-2 items-start"}>
{domains.map((domain) => {
return (
domain && (
<div
key={domain}
className={"flex gap-2 items-center justify-between w-full"}
>
<div
className={
"flex gap-2 items-center text-nb-gray-300 text-xs"
}
>
<GlobeIcon size={11} />
{domain}
</div>
</div>
)
);
})}
</div>
}
disabled={domains.length <= 1}
>
{children}
</FullTooltip>
);
};
+88
View File
@@ -0,0 +1,88 @@
import Button from "@components/Button";
import { Input } from "@components/Input";
import { validator } from "@utils/helpers";
import { uniqueId } from "lodash";
import { GlobeIcon, MinusCircleIcon } from "lucide-react";
import * as React from "react";
import { useEffect, useMemo, useState } from "react";
import { Domain } from "@/interfaces/Domain";
type Props = {
value: Domain;
onChange: (d: Domain) => void;
onRemove: () => void;
onError?: (error: boolean) => void;
error?: string;
};
enum ActionType {
ADD = "ADD",
REMOVE = "REMOVE",
UPDATE = "UPDATE",
}
export const domainReducer = (state: Domain[], action: any): Domain[] => {
switch (action.type) {
case ActionType.ADD:
return [...state, { name: "", id: uniqueId("domain") }];
case ActionType.REMOVE:
return state.filter((_, i) => i !== action.index);
case ActionType.UPDATE:
return state.map((n, i) => (i === action.index ? action.d : n));
default:
return state;
}
};
export default function InputDomain({
value,
onChange,
onRemove,
onError,
}: Readonly<Props>) {
const [name, setName] = useState(value?.name || "");
const handleNameChange = (e: React.ChangeEvent<HTMLInputElement>) => {
setName(e.target.value);
onChange({ ...value, name: e.target.value });
};
const domainError = useMemo(() => {
if (name == "") {
return "";
}
const valid = validator.isValidDomain(name);
if (!valid) {
return "Please enter a valid domain, e.g. example.com or intra.example.com";
}
}, [name]);
useEffect(() => {
const hasError = domainError !== "" && domainError !== undefined;
onError?.(hasError);
return () => onError?.(false);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [domainError]);
return (
<div className={"flex gap-2 w-full"}>
<div className={"w-full"}>
<Input
customPrefix={<GlobeIcon size={15} />}
placeholder={"e.g., example.com"}
maxWidthClass={"w-full"}
value={name}
error={domainError}
onChange={handleNameChange}
/>
</div>
<Button
className={"h-[42px]"}
variant={"default-outline"}
onClick={onRemove}
>
<MinusCircleIcon size={15} />
</Button>
</div>
);
}
+2
View File
@@ -94,6 +94,7 @@ export default function DialogProvider({ children }: Props) {
className={"w-full"}
size={"sm"}
tabIndex={-1}
data-cy={"confirmation.cancel"}
onClick={() => fn.current && fn.current(false)}
>
{dialogOptions.cancelText || "Cancel"}
@@ -109,6 +110,7 @@ export default function DialogProvider({ children }: Props) {
}
className={"w-full"}
size={"sm"}
data-cy={"confirmation.confirm"}
onClick={() => fn.current && fn.current(true)}
>
{dialogOptions.confirmText || "Confirm"}
+2 -4
View File
@@ -77,12 +77,10 @@ export default function PeerProvider({ children, peer }: Props) {
? loginExpiration
: peer.login_expiration_enabled,
approval_required:
approval_required != undefined
? approval_required
: peer.approval_required,
approval_required == undefined ? undefined : approval_required,
},
`/${peer.id}`,
);
) as Promise<Peer>;
};
const openSSHDialog = async (): Promise<boolean> => {
Loaded 20 of 59 files, more files were not shown because too many files have changed in this diff. Show more