mirror of
https://github.com/netbirdio/dashboard.git
synced 2026-09-23 00:30:43 -07:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e167afb06b |
No files matched your search
+2
-1
@@ -12,5 +12,6 @@
|
|||||||
"tokenSource": "$NETBIRD_TOKEN_SOURCE",
|
"tokenSource": "$NETBIRD_TOKEN_SOURCE",
|
||||||
"dragQueryParams": "$NETBIRD_DRAG_QUERY_PARAMS",
|
"dragQueryParams": "$NETBIRD_DRAG_QUERY_PARAMS",
|
||||||
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
|
"hotjarTrackID": "$NETBIRD_HOTJAR_TRACK_ID",
|
||||||
"googleAnalyticsID": "$NETBIRD_GOOGLE_ANALYTICS_ID"
|
"googleAnalyticsID": "$NETBIRD_GOOGLE_ANALYTICS_ID",
|
||||||
|
"authServiceUrl": "$NETBIRD_AUTH_SERVICE_URL"
|
||||||
}
|
}
|
||||||
Generated
+196
-720
File diff suppressed because it is too large.
Load diff
+3
-4
@@ -19,7 +19,6 @@
|
|||||||
"@radix-ui/react-collapsible": "^1.0.3",
|
"@radix-ui/react-collapsible": "^1.0.3",
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
"@radix-ui/react-dropdown-menu": "^2.0.6",
|
||||||
"@radix-ui/react-hover-card": "^1.1.4",
|
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
"@radix-ui/react-label": "^2.0.2",
|
||||||
"@radix-ui/react-popover": "^1.0.7",
|
"@radix-ui/react-popover": "^1.0.7",
|
||||||
"@radix-ui/react-radio-group": "^1.1.3",
|
"@radix-ui/react-radio-group": "^1.1.3",
|
||||||
@@ -56,8 +55,8 @@
|
|||||||
"framer-motion": "^10.16.4",
|
"framer-motion": "^10.16.4",
|
||||||
"ip-cidr": "^3.1.0",
|
"ip-cidr": "^3.1.0",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"lucide-react": "^0.460.0",
|
"lucide-react": "^0.383.0",
|
||||||
"next": "13.5.7",
|
"next": "13.5.5",
|
||||||
"next-themes": "^0.2.1",
|
"next-themes": "^0.2.1",
|
||||||
"punycode": "^2.3.1",
|
"punycode": "^2.3.1",
|
||||||
"react": "^18",
|
"react": "^18",
|
||||||
@@ -77,7 +76,7 @@
|
|||||||
"typescript": "^5"
|
"typescript": "^5"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"cypress": "^13.13.0",
|
"cypress": "^13.3.3",
|
||||||
"postcss": "^8",
|
"postcss": "^8",
|
||||||
"prettier": "3.0.3",
|
"prettier": "3.0.3",
|
||||||
"tailwindcss": "^3"
|
"tailwindcss": "^3"
|
||||||
|
|||||||
@@ -1,8 +0,0 @@
|
|||||||
import { globalMetaTitle } from "@utils/meta";
|
|
||||||
import type { Metadata } from "next";
|
|
||||||
import BlankLayout from "@/layouts/BlankLayout";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: `Network - Networks - ${globalMetaTitle}`,
|
|
||||||
};
|
|
||||||
export default BlankLayout;
|
|
||||||
@@ -1,229 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Breadcrumbs from "@components/Breadcrumbs";
|
|
||||||
import Card from "@components/Card";
|
|
||||||
import FullTooltip from "@components/FullTooltip";
|
|
||||||
import InlineLink from "@components/InlineLink";
|
|
||||||
import Separator from "@components/Separator";
|
|
||||||
import FullScreenLoading from "@components/ui/FullScreenLoading";
|
|
||||||
import useRedirect from "@hooks/useRedirect";
|
|
||||||
import useFetchApi from "@utils/api";
|
|
||||||
import { cn } from "@utils/helpers";
|
|
||||||
import {
|
|
||||||
ArrowUpRightIcon,
|
|
||||||
HelpCircle,
|
|
||||||
PencilLineIcon,
|
|
||||||
ServerIcon,
|
|
||||||
ShieldCheckIcon,
|
|
||||||
ShieldXIcon,
|
|
||||||
} from "lucide-react";
|
|
||||||
import { useSearchParams } from "next/navigation";
|
|
||||||
import React, { useMemo, useState } from "react";
|
|
||||||
import { useSWRConfig } from "swr";
|
|
||||||
import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon";
|
|
||||||
import { useLoggedInUser } from "@/contexts/UsersProvider";
|
|
||||||
import { Network } from "@/interfaces/Network";
|
|
||||||
import PageContainer from "@/layouts/PageContainer";
|
|
||||||
import { NetworkInformationSquare } from "@/modules/networks/misc/NetworkInformationSquare";
|
|
||||||
import NetworkModal from "@/modules/networks/NetworkModal";
|
|
||||||
import { NetworkProvider } from "@/modules/networks/NetworkProvider";
|
|
||||||
import { ResourcesSection } from "@/modules/networks/resources/ResourcesSection";
|
|
||||||
import { NetworkRoutingPeersSection } from "@/modules/networks/routing-peers/NetworkRoutingPeersSection";
|
|
||||||
|
|
||||||
export default function NetworkDetailPage() {
|
|
||||||
const queryParameter = useSearchParams();
|
|
||||||
const networkId = queryParameter.get("id");
|
|
||||||
const { data: network, isLoading } = useFetchApi<Network>(
|
|
||||||
`/networks/${networkId}`,
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
|
|
||||||
useRedirect("/networks", false, !networkId);
|
|
||||||
|
|
||||||
return network && !isLoading ? (
|
|
||||||
<NetworkOverview network={network} />
|
|
||||||
) : (
|
|
||||||
<FullScreenLoading />
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function NetworkOverview({ network }: Readonly<{ network: Network }>) {
|
|
||||||
const { isUser } = useLoggedInUser();
|
|
||||||
const [networkModal, setNetworkModal] = useState(false);
|
|
||||||
const { mutate } = useSWRConfig();
|
|
||||||
|
|
||||||
const isActive = !!(
|
|
||||||
network?.routing_peers_count && network.routing_peers_count > 0
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContainer>
|
|
||||||
<NetworkProvider network={network}>
|
|
||||||
<div className={"p-default py-6 mb-4"}>
|
|
||||||
<Breadcrumbs>
|
|
||||||
<Breadcrumbs.Item
|
|
||||||
href={"/networks"}
|
|
||||||
label={"Networks"}
|
|
||||||
disabled={isUser}
|
|
||||||
icon={<NetworkRoutesIcon size={13} />}
|
|
||||||
/>
|
|
||||||
<Breadcrumbs.Item
|
|
||||||
href={"/network"}
|
|
||||||
label={network.name}
|
|
||||||
active={true}
|
|
||||||
/>
|
|
||||||
</Breadcrumbs>
|
|
||||||
|
|
||||||
<div className={"flex justify-between max-w-6xl"}>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex items-center",
|
|
||||||
!network.description && "gap-2",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NetworkInformationSquare
|
|
||||||
name={network.name}
|
|
||||||
active={isActive}
|
|
||||||
size={"lg"}
|
|
||||||
description={network.description}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
className={
|
|
||||||
"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 cursor-pointer"
|
|
||||||
}
|
|
||||||
onClick={() => setNetworkModal(true)}
|
|
||||||
>
|
|
||||||
<PencilLineIcon size={18} />
|
|
||||||
</button>
|
|
||||||
<NetworkModal
|
|
||||||
open={networkModal}
|
|
||||||
setOpen={setNetworkModal}
|
|
||||||
onUpdated={() => {
|
|
||||||
mutate(`/networks/${network.id}`);
|
|
||||||
}}
|
|
||||||
network={network}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"flex gap-10 w-full mt-8 max-w-6xl items-start"}>
|
|
||||||
<NetworkInformationCard network={network} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Separator />
|
|
||||||
<ResourcesSection network={network} />
|
|
||||||
<div className={"h-3"} />
|
|
||||||
<Separator />
|
|
||||||
<NetworkRoutingPeersSection network={network} />
|
|
||||||
</NetworkProvider>
|
|
||||||
</PageContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
function NetworkInformationCard({ network }: Readonly<{ network: Network }>) {
|
|
||||||
const isHighlyAvailable = !!(
|
|
||||||
network?.routing_peers_count && network?.routing_peers_count >= 2
|
|
||||||
);
|
|
||||||
|
|
||||||
const disabledText = useMemo(
|
|
||||||
() => (
|
|
||||||
<>
|
|
||||||
High availability is currently{" "}
|
|
||||||
<span className={"text-yellow-400 font-medium"}>inactive</span> for this
|
|
||||||
network.
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const enabledText = useMemo(
|
|
||||||
() => (
|
|
||||||
<>
|
|
||||||
High availability is{" "}
|
|
||||||
<span className={"text-green-500 font-medium"}>active</span> for this
|
|
||||||
network.
|
|
||||||
</>
|
|
||||||
),
|
|
||||||
[],
|
|
||||||
);
|
|
||||||
|
|
||||||
const policyCount = network.policies?.length ?? 0;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Card>
|
|
||||||
<Card.List>
|
|
||||||
<Card.ListItem
|
|
||||||
tooltip={false}
|
|
||||||
label={
|
|
||||||
<>
|
|
||||||
<ServerIcon size={16} />
|
|
||||||
High Availability
|
|
||||||
</>
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
<FullTooltip
|
|
||||||
interactive={false}
|
|
||||||
content={
|
|
||||||
<div className={"max-w-xs text-xs"}>
|
|
||||||
{isHighlyAvailable ? enabledText : disabledText}
|
|
||||||
{isHighlyAvailable ? (
|
|
||||||
<div className={"inline-flex mt-2"}>
|
|
||||||
You can add more routing peers to increase the
|
|
||||||
availability of this network.
|
|
||||||
</div>
|
|
||||||
) : (
|
|
||||||
<div className={"inline-flex mt-2"}>
|
|
||||||
Go ahead and add more routing peers or groups with routing
|
|
||||||
peers to enable high availability for this network.
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"flex gap-2.5 items-center text-nb-gray-300 text-sm cursor-help",
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<span
|
|
||||||
className={cn(
|
|
||||||
"h-2 w-2 rounded-full",
|
|
||||||
!isHighlyAvailable ? "bg-yellow-400" : "bg-green-500",
|
|
||||||
)}
|
|
||||||
></span>
|
|
||||||
{isHighlyAvailable ? "Active" : "Inactive"}
|
|
||||||
<HelpCircle size={12} />
|
|
||||||
</div>
|
|
||||||
</FullTooltip>
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
<Card.ListItem
|
|
||||||
tooltip={false}
|
|
||||||
label={
|
|
||||||
policyCount > 0 ? (
|
|
||||||
<>
|
|
||||||
<ShieldCheckIcon size={16} className={"text-green-500"} />
|
|
||||||
{policyCount}{" "}
|
|
||||||
{policyCount === 1 ? "Active Policy" : "Active Policies"}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<ShieldXIcon size={16} className={"text-red-500"} />
|
|
||||||
No Active Policies
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
value={
|
|
||||||
policyCount > 0 ? (
|
|
||||||
<InlineLink href={"/access-control"}>
|
|
||||||
Go to Policies
|
|
||||||
<ArrowUpRightIcon size={14} />
|
|
||||||
</InlineLink>
|
|
||||||
) : null
|
|
||||||
}
|
|
||||||
/>
|
|
||||||
</Card.List>
|
|
||||||
</Card>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import { globalMetaTitle } from "@utils/meta";
|
|
||||||
import type { Metadata } from "next";
|
|
||||||
import BlankLayout from "@/layouts/BlankLayout";
|
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
|
||||||
title: `Networks - ${globalMetaTitle}`,
|
|
||||||
};
|
|
||||||
export default BlankLayout;
|
|
||||||
@@ -1,61 +0,0 @@
|
|||||||
"use client";
|
|
||||||
|
|
||||||
import Breadcrumbs from "@components/Breadcrumbs";
|
|
||||||
import InlineLink from "@components/InlineLink";
|
|
||||||
import Paragraph from "@components/Paragraph";
|
|
||||||
import SkeletonTable from "@components/skeletons/SkeletonTable";
|
|
||||||
import { RestrictedAccess } from "@components/ui/RestrictedAccess";
|
|
||||||
import { usePortalElement } from "@hooks/usePortalElement";
|
|
||||||
import useFetchApi from "@utils/api";
|
|
||||||
import { ExternalLinkIcon } from "lucide-react";
|
|
||||||
import React, { Suspense } from "react";
|
|
||||||
import NetworkRoutesIcon from "@/assets/icons/NetworkRoutesIcon";
|
|
||||||
import { Network } from "@/interfaces/Network";
|
|
||||||
import PageContainer from "@/layouts/PageContainer";
|
|
||||||
import NetworksTable from "@/modules/networks/table/NetworksTable";
|
|
||||||
|
|
||||||
export default function Networks() {
|
|
||||||
const { data: networks, isLoading } = useFetchApi<Network[]>("/networks");
|
|
||||||
const { ref: headingRef, portalTarget } =
|
|
||||||
usePortalElement<HTMLHeadingElement>();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<PageContainer>
|
|
||||||
<div className={"p-default py-6"}>
|
|
||||||
<Breadcrumbs>
|
|
||||||
<Breadcrumbs.Item
|
|
||||||
href={"/networks"}
|
|
||||||
label={"Networks"}
|
|
||||||
icon={<NetworkRoutesIcon size={13} />}
|
|
||||||
/>
|
|
||||||
</Breadcrumbs>
|
|
||||||
<h1 ref={headingRef}>Networks</h1>
|
|
||||||
<Paragraph>
|
|
||||||
Networks allow you to access internal resources in LANs and VPCs without
|
|
||||||
installing NetBird on every machine.
|
|
||||||
</Paragraph>
|
|
||||||
<Paragraph>
|
|
||||||
Learn more about
|
|
||||||
<InlineLink
|
|
||||||
href={"https://docs.netbird.io/how-to/networks"}
|
|
||||||
target={"_blank"}
|
|
||||||
>
|
|
||||||
Networks
|
|
||||||
<ExternalLinkIcon size={12} />
|
|
||||||
</InlineLink>
|
|
||||||
in our documentation.
|
|
||||||
</Paragraph>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<RestrictedAccess>
|
|
||||||
<Suspense fallback={<SkeletonTable />}>
|
|
||||||
<NetworksTable
|
|
||||||
data={networks}
|
|
||||||
isLoading={isLoading}
|
|
||||||
headingTarget={portalTarget}
|
|
||||||
/>
|
|
||||||
</Suspense>
|
|
||||||
</RestrictedAccess>
|
|
||||||
</PageContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -297,12 +297,29 @@ function PeerOverview() {
|
|||||||
/>
|
/>
|
||||||
</FullTooltip>
|
</FullTooltip>
|
||||||
|
|
||||||
{!isUser && (
|
<div>
|
||||||
<div>
|
<Label>Assigned Groups</Label>
|
||||||
<Label>Assigned Groups</Label>
|
<HelpText>
|
||||||
<HelpText>
|
Use groups to control what this peer can access.
|
||||||
Use groups to control what this peer can access.
|
</HelpText>
|
||||||
</HelpText>
|
<FullTooltip
|
||||||
|
content={
|
||||||
|
<div
|
||||||
|
className={
|
||||||
|
"flex gap-2 items-center !text-nb-gray-300 text-xs"
|
||||||
|
}
|
||||||
|
>
|
||||||
|
<LockIcon size={14} />
|
||||||
|
<span>
|
||||||
|
{`You don't have the required permissions to update this
|
||||||
|
setting.`}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
interactive={false}
|
||||||
|
className={"w-full block"}
|
||||||
|
disabled={!isUser}
|
||||||
|
>
|
||||||
<PeerGroupSelector
|
<PeerGroupSelector
|
||||||
disabled={isUser}
|
disabled={isUser}
|
||||||
onChange={setSelectedGroups}
|
onChange={setSelectedGroups}
|
||||||
@@ -310,8 +327,8 @@ function PeerOverview() {
|
|||||||
hideAllGroup={true}
|
hideAllGroup={true}
|
||||||
peer={peer}
|
peer={peer}
|
||||||
/>
|
/>
|
||||||
</div>
|
</FullTooltip>
|
||||||
)}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -6,18 +6,17 @@ import {
|
|||||||
AlertOctagonIcon,
|
AlertOctagonIcon,
|
||||||
FolderGit2Icon,
|
FolderGit2Icon,
|
||||||
LockIcon,
|
LockIcon,
|
||||||
NetworkIcon,
|
|
||||||
ShieldIcon,
|
ShieldIcon,
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import { useSearchParams } from "next/navigation";
|
import { useSearchParams } from "next/navigation";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { useLoggedInUser } from "@/contexts/UsersProvider";
|
import { useLoggedInUser } from "@/contexts/UsersProvider";
|
||||||
|
import { Role } from "@/interfaces/User";
|
||||||
import PageContainer from "@/layouts/PageContainer";
|
import PageContainer from "@/layouts/PageContainer";
|
||||||
import { useAccount } from "@/modules/account/useAccount";
|
import { useAccount } from "@/modules/account/useAccount";
|
||||||
import AuthenticationTab from "@/modules/settings/AuthenticationTab";
|
import AuthenticationTab from "@/modules/settings/AuthenticationTab";
|
||||||
import DangerZoneTab from "@/modules/settings/DangerZoneTab";
|
import DangerZoneTab from "@/modules/settings/DangerZoneTab";
|
||||||
import GroupsTab from "@/modules/settings/GroupsTab";
|
import GroupsTab from "@/modules/settings/GroupsTab";
|
||||||
import NetworkSettingsTab from "@/modules/settings/NetworkSettingsTab";
|
|
||||||
import PermissionsTab from "@/modules/settings/PermissionsTab";
|
import PermissionsTab from "@/modules/settings/PermissionsTab";
|
||||||
|
|
||||||
export default function NetBirdSettings() {
|
export default function NetBirdSettings() {
|
||||||
@@ -49,21 +48,19 @@ export default function NetBirdSettings() {
|
|||||||
<LockIcon size={14} />
|
<LockIcon size={14} />
|
||||||
Permissions
|
Permissions
|
||||||
</VerticalTabs.Trigger>
|
</VerticalTabs.Trigger>
|
||||||
<VerticalTabs.Trigger value="networks">
|
|
||||||
<NetworkIcon size={14} />
|
|
||||||
Networks
|
|
||||||
</VerticalTabs.Trigger>
|
|
||||||
<VerticalTabs.Trigger value="danger-zone" disabled={!isOwner}>
|
<VerticalTabs.Trigger value="danger-zone" disabled={!isOwner}>
|
||||||
<AlertOctagonIcon size={14} />
|
<AlertOctagonIcon size={14} />
|
||||||
Danger zone
|
Danger zone
|
||||||
</VerticalTabs.Trigger>
|
</VerticalTabs.Trigger>
|
||||||
</VerticalTabs.List>
|
</VerticalTabs.List>
|
||||||
<RestrictedAccess page={"Settings"}>
|
<RestrictedAccess
|
||||||
|
page={"Settings"}
|
||||||
|
allow={[Role.Admin, Role.Owner, Role.BillingAdmin]}
|
||||||
|
>
|
||||||
<div className={"border-l border-nb-gray-930 w-full"}>
|
<div className={"border-l border-nb-gray-930 w-full"}>
|
||||||
{account && <AuthenticationTab account={account} />}
|
{account && <AuthenticationTab account={account} />}
|
||||||
{account && <PermissionsTab account={account} />}
|
{account && <PermissionsTab account={account} />}
|
||||||
{account && <GroupsTab account={account} />}
|
{account && <GroupsTab account={account} />}
|
||||||
{account && <NetworkSettingsTab account={account} />}
|
|
||||||
{account && <DangerZoneTab account={account} />}
|
{account && <DangerZoneTab account={account} />}
|
||||||
</div>
|
</div>
|
||||||
</RestrictedAccess>
|
</RestrictedAccess>
|
||||||
|
|||||||
@@ -316,7 +316,6 @@ function UserInformationCard({ user }: { user: User }) {
|
|||||||
<>
|
<>
|
||||||
{!user.is_current && user.role != Role.Owner && (
|
{!user.is_current && user.role != Role.Owner && (
|
||||||
<Card.ListItem
|
<Card.ListItem
|
||||||
tooltip={false}
|
|
||||||
label={
|
label={
|
||||||
<>
|
<>
|
||||||
<Ban size={16} />
|
<Ban size={16} />
|
||||||
|
|||||||
@@ -15,9 +15,7 @@ export const OIDCError = () => {
|
|||||||
const params = useSearchParams();
|
const params = useSearchParams();
|
||||||
const errorParam = params.get("error");
|
const errorParam = params.get("error");
|
||||||
const accessDenied = errorParam === "access_denied";
|
const accessDenied = errorParam === "access_denied";
|
||||||
const invalidRequest = errorParam === "invalid_request";
|
|
||||||
const [title, setTitle] = useState(params.get("error_description"));
|
const [title, setTitle] = useState(params.get("error_description"));
|
||||||
const errorDescription = params.get("error_description");
|
|
||||||
const { logout, login } = useOidc();
|
const { logout, login } = useOidc();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -74,14 +72,9 @@ export const OIDCError = () => {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Paragraph className={"text-center mt-2 block"}>
|
<Paragraph className={"text-center mt-2"}>
|
||||||
There was an error logging you in. <br />
|
There was an error logging you in. <br />
|
||||||
Error:{" "}
|
Error: {oidcUserLoadingState}
|
||||||
<span className={"inline capitalize"}>
|
|
||||||
{invalidRequest && errorDescription
|
|
||||||
? errorDescription
|
|
||||||
: oidcUserLoadingState}
|
|
||||||
</span>
|
|
||||||
</Paragraph>
|
</Paragraph>
|
||||||
<Button
|
<Button
|
||||||
variant={"primary"}
|
variant={"primary"}
|
||||||
|
|||||||
@@ -35,11 +35,6 @@ export const buttonVariants = cva(
|
|||||||
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
|
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
|
||||||
"dark:bg-nb-gray-900/30 dark:text-gray-400 dark:border-gray-700/40 dark:hover:text-white dark:hover:bg-zinc-800/50",
|
"dark:bg-nb-gray-900/30 dark:text-gray-400 dark:border-gray-700/40 dark:hover:text-white dark:hover:bg-zinc-800/50",
|
||||||
],
|
],
|
||||||
secondaryLighter: [
|
|
||||||
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-gray-200 text-gray-900",
|
|
||||||
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
|
|
||||||
"dark:bg-nb-gray-900/70 dark:text-gray-400 dark:border-gray-700/70 dark:hover:text-white dark:hover:bg-nb-gray-800/60",
|
|
||||||
],
|
|
||||||
input: [
|
input: [
|
||||||
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-neutral-200 text-gray-900",
|
"bg-white hover:text-black focus:ring-zinc-200/50 hover:bg-gray-100 border-neutral-200 text-gray-900",
|
||||||
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
|
"dark:ring-offset-neutral-950/50 dark:focus:ring-neutral-500/20 ",
|
||||||
|
|||||||
@@ -1,15 +1,12 @@
|
|||||||
import FullTooltip from "@components/FullTooltip";
|
import FullTooltip from "@components/FullTooltip";
|
||||||
import Paragraph from "@components/Paragraph";
|
import Paragraph from "@components/Paragraph";
|
||||||
import { cn } from "@utils/helpers";
|
import { cn } from "@utils/helpers";
|
||||||
import { cva, VariantProps } from "class-variance-authority";
|
import { cva } from "class-variance-authority";
|
||||||
import { AlertCircle } from "lucide-react";
|
import { AlertCircle } from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
|
|
||||||
type InputVariants = VariantProps<typeof inputVariants>;
|
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement>,
|
extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
InputVariants {
|
|
||||||
customPrefix?: React.ReactNode;
|
customPrefix?: React.ReactNode;
|
||||||
customSuffix?: React.ReactNode;
|
customSuffix?: React.ReactNode;
|
||||||
maxWidthClass?: string;
|
maxWidthClass?: string;
|
||||||
@@ -17,7 +14,6 @@ export interface InputProps
|
|||||||
error?: string;
|
error?: string;
|
||||||
errorTooltip?: boolean;
|
errorTooltip?: boolean;
|
||||||
errorTooltipPosition?: "top" | "top-right";
|
errorTooltipPosition?: "top" | "top-right";
|
||||||
prefixClassName?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const inputVariants = cva("", {
|
const inputVariants = cva("", {
|
||||||
@@ -27,10 +23,6 @@ const inputVariants = cva("", {
|
|||||||
"dark:bg-nb-gray-900 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-200 dark:border-nb-gray-700",
|
"dark:bg-nb-gray-900 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-200 dark:border-nb-gray-700",
|
||||||
"ring-offset-neutral-200/20 dark:ring-offset-neutral-950/50 dark:focus-visible:ring-neutral-500/20 focus-visible:ring-neutral-300/10",
|
"ring-offset-neutral-200/20 dark:ring-offset-neutral-950/50 dark:focus-visible:ring-neutral-500/20 focus-visible:ring-neutral-300/10",
|
||||||
],
|
],
|
||||||
darker: [
|
|
||||||
"dark:bg-nb-gray-920 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-300 dark:border-nb-gray-800",
|
|
||||||
"ring-offset-neutral-200/20 dark:ring-offset-neutral-950/50 dark:focus-visible:ring-neutral-500/20 focus-visible:ring-neutral-300/10",
|
|
||||||
],
|
|
||||||
error: [
|
error: [
|
||||||
"dark:bg-nb-gray-900 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-200 dark:border-red-500 text-red-500",
|
"dark:bg-nb-gray-900 dark:placeholder:text-neutral-400/70 placeholder:text-neutral-500 border-neutral-200 dark:border-red-500 text-red-500",
|
||||||
"ring-offset-red-500/10 dark:ring-offset-red-500/10 dark:focus-visible:ring-red-500/10 focus-visible:ring-red-500/10",
|
"ring-offset-red-500/10 dark:ring-offset-red-500/10 dark:focus-visible:ring-red-500/10 focus-visible:ring-red-500/10",
|
||||||
@@ -59,8 +51,6 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
error,
|
error,
|
||||||
errorTooltip = false,
|
errorTooltip = false,
|
||||||
errorTooltipPosition = "top",
|
errorTooltipPosition = "top",
|
||||||
variant = "default",
|
|
||||||
prefixClassName,
|
|
||||||
...props
|
...props
|
||||||
},
|
},
|
||||||
ref,
|
ref,
|
||||||
@@ -77,7 +67,6 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
"flex h-[42px] w-auto rounded-l-md bg-white px-3 py-2 text-sm ",
|
"flex h-[42px] w-auto rounded-l-md bg-white px-3 py-2 text-sm ",
|
||||||
"border items-center whitespace-nowrap",
|
"border items-center whitespace-nowrap",
|
||||||
props.disabled && "opacity-20",
|
props.disabled && "opacity-20",
|
||||||
prefixClassName,
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{customPrefix}
|
{customPrefix}
|
||||||
@@ -98,7 +87,7 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
ref={ref}
|
ref={ref}
|
||||||
{...props}
|
{...props}
|
||||||
className={cn(
|
className={cn(
|
||||||
inputVariants({ variant: error ? "error" : variant }),
|
inputVariants({ variant: error ? "error" : "default" }),
|
||||||
"flex h-[42px] w-full rounded-md bg-white px-3 py-2 text-sm file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-20 ",
|
"flex h-[42px] w-full rounded-md bg-white px-3 py-2 text-sm file:bg-transparent file:text-sm file:font-medium focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-20 ",
|
||||||
"file:border-0",
|
"file:border-0",
|
||||||
"focus-visible:ring-2 focus-visible:ring-offset-2",
|
"focus-visible:ring-2 focus-visible:ring-offset-2",
|
||||||
|
|||||||
@@ -7,31 +7,22 @@ import { ScrollArea } from "@components/ScrollArea";
|
|||||||
import { AccessControlGroupCount } from "@components/ui/AccessControlGroupCount";
|
import { AccessControlGroupCount } from "@components/ui/AccessControlGroupCount";
|
||||||
import GroupBadge from "@components/ui/GroupBadge";
|
import GroupBadge from "@components/ui/GroupBadge";
|
||||||
import GroupBadgeWithEditPeers from "@components/ui/GroupBadgeWithEditPeers";
|
import GroupBadgeWithEditPeers from "@components/ui/GroupBadgeWithEditPeers";
|
||||||
import TextWithTooltip from "@components/ui/TextWithTooltip";
|
|
||||||
import { VirtualScrollAreaList } from "@components/VirtualScrollAreaList";
|
|
||||||
import { useSearch } from "@hooks/useSearch";
|
|
||||||
import useSortedDropdownOptions from "@hooks/useSortedDropdownOptions";
|
import useSortedDropdownOptions from "@hooks/useSortedDropdownOptions";
|
||||||
import { IconArrowBack } from "@tabler/icons-react";
|
import { IconArrowBack } from "@tabler/icons-react";
|
||||||
import useFetchApi from "@utils/api";
|
|
||||||
import { cn } from "@utils/helpers";
|
import { cn } from "@utils/helpers";
|
||||||
import { Command, CommandGroup, CommandInput, CommandList } from "cmdk";
|
import { Command, CommandGroup, CommandInput, CommandList } from "cmdk";
|
||||||
import { sortBy, trim, unionBy } from "lodash";
|
import { sortBy, trim, unionBy } from "lodash";
|
||||||
import {
|
import {
|
||||||
ChevronsUpDown,
|
ChevronsUpDown,
|
||||||
FolderGit2,
|
FolderGit2,
|
||||||
GlobeIcon,
|
|
||||||
Layers3,
|
|
||||||
MonitorSmartphoneIcon,
|
MonitorSmartphoneIcon,
|
||||||
NetworkIcon,
|
|
||||||
SearchIcon,
|
SearchIcon,
|
||||||
WorkflowIcon,
|
|
||||||
} from "lucide-react";
|
} from "lucide-react";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
import { Fragment, useEffect, useMemo, useState } from "react";
|
import { useEffect, useMemo, useState } from "react";
|
||||||
import { useGroups } from "@/contexts/GroupsProvider";
|
import { useGroups } from "@/contexts/GroupsProvider";
|
||||||
import { useElementSize } from "@/hooks/useElementSize";
|
import { useElementSize } from "@/hooks/useElementSize";
|
||||||
import type { Group, GroupPeer, GroupResource } from "@/interfaces/Group";
|
import type { Group, GroupPeer } from "@/interfaces/Group";
|
||||||
import { NetworkResource } from "@/interfaces/Network";
|
|
||||||
import type { Peer } from "@/interfaces/Peer";
|
import type { Peer } from "@/interfaces/Peer";
|
||||||
|
|
||||||
interface MultiSelectProps {
|
interface MultiSelectProps {
|
||||||
@@ -48,7 +39,6 @@ interface MultiSelectProps {
|
|||||||
showRoutes?: boolean;
|
showRoutes?: boolean;
|
||||||
disabledGroups?: Group[];
|
disabledGroups?: Group[];
|
||||||
dataCy?: string;
|
dataCy?: string;
|
||||||
showResourceCounter?: boolean;
|
|
||||||
}
|
}
|
||||||
export function PeerGroupSelector({
|
export function PeerGroupSelector({
|
||||||
onChange,
|
onChange,
|
||||||
@@ -64,7 +54,6 @@ export function PeerGroupSelector({
|
|||||||
showRoutes = false,
|
showRoutes = false,
|
||||||
disabledGroups,
|
disabledGroups,
|
||||||
dataCy = "group-selector-dropdown",
|
dataCy = "group-selector-dropdown",
|
||||||
showResourceCounter = true,
|
|
||||||
}: Readonly<MultiSelectProps>) {
|
}: Readonly<MultiSelectProps>) {
|
||||||
const { groups, dropdownOptions, setDropdownOptions, addDropdownOptions } =
|
const { groups, dropdownOptions, setDropdownOptions, addDropdownOptions } =
|
||||||
useGroups();
|
useGroups();
|
||||||
@@ -106,35 +95,19 @@ export function PeerGroupSelector({
|
|||||||
const option = dropdownOptions.find((option) => option.name == name);
|
const option = dropdownOptions.find((option) => option.name == name);
|
||||||
const groupPeers: GroupPeer[] | undefined =
|
const groupPeers: GroupPeer[] | undefined =
|
||||||
(group?.peers as GroupPeer[]) || [];
|
(group?.peers as GroupPeer[]) || [];
|
||||||
const groupResources: GroupResource[] | undefined =
|
|
||||||
(group?.resources as GroupResource[]) || [];
|
|
||||||
|
|
||||||
if (peer) groupPeers?.push({ id: peer?.id as string, name: peer?.name });
|
if (peer) groupPeers?.push({ id: peer?.id as string, name: peer?.name });
|
||||||
|
|
||||||
if (!group && !option) {
|
if (!group && !option) {
|
||||||
addDropdownOptions([
|
addDropdownOptions([{ name: name, peers: groupPeers }]);
|
||||||
{ name: name, peers: groupPeers, resources: groupResources },
|
|
||||||
]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (max == 1 && values.length == 1) {
|
if (max == 1 && values.length == 1) {
|
||||||
onChange([
|
onChange([{ name: name, id: group?.id, peers: groupPeers }]);
|
||||||
{
|
|
||||||
name: name,
|
|
||||||
id: group?.id,
|
|
||||||
peers: groupPeers,
|
|
||||||
resources: groupResources,
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
} else {
|
} else {
|
||||||
onChange((previous) => [
|
onChange((previous) => [
|
||||||
...previous,
|
...previous,
|
||||||
{
|
{ name: name, id: group?.id, peers: groupPeers },
|
||||||
name: name,
|
|
||||||
id: group?.id,
|
|
||||||
peers: groupPeers,
|
|
||||||
resources: groupResources,
|
|
||||||
},
|
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -412,10 +385,6 @@ export function PeerGroupSelector({
|
|||||||
<AccessControlGroupCount group_id={option.id} />
|
<AccessControlGroupCount group_id={option.id} />
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{showResourceCounter && (
|
|
||||||
<ResourcesCounter group={option} />
|
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={
|
className={
|
||||||
"text-neutral-500 dark:text-nb-gray-300 font-medium flex items-center gap-2"
|
"text-neutral-500 dark:text-nb-gray-300 font-medium flex items-center gap-2"
|
||||||
@@ -438,99 +407,3 @@ export function PeerGroupSelector({
|
|||||||
</Popover>
|
</Popover>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const ResourcesCounter = ({ group }: { group: Group }) => {
|
|
||||||
return group?.resources_count && group.resources_count > 0 ? (
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"text-nb-gray-300 font-medium flex items-center gap-2 transition-all"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Layers3 size={14} className={"shrink-0"} />
|
|
||||||
{group.resources_count} Resource(s)
|
|
||||||
</div>
|
|
||||||
) : null;
|
|
||||||
};
|
|
||||||
|
|
||||||
const resourcesSearchPredicate = (item: NetworkResource, query: string) => {
|
|
||||||
const lowerCaseQuery = query.toLowerCase();
|
|
||||||
if (item.name.toLowerCase().includes(lowerCaseQuery)) return true;
|
|
||||||
return item.address.toLowerCase().includes(lowerCaseQuery);
|
|
||||||
};
|
|
||||||
|
|
||||||
const ResourcesList = ({ search }: { search: string }) => {
|
|
||||||
const { data: resources, isLoading } = useFetchApi<NetworkResource[]>(
|
|
||||||
"/networks/resources",
|
|
||||||
);
|
|
||||||
|
|
||||||
const [filteredItems, _, setSearch] = useSearch(
|
|
||||||
resources || [],
|
|
||||||
resourcesSearchPredicate,
|
|
||||||
{ filter: true, debounce: 150 },
|
|
||||||
);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setSearch(search);
|
|
||||||
}, [search, setSearch]);
|
|
||||||
|
|
||||||
return isLoading ? (
|
|
||||||
<>Loading...</>
|
|
||||||
) : (
|
|
||||||
filteredItems.length > 0 && (
|
|
||||||
<VirtualScrollAreaList
|
|
||||||
items={filteredItems}
|
|
||||||
onSelect={(option) => null}
|
|
||||||
renderItem={(res) => {
|
|
||||||
const isSelected = false;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Fragment key={res.id}>
|
|
||||||
<div className={"flex items-center gap-2"}>
|
|
||||||
<Badge
|
|
||||||
useHover={true}
|
|
||||||
data-cy={"group-badge"}
|
|
||||||
variant={"gray-ghost"}
|
|
||||||
className={cn("transition-all group whitespace-nowrap")}
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault();
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{res.type === "host" && (
|
|
||||||
<WorkflowIcon
|
|
||||||
size={12}
|
|
||||||
className={"text-yellow-400 shrink-0"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{res.type === "domain" && (
|
|
||||||
<GlobeIcon
|
|
||||||
size={12}
|
|
||||||
className={"text-yellow-400 shrink-0"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
{res.type === "subnet" && (
|
|
||||||
<NetworkIcon
|
|
||||||
size={12}
|
|
||||||
className={"text-yellow-400 shrink-0"}
|
|
||||||
/>
|
|
||||||
)}
|
|
||||||
|
|
||||||
<TextWithTooltip text={res?.name || ""} maxChars={20} />
|
|
||||||
</Badge>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className={"flex items-center gap-5"}>
|
|
||||||
<div
|
|
||||||
className={
|
|
||||||
"text-neutral-500 dark:text-nb-gray-300 font-medium flex items-center gap-2"
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<Checkbox checked={isSelected} />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</Fragment>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -166,17 +166,15 @@ export function PeerSelector({
|
|||||||
placeholder={"Search for peers by name or ip..."}
|
placeholder={"Search for peers by name or ip..."}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{unfilteredItems.length == 0 && !search && (
|
{unfilteredItems.length == 0 && (
|
||||||
<div className={"max-w-xs mx-auto"}>
|
<DropdownInfoText>
|
||||||
<DropdownInfoText>
|
{
|
||||||
{
|
"Seems like you don't have any linux peers to assign as a routing peer."
|
||||||
"Seems like you don't have any Linux peers to assign as a routing peer."
|
}
|
||||||
}
|
</DropdownInfoText>
|
||||||
</DropdownInfoText>
|
|
||||||
</div>
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{filteredItems.length == 0 && search != "" && (
|
{filteredItems.length == 0 && (
|
||||||
<DropdownInfoText>
|
<DropdownInfoText>
|
||||||
There are no peers matching your search.
|
There are no peers matching your search.
|
||||||
</DropdownInfoText>
|
</DropdownInfoText>
|
||||||
|
|||||||
@@ -1,3 +1,5 @@
|
|||||||
|
"use client";
|
||||||
|
|
||||||
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
|
||||||
import { cn } from "@utils/helpers";
|
import { cn } from "@utils/helpers";
|
||||||
import * as React from "react";
|
import * as React from "react";
|
||||||
@@ -13,31 +15,46 @@ const ScrollArea = React.forwardRef<
|
|||||||
>(({ className, children, withoutViewport = false, ...props }, ref) => (
|
>(({ className, children, withoutViewport = false, ...props }, ref) => (
|
||||||
<ScrollAreaPrimitive.Root
|
<ScrollAreaPrimitive.Root
|
||||||
ref={ref}
|
ref={ref}
|
||||||
className={cn("relative overflow-hidden", className)}
|
className={cn(
|
||||||
|
"relative will-change-scroll webkit-scroll",
|
||||||
|
className,
|
||||||
|
"overflow-hidden",
|
||||||
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
{withoutViewport ? (
|
{withoutViewport ? (
|
||||||
children
|
children
|
||||||
) : (
|
) : (
|
||||||
<ScrollAreaViewport>{children}</ScrollAreaViewport>
|
<ScrollAreaViewport disableOverflowY={false}>
|
||||||
|
{children}
|
||||||
|
</ScrollAreaViewport>
|
||||||
)}
|
)}
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar />
|
||||||
<ScrollBar orientation="vertical" />
|
|
||||||
<ScrollAreaPrimitive.Corner />
|
<ScrollAreaPrimitive.Corner />
|
||||||
</ScrollAreaPrimitive.Root>
|
</ScrollAreaPrimitive.Root>
|
||||||
));
|
));
|
||||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName;
|
||||||
|
|
||||||
|
type AdditionalScrollAreaViewportProps = {
|
||||||
|
disableOverflowY?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
const ScrollAreaViewport = React.forwardRef<
|
const ScrollAreaViewport = React.forwardRef<
|
||||||
React.ElementRef<typeof ScrollAreaPrimitive.Viewport>,
|
React.ElementRef<typeof ScrollAreaPrimitive.Viewport>,
|
||||||
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Viewport>
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Viewport> &
|
||||||
>(({ className, ...props }, ref) => (
|
AdditionalScrollAreaViewportProps
|
||||||
<ScrollAreaPrimitive.Viewport
|
>(({ disableOverflowY = true, ...props }, ref) => {
|
||||||
ref={ref}
|
return (
|
||||||
className={cn("h-full w-full rounded-[inherit]", className)}
|
<ScrollAreaPrimitive.Viewport
|
||||||
{...props}
|
ref={ref}
|
||||||
/>
|
className="h-full w-full rounded-[inherit] will-change-scroll webkit-scroll"
|
||||||
));
|
{...props}
|
||||||
|
style={
|
||||||
|
disableOverflowY ? { overflowY: undefined, ...props.style } : undefined
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
ScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName;
|
ScrollAreaViewport.displayName = ScrollAreaPrimitive.Viewport.displayName;
|
||||||
|
|
||||||
const ScrollBar = React.forwardRef<
|
const ScrollBar = React.forwardRef<
|
||||||
@@ -46,11 +63,14 @@ const ScrollBar = React.forwardRef<
|
|||||||
>(({ className, orientation = "vertical", ...props }, ref) => (
|
>(({ className, orientation = "vertical", ...props }, ref) => (
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
style={{ boxSizing: "unset", overflow: undefined }}
|
||||||
orientation={orientation}
|
orientation={orientation}
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex select-none touch-none transition-colors",
|
"flex touch-none select-none transition-colors",
|
||||||
orientation === "vertical" && "h-full w-2.5 p-[1px]",
|
orientation === "vertical" &&
|
||||||
orientation === "horizontal" && "w-full h-2.5 p-[1px] bottom-0",
|
"h-full w-2.5 border-l border-l-transparent p-[1px]",
|
||||||
|
orientation === "horizontal" &&
|
||||||
|
"h-2.5 border-t border-t-transparent p-[1px]",
|
||||||
className,
|
className,
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
@@ -59,7 +79,6 @@ const ScrollBar = React.forwardRef<
|
|||||||
className={cn(
|
className={cn(
|
||||||
"relative rounded-full bg-neutral-200 dark:bg-nb-gray-800",
|
"relative rounded-full bg-neutral-200 dark:bg-nb-gray-800",
|
||||||
orientation === "vertical" && "flex-1",
|
orientation === "vertical" && "flex-1",
|
||||||
orientation === "horizontal" && "h-full",
|
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
||||||
|
|||||||
@@ -60,12 +60,10 @@ export default function SidebarItem({
|
|||||||
<li className={"px-4 cursor-pointer"}>
|
<li className={"px-4 cursor-pointer"}>
|
||||||
<button
|
<button
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"rounded-lg text-[.87rem] w-full ",
|
"rounded-lg text-[.95rem] w-full ",
|
||||||
"font-normal ",
|
"font-normal ",
|
||||||
className,
|
className,
|
||||||
isChild
|
isChild ? "pl-7 pr-2 py-2 mt-1 mb-0.5" : "py-2 px-3",
|
||||||
? "pl-7 pr-2 py-[.45rem] mt-1 mb-0.5"
|
|
||||||
: "py-[.45rem] px-3",
|
|
||||||
isActive
|
isActive
|
||||||
? "text-gray-900 bg-gray-200 dark:text-white dark:bg-nb-gray-900"
|
? "text-gray-900 bg-gray-200 dark:text-white dark:bg-nb-gray-900"
|
||||||
: "text-gray-600 hover:bg-gray-200 dark:text-nb-gray-400 dark:hover:bg-nb-gray-900/50",
|
: "text-gray-600 hover:bg-gray-200 dark:text-nb-gray-400 dark:hover:bg-nb-gray-900/50",
|
||||||
|
|||||||
@@ -4,18 +4,9 @@ import React from "react";
|
|||||||
type Props = {
|
type Props = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
className?: string;
|
className?: string;
|
||||||
horizontal?: boolean;
|
|
||||||
};
|
};
|
||||||
export default function Steps({
|
export default function Steps({ children, className }: Props) {
|
||||||
children,
|
return <div className={cn("pt-4", className)}>{children}</div>;
|
||||||
className,
|
|
||||||
horizontal = false,
|
|
||||||
}: Readonly<Props>) {
|
|
||||||
return (
|
|
||||||
<div className={cn("pt-4", horizontal && "flex", className)}>
|
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type StepProps = {
|
type StepProps = {
|
||||||
@@ -23,32 +14,21 @@ type StepProps = {
|
|||||||
step: number;
|
step: number;
|
||||||
line?: boolean;
|
line?: boolean;
|
||||||
center?: boolean;
|
center?: boolean;
|
||||||
horizontal?: boolean;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const Step = ({
|
const Step = ({ children, step, line = true, center = false }: StepProps) => {
|
||||||
children,
|
|
||||||
step,
|
|
||||||
line = true,
|
|
||||||
center = false,
|
|
||||||
horizontal,
|
|
||||||
}: StepProps) => {
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
className={cn(
|
className={cn(
|
||||||
"flex gap-4 items-start justify-start relative pb-6 -mx-1.5 group px-[2px]",
|
"flex gap-4 items-start min-w-full justify-start relative pb-6 -mx-1.5 group px-[2px]",
|
||||||
center && "items-center",
|
center && "items-center",
|
||||||
horizontal ? "flex-col items-center" : "min-w-full",
|
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{line && (
|
{line && (
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={
|
||||||
"bg-nb-gray-100 dark:bg-nb-gray-800 z-0 transition-all",
|
"h-full w-[2px] bg-nb-gray-100 dark:bg-nb-gray-800 absolute left-0 ml-[18px] z-0 transition-all"
|
||||||
horizontal
|
}
|
||||||
? "w-full h-[2px] absolute mt-[16px] transform translate-x-1/2"
|
|
||||||
: "h-full w-[2px] absolute left-0 ml-[18px]",
|
|
||||||
)}
|
|
||||||
></span>
|
></span>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
@@ -39,7 +39,7 @@ const Tabs = React.forwardRef<
|
|||||||
Tabs.displayName = TabsPrimitive.Root.displayName;
|
Tabs.displayName = TabsPrimitive.Root.displayName;
|
||||||
|
|
||||||
type TabListProps = {
|
type TabListProps = {
|
||||||
justify?: "start" | "end" | "center" | "between";
|
justify?: "start" | "end" | "center";
|
||||||
};
|
};
|
||||||
|
|
||||||
const TabsList = React.forwardRef<
|
const TabsList = React.forwardRef<
|
||||||
@@ -54,7 +54,6 @@ const TabsList = React.forwardRef<
|
|||||||
justify == "center" && "justify-center justify-items-end",
|
justify == "center" && "justify-center justify-items-end",
|
||||||
justify == "start" && "justify-start",
|
justify == "start" && "justify-start",
|
||||||
justify == "end" && "justify-end",
|
justify == "end" && "justify-end",
|
||||||
justify == "between" && "justify-between",
|
|
||||||
)}
|
)}
|
||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
@@ -64,9 +63,7 @@ const TabsList = React.forwardRef<
|
|||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
<ScrollArea>
|
<ScrollArea>
|
||||||
<div className={"relative z-[1] flex flex-nowrap w-full "}>
|
<div className={"relative z-[1] flex flex-nowrap"}>{props.children}</div>
|
||||||
{props.children}
|
|
||||||
</div>
|
|
||||||
<ScrollBar orientation="horizontal" />
|
<ScrollBar orientation="horizontal" />
|
||||||
</ScrollArea>
|
</ScrollArea>
|
||||||
</TabsPrimitive.List>
|
</TabsPrimitive.List>
|
||||||
|
|||||||
@@ -11,36 +11,17 @@ type Props = {
|
|||||||
onChange: (value: string) => void;
|
onChange: (value: string) => void;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
const TabSwitchContext = React.createContext<{
|
|
||||||
switchTab: (value: string) => void;
|
|
||||||
}>({
|
|
||||||
switchTab: () => {},
|
|
||||||
});
|
|
||||||
|
|
||||||
export const useTabSwitchContext = () => {
|
|
||||||
return React.useContext(TabSwitchContext);
|
|
||||||
};
|
|
||||||
|
|
||||||
function VerticalTabs({ value, onChange, children }: Props) {
|
function VerticalTabs({ value, onChange, children }: Props) {
|
||||||
return (
|
return (
|
||||||
<TabContext.Provider value={value || ""}>
|
<TabContext.Provider value={value || ""}>
|
||||||
<TabSwitchContext.Provider
|
<Tabs.Root
|
||||||
value={{
|
orientation={"vertical"}
|
||||||
switchTab: (value: string) => {
|
className={"block lg:flex bg-nb-gray"}
|
||||||
onChange(value);
|
value={value}
|
||||||
},
|
onValueChange={(value) => onChange(value)}
|
||||||
}}
|
|
||||||
>
|
>
|
||||||
<Tabs.Root
|
{children}
|
||||||
orientation={"vertical"}
|
</Tabs.Root>
|
||||||
className={"block lg:flex bg-nb-gray"}
|
|
||||||
value={value}
|
|
||||||
onValueChange={(value) => onChange(value)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Tabs.Root>
|
|
||||||
</TabSwitchContext.Provider>
|
|
||||||
</TabContext.Provider>
|
</TabContext.Provider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Loaded 20 of 93 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user