mirror of
https://github.com/fosrl/pangolin.git
synced 2026-05-04 11:34:19 +00:00
Merge branch 'dev' into alerting-rules
This commit is contained in:
@@ -8,23 +8,25 @@ import { useEnvContext } from "@app/hooks/useEnvContext";
|
||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { build } from "@server/build";
|
||||
import type { Env } from "@app/lib/types/env";
|
||||
|
||||
export function isIdpGlobalModeBannerVisible(env: Env): boolean {
|
||||
if (build === "saas") {
|
||||
return false;
|
||||
}
|
||||
return env.app.identityProviderMode === undefined;
|
||||
}
|
||||
|
||||
export function IdpGlobalModeBanner() {
|
||||
const t = useTranslations();
|
||||
const { env } = useEnvContext();
|
||||
const { isPaidUser, hasEnterpriseLicense } = usePaidStatus();
|
||||
|
||||
const identityProviderModeUndefined =
|
||||
env.app.identityProviderMode === undefined;
|
||||
const paidUserForOrgOidc = isPaidUser(tierMatrix.orgOidc);
|
||||
const enterpriseUnlicensed =
|
||||
build === "enterprise" && !hasEnterpriseLicense;
|
||||
|
||||
if (build === "saas") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!identityProviderModeUndefined) {
|
||||
if (!isIdpGlobalModeBannerVisible(env)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1542,7 +1542,7 @@ export function InternalResourceForm({
|
||||
</span>
|
||||
)
|
||||
)}
|
||||
<span className="pl-1">
|
||||
<span className="pl-1 font-normal">
|
||||
{t(
|
||||
"accessClientSelect"
|
||||
)}
|
||||
|
||||
@@ -49,7 +49,7 @@ export function LayoutHeader({ showTopBar }: LayoutHeaderProps) {
|
||||
|
||||
return (
|
||||
<div className="absolute top-0 left-0 right-0 z-50 hidden md:block">
|
||||
<div className="absolute inset-0 bg-background/83 backdrop-blur-sm" />
|
||||
<div className="absolute inset-0 bg-background backdrop-blur-sm" />
|
||||
<div className="relative z-10 px-6 py-2">
|
||||
<div className="container mx-auto max-w-12xl">
|
||||
<div className="h-16 flex items-center justify-between">
|
||||
|
||||
@@ -125,7 +125,7 @@ export function LayoutSidebar({
|
||||
return (
|
||||
<div
|
||||
className={cn(
|
||||
"hidden md:flex border-r bg-card flex-col h-full shrink-0 relative",
|
||||
"hidden md:flex border-r bg-sidebar flex-col h-full shrink-0 relative",
|
||||
isSidebarCollapsed ? "w-16" : "w-64"
|
||||
)}
|
||||
>
|
||||
@@ -154,7 +154,7 @@ export function LayoutSidebar({
|
||||
<Link
|
||||
href="/admin"
|
||||
className={cn(
|
||||
"flex items-center transition-colors text-muted-foreground hover:text-foreground text-sm w-full hover:bg-secondary/80 dark:hover:bg-secondary/50 rounded-md",
|
||||
"flex items-center transition-colors text-muted-foreground hover:text-foreground text-sm w-full hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50 rounded-md",
|
||||
isSidebarCollapsed
|
||||
? "px-2 py-2 justify-center"
|
||||
: "px-3 py-1.5"
|
||||
@@ -191,7 +191,7 @@ export function LayoutSidebar({
|
||||
/>
|
||||
</div>
|
||||
{/* Fade gradient at bottom to indicate scrollable content */}
|
||||
<div className="sticky bottom-0 left-0 right-0 h-8 pointer-events-none bg-gradient-to-t from-card to-transparent" />
|
||||
<div className="sticky bottom-0 left-0 right-0 h-8 pointer-events-none bg-gradient-to-t from-sidebar to-transparent" />
|
||||
</div>
|
||||
|
||||
{isSidebarCollapsed && (
|
||||
@@ -206,7 +206,7 @@ export function LayoutSidebar({
|
||||
setHasManualToggle(true);
|
||||
setSidebarStateCookie(false);
|
||||
}}
|
||||
className="rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 transition-colors"
|
||||
className="rounded-md p-2 text-muted-foreground hover:text-foreground hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50 transition-colors"
|
||||
aria-label={t("sidebarExpand")}
|
||||
>
|
||||
<PanelRightOpen className="h-4 w-4" />
|
||||
@@ -220,7 +220,12 @@ export function LayoutSidebar({
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className="pt-1 flex flex-col shrink-0 gap-2 w-full border-t border-border">
|
||||
<div
|
||||
className={cn(
|
||||
"pt-1 flex flex-col shrink-0 gap-2 w-full border-t border-border",
|
||||
isSidebarCollapsed && "pb-2"
|
||||
)}
|
||||
>
|
||||
{canShowProductUpdates ? (
|
||||
<div className="px-4">
|
||||
<ProductUpdates isCollapsed={isSidebarCollapsed} />
|
||||
|
||||
@@ -53,7 +53,7 @@ export default function LocaleSwitcherSelect({
|
||||
)}
|
||||
aria-label={label}
|
||||
>
|
||||
<Languages className="h-4 w-4" />
|
||||
<Languages className="text-muted-foreground h-4 w-4" />
|
||||
<span className="text-left flex-1">
|
||||
{selected?.label ?? label}
|
||||
</span>
|
||||
|
||||
@@ -12,13 +12,15 @@ interface DataTableProps<TData, TValue> {
|
||||
data: TData[];
|
||||
onAdd?: () => void;
|
||||
addActions?: DataTableAddAction[];
|
||||
addButtonDisabled?: boolean;
|
||||
}
|
||||
|
||||
export function IdpDataTable<TData, TValue>({
|
||||
columns,
|
||||
data,
|
||||
onAdd,
|
||||
addActions
|
||||
addActions,
|
||||
addButtonDisabled
|
||||
}: DataTableProps<TData, TValue>) {
|
||||
const t = useTranslations();
|
||||
|
||||
@@ -33,6 +35,7 @@ export function IdpDataTable<TData, TValue>({
|
||||
addButtonText={t("idpAdd")}
|
||||
onAdd={onAdd}
|
||||
addActions={addActions}
|
||||
addButtonDisabled={addButtonDisabled}
|
||||
enableColumnVisibility={true}
|
||||
stickyRightColumn="actions"
|
||||
/>
|
||||
|
||||
@@ -52,6 +52,7 @@ import type { ListUserAdminOrgIdpsResponse } from "@server/routers/orgIdp/types"
|
||||
import { cn } from "@app/lib/cn";
|
||||
import { usePaidStatus } from "@app/hooks/usePaidStatus";
|
||||
import { tierMatrix } from "@server/lib/billing/tierMatrix";
|
||||
import { isIdpGlobalModeBannerVisible } from "@app/components/IdpGlobalModeBanner";
|
||||
|
||||
export type IdpRow = {
|
||||
idpId: number;
|
||||
@@ -85,13 +86,15 @@ export default function IdpTable({ idps, orgId }: Props) {
|
||||
const [importSubmitting, setImportSubmitting] = useState(false);
|
||||
const [debouncedImportSearch] = useDebounce(importSearchQuery, 150);
|
||||
|
||||
const api = createApiClient(useEnvContext());
|
||||
const envContext = useEnvContext();
|
||||
const api = createApiClient(envContext);
|
||||
const { user } = useUserContext();
|
||||
const { isPaidUser } = usePaidStatus();
|
||||
const router = useRouter();
|
||||
const t = useTranslations();
|
||||
|
||||
const canImportOrgOidcIdp = isPaidUser(tierMatrix.orgOidc);
|
||||
const addIdpDisabled = isIdpGlobalModeBannerVisible(envContext.env);
|
||||
|
||||
const { data: adminIdpsRaw = [] } = useQuery({
|
||||
queryKey: ["admin-org-idps", user.userId],
|
||||
@@ -184,23 +187,6 @@ export default function IdpTable({ idps, orgId }: Props) {
|
||||
};
|
||||
|
||||
const columns: ExtendedColumnDef<IdpRow>[] = [
|
||||
{
|
||||
accessorKey: "idpId",
|
||||
friendlyName: "ID",
|
||||
header: ({ column }) => {
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
onClick={() =>
|
||||
column.toggleSorting(column.getIsSorted() === "asc")
|
||||
}
|
||||
>
|
||||
ID
|
||||
<ArrowUpDown className="ml-2 h-4 w-4" />
|
||||
</Button>
|
||||
);
|
||||
}
|
||||
},
|
||||
{
|
||||
accessorKey: "name",
|
||||
friendlyName: t("name"),
|
||||
@@ -427,6 +413,7 @@ export default function IdpTable({ idps, orgId }: Props) {
|
||||
<IdpDataTable
|
||||
columns={columns}
|
||||
data={idps}
|
||||
addButtonDisabled={addIdpDisabled}
|
||||
addActions={[
|
||||
{
|
||||
label: t("idpAddActionCreateNew"),
|
||||
|
||||
@@ -76,8 +76,8 @@ export function OrgSelector({
|
||||
className={cn(
|
||||
"cursor-pointer transition-colors",
|
||||
isCollapsed
|
||||
? "w-full h-16 flex items-center justify-center hover:bg-muted"
|
||||
: "w-full px-5 py-4 hover:bg-muted"
|
||||
? "w-full h-16 flex items-center justify-center hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50"
|
||||
: "w-full px-5 py-4 hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50"
|
||||
)}
|
||||
>
|
||||
{isCollapsed ? (
|
||||
@@ -172,7 +172,7 @@ export function OrgSelector({
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="w-full justify-start h-8 font-normal text-muted-foreground hover:text-foreground"
|
||||
className="w-full justify-start h-8 font-normal text-muted-foreground"
|
||||
onClick={() => {
|
||||
setOpen(false);
|
||||
router.push("/setup");
|
||||
|
||||
@@ -121,8 +121,8 @@ function CollapsibleNavItem({
|
||||
"flex items-center w-full rounded-md transition-colors",
|
||||
"px-3 py-1.5",
|
||||
isActive
|
||||
? "bg-secondary font-medium"
|
||||
: "text-muted-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 hover:text-foreground",
|
||||
? "bg-sidebar-accent font-medium"
|
||||
: "text-muted-foreground hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50 hover:text-foreground",
|
||||
isDisabled && "cursor-not-allowed opacity-60"
|
||||
)}
|
||||
disabled={isDisabled}
|
||||
@@ -256,8 +256,8 @@ function CollapsedNavItemWithPopover({
|
||||
className={cn(
|
||||
"flex items-center rounded-md transition-colors px-2 py-2 justify-center w-full",
|
||||
isActive || isChildActive
|
||||
? "bg-secondary font-medium"
|
||||
: "text-muted-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 hover:text-foreground",
|
||||
? "bg-sidebar-accent font-medium"
|
||||
: "text-muted-foreground hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50 hover:text-foreground",
|
||||
isDisabled &&
|
||||
"cursor-not-allowed opacity-60"
|
||||
)}
|
||||
@@ -308,8 +308,8 @@ function CollapsedNavItemWithPopover({
|
||||
className={cn(
|
||||
"flex items-center rounded-md transition-colors px-3 py-1.5 text-sm",
|
||||
childIsActive
|
||||
? "bg-secondary font-medium"
|
||||
: "text-muted-foreground hover:bg-secondary/50 hover:text-foreground",
|
||||
? "bg-sidebar-accent font-medium"
|
||||
: "text-muted-foreground hover:bg-sidebar-accent/50 hover:text-foreground",
|
||||
childIsDisabled &&
|
||||
"cursor-not-allowed opacity-60"
|
||||
)}
|
||||
@@ -450,8 +450,8 @@ export function SidebarNav({
|
||||
"flex items-center rounded-md transition-colors relative",
|
||||
isCollapsed ? "px-2 py-2 justify-center" : "px-3 py-1.5",
|
||||
isActive
|
||||
? "bg-secondary font-medium"
|
||||
: "text-muted-foreground hover:bg-secondary/80 dark:hover:bg-secondary/50 hover:text-foreground",
|
||||
? "bg-sidebar-accent font-medium"
|
||||
: "text-muted-foreground hover:bg-sidebar-accent/80 dark:hover:bg-sidebar-accent/50 hover:text-foreground",
|
||||
isDisabled && "cursor-not-allowed opacity-60"
|
||||
)}
|
||||
onClick={(e) => {
|
||||
|
||||
@@ -27,7 +27,7 @@ import {
|
||||
TableHeader,
|
||||
TableRow
|
||||
} from "@app/components/ui/table";
|
||||
import { Tabs, TabsList, TabsTrigger, TabsContent } from "@app/components/ui/tabs";
|
||||
import { HorizontalTabs } from "@app/components/HorizontalTabs";
|
||||
import { Loader2, RefreshCw } from "lucide-react";
|
||||
import moment from "moment";
|
||||
import { useUserContext } from "@app/hooks/useUserContext";
|
||||
@@ -58,7 +58,6 @@ export default function ViewDevicesDialog({
|
||||
|
||||
const [devices, setDevices] = useState<Device[]>([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [activeTab, setActiveTab] = useState<"available" | "archived">("available");
|
||||
|
||||
const fetchDevices = async () => {
|
||||
setLoading(true);
|
||||
@@ -177,34 +176,21 @@ export default function ViewDevicesDialog({
|
||||
<Loader2 className="h-6 w-6 animate-spin" />
|
||||
</div>
|
||||
) : (
|
||||
<Tabs
|
||||
value={activeTab}
|
||||
onValueChange={(value) =>
|
||||
setActiveTab(value as "available" | "archived")
|
||||
}
|
||||
className="w-full"
|
||||
<HorizontalTabs
|
||||
clientSide
|
||||
defaultTab={0}
|
||||
items={[
|
||||
{
|
||||
title: `${t("available") || "Available"} (${devices.filter((d) => !d.archived).length})`,
|
||||
href: "#available"
|
||||
},
|
||||
{
|
||||
title: `${t("archived") || "Archived"} (${devices.filter((d) => d.archived).length})`,
|
||||
href: "#archived"
|
||||
}
|
||||
]}
|
||||
>
|
||||
<TabsList className="grid w-full grid-cols-2">
|
||||
<TabsTrigger value="available">
|
||||
{t("available") || "Available"} (
|
||||
{
|
||||
devices.filter(
|
||||
(d) => !d.archived
|
||||
).length
|
||||
}
|
||||
)
|
||||
</TabsTrigger>
|
||||
<TabsTrigger value="archived">
|
||||
{t("archived") || "Archived"} (
|
||||
{
|
||||
devices.filter(
|
||||
(d) => d.archived
|
||||
).length
|
||||
}
|
||||
)
|
||||
</TabsTrigger>
|
||||
</TabsList>
|
||||
<TabsContent value="available" className="mt-4">
|
||||
<div>
|
||||
{devices.filter((d) => !d.archived)
|
||||
.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
@@ -271,8 +257,8 @@ export default function ViewDevicesDialog({
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
<TabsContent value="archived" className="mt-4">
|
||||
</div>
|
||||
<div>
|
||||
{devices.filter((d) => d.archived)
|
||||
.length === 0 ? (
|
||||
<div className="text-center py-8 text-muted-foreground">
|
||||
@@ -336,8 +322,8 @@ export default function ViewDevicesDialog({
|
||||
</Table>
|
||||
</div>
|
||||
)}
|
||||
</TabsContent>
|
||||
</Tabs>
|
||||
</div>
|
||||
</HorizontalTabs>
|
||||
)}
|
||||
</CredenzaBody>
|
||||
<CredenzaFooter>
|
||||
|
||||
Reference in New Issue
Block a user