Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

website: dynamically render index.mdx page for integrations #12897

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file removed website/integrations/apps-logo.png
Binary file not shown.
16 changes: 2 additions & 14 deletions website/integrations/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,6 @@
title: Integrations overview
---

There are two main types of integrations with authentik: **Applications** and **Sources**.
import IntegrationsIndex from "@site/src/components/Integrations/IntegrationsIndex";

## Applications

authentik integrates with many applications. For a full list, and to learn more about adding documentation for a new application, refer to the [Applications](../integrations/services/index.mdx) documentation.

![](./apps-logo.png)

## Sources

In addition to applications, authentik also integrates with external sources, including federated directories like Active Directory and through protocols such as LDAP, OAuth, SAML, and SCIM sources. Sources are a way for authentik to use external credentials for authentication and verification. Sources in authentik can also be used for social logins, using external providers such as Facebook, Twitter, etc.

To learn more, refer to the [Sources](https://docs.goauthentik.io/docs/users-sources/sources/index) documentation.

![](./sources-logo.png)
<IntegrationsIndex />
Binary file removed website/integrations/sources-logo.png
Binary file not shown.
138 changes: 138 additions & 0 deletions website/src/components/Integrations/IntegrationsIndex.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
import React, { useMemo } from "react";
import { useDocsSidebar } from "@docusaurus/plugin-content-docs/client";
import type {
PropSidebar,
PropSidebarItem,
PropSidebarItemLink,
PropSidebarItemCategory,
} from "@docusaurus/plugin-content-docs";
import clsx from "clsx";
import DocCard from "@theme/DocCard";

const IGNORED_URLS = new Set(["/integrations/", "/integrations/services/"]);
const CARD_CLASSES = "col col--4 margin-bottom--lg";

interface CategoryGroup {
category: string;
items: PropSidebarItemLink[];
}

/**
* Type guard to determine if a sidebar item is a category
*/
const isCategory = (item: PropSidebarItem): item is PropSidebarItemCategory => {
return item.type === "category";
};

/**
* Type guard to determine if a sidebar item is a link
*/
const isLink = (item: PropSidebarItem): item is PropSidebarItemLink => {
return item.type === "link";
};

/**
* Custom hook to fetch and process all integration items from the sidebar
* Groups items by their parent category
*/
function useGroupedIntegrations(): CategoryGroup[] {
const sidebar = useDocsSidebar();

return useMemo(() => {
if (!sidebar) {
console.warn("No sidebar found for integrations");
return [];
}

/**
* Recursively processes sidebar items to maintain category structure
*/
const processItems = (
items: PropSidebarItem[],
parentCategory?: string,
): CategoryGroup[] => {
const groups: CategoryGroup[] = [];
const uncategorizedItems: PropSidebarItemLink[] = [];

items.forEach((item) => {
if (isCategory(item)) {
// Process category items
const categoryGroups = processItems(item.items, item.label);
groups.push(...categoryGroups);
} else if (isLink(item) && !IGNORED_URLS.has(item.href)) {
// Collect links
if (parentCategory) {
const existingGroup = groups.find(
(g) => g.category === parentCategory,
);
if (existingGroup) {
existingGroup.items.push(item);
} else {
groups.push({
category: parentCategory,
items: [item],
});
}
} else {
uncategorizedItems.push(item);
}
}
});

// Add uncategorized items if any exist
if (uncategorizedItems.length > 0) {
groups.push({ category: "Other", items: uncategorizedItems });
}

return groups;
};

const sidebarItems = Array.isArray(sidebar.items) ? sidebar.items : [];
return processItems(sidebarItems);
}, [sidebar]);
}

/**
* Component to render a category section
*/
function CategorySection({ category, items }: CategoryGroup) {
return (
<div className="margin-bottom--xl">
<h2 className="margin-bottom--lg">{category}</h2>
<div className="row">
{items.map((item) => (
<article key={item.href} className={CARD_CLASSES} itemScope>
<DocCard item={item} />
</article>
))}
</div>
</div>
);
}

/**
* Main component that renders the categorized integrations index page
*/
function IntegrationsIndex() {
const groupedIntegrations = useGroupedIntegrations();

return (
<section className="padding-horiz--md">
{groupedIntegrations.length > 0 ? (
groupedIntegrations.map((group) => (
<CategorySection
key={group.category}
category={group.category}
items={group.items}
/>
))
) : (
<div className="col text--center padding-vert--xl">
<p>No integrations found.</p>
</div>
)}
</section>
);
}

export default IntegrationsIndex;
Loading