Skip to content

Commit e83260a

Browse files
authored
feat: add interactive Ory Project chart (#2225)
1 parent 98d2253 commit e83260a

File tree

5 files changed

+315
-1
lines changed

5 files changed

+315
-1
lines changed

docs/ecosystem/projects.mdx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,7 +86,11 @@ demands. If you're delivering enterprise-grade SaaS and need SSO that just works
8686

8787
## All of Ory Open Source
8888

89-
![Full Ory Ecosystem](./_static/projects/1.png)
89+
```mdx-code-block
90+
import { ProjectOverviewGraph } from "@site/src/pages/_assets/project-overview-graph"
91+
92+
<ProjectOverviewGraph />
93+
```
9094

9195
If you were to use the full Ory Ecosystem, it would probably look something like this. Keep in mind that any component shown here
9296
can be replaced or removed, depending on your use case.

package-lock.json

Lines changed: 10 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"@docusaurus/preset-classic": "3.8.0",
3939
"@docusaurus/theme-classic": "3.8.0",
4040
"@docusaurus/theme-search-algolia": "3.8.0",
41+
"@iconify-json/tabler": "1.2.19",
4142
"@octokit/rest": "20.0.2",
4243
"@ory/client-fetch": "1.15.16",
4344
"@rjsf/bootstrap-4": "5.24.1",
Lines changed: 293 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,293 @@
1+
import Mermaid from "@site/src/theme/Mermaid"
2+
import { useState } from "react"
3+
4+
// Types
5+
type ComponentKey =
6+
| "kratos"
7+
| "hydra"
8+
| "keto"
9+
| "polis"
10+
| "oathkeeper"
11+
| "elements"
12+
13+
interface ComponentConfig {
14+
key: ComponentKey
15+
label: string
16+
logoUrl: string
17+
description: string
18+
}
19+
20+
interface ChartNode {
21+
id: string
22+
config: string
23+
condition?: (components: ComponentKey[]) => boolean
24+
}
25+
26+
interface ChartConnection {
27+
from: string
28+
to: string
29+
label?: string
30+
condition?: (components: ComponentKey[]) => boolean
31+
}
32+
33+
// Configuration
34+
const COMPONENT_CONFIGS: ComponentConfig[] = [
35+
{
36+
key: "kratos",
37+
label: "Identity Management",
38+
logoUrl:
39+
"https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-kratos.svg",
40+
description: "Identity Management",
41+
},
42+
{
43+
key: "hydra",
44+
label: "OAuth2 & OpenID Connect Server",
45+
logoUrl:
46+
"https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-hydra.svg",
47+
description: "OAuth2 & OpenID Connect Server",
48+
},
49+
{
50+
key: "keto",
51+
label: "Permissions",
52+
logoUrl:
53+
"https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-keto.svg",
54+
description: "Permissions",
55+
},
56+
{
57+
key: "polis",
58+
label: "Enterprise SSO bridge / SAML support",
59+
logoUrl:
60+
"https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-polis.svg",
61+
description: "Enterprise SSO bridge",
62+
},
63+
{
64+
key: "oathkeeper",
65+
label: "Identity and Access Proxy IAP",
66+
logoUrl:
67+
"https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-oathkeeper.svg",
68+
description: "Identity and Access Proxy IAP",
69+
},
70+
{
71+
key: "elements",
72+
label: "Pre-built UI",
73+
logoUrl:
74+
"https://raw.githubusercontent.com/ory/meta/master/static/logos/logo-elements.svg",
75+
description: "Ory Elements",
76+
},
77+
]
78+
79+
const COMPONENT_KEYS: ComponentKey[] = COMPONENT_CONFIGS.map(
80+
(config) => config.key,
81+
)
82+
83+
const LABELS: Record<ComponentKey, string> = Object.fromEntries(
84+
COMPONENT_CONFIGS.map((config) => [config.key, config.label]),
85+
) as Record<ComponentKey, string>
86+
87+
// Chart configuration
88+
const CHART_NODES: ChartNode[] = [
89+
{ id: "User", config: '@{ icon: "tabler:user" }' },
90+
{ id: "Devices", config: '@{ icon: "tabler:devices"}' },
91+
{
92+
id: "Kratos",
93+
config:
94+
'@{ img: "https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-kratos.svg", pos: "b", w: 120, constraint: "on", label: "Identity Management" }',
95+
},
96+
{
97+
id: "Hydra",
98+
config:
99+
'@{ img: "https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-hydra.svg", pos: "b", w: 120, constraint: "on", label: "OAuth2 & OpenID Connect Server" }',
100+
condition: (components) => components.includes("hydra"),
101+
},
102+
{
103+
id: "Keto",
104+
config:
105+
'@{ img: "https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-keto.svg", pos: "b", w: 120, constraint: "on", label: "Permissions" }',
106+
condition: (components) => components.includes("keto"),
107+
},
108+
{
109+
id: "Polis",
110+
config:
111+
'@{ img: "https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-polis.svg", pos: "b", w: 120, constraint: "on", label: "Enterprise SSO bridge" }',
112+
condition: (components) => components.includes("polis"),
113+
},
114+
{
115+
id: "Oathkeeper",
116+
config:
117+
'@{ img: "https://raw.githubusercontent.com/ory/meta/refs/heads/master/static/logos/logo-oathkeeper.svg", pos: "b", w: 120, constraint: "on", label: "Identity and Access Proxy IAP" }',
118+
condition: (components) => components.includes("oathkeeper"),
119+
},
120+
{
121+
id: "API",
122+
config:
123+
'@{ icon: "tabler:code", label: "API Endpoint 1<br/> API Endpoint 2" }',
124+
},
125+
{
126+
id: "Elements",
127+
config:
128+
'@{ img: "https://raw.githubusercontent.com/ory/meta/master/static/logos/logo-elements.svg", pos: "b", w: 120, constraint: "on", label: "Ory Elements" }',
129+
condition: (components) => components.includes("elements"),
130+
},
131+
]
132+
133+
const CHART_CONNECTIONS: ChartConnection[] = [
134+
{ from: "User", to: "Devices" },
135+
{
136+
from: "Devices",
137+
to: "Oathkeeper",
138+
condition: (components) => components.includes("oathkeeper"),
139+
},
140+
{
141+
from: "Oathkeeper",
142+
to: "API",
143+
label: "protects",
144+
condition: (components) => components.includes("oathkeeper"),
145+
},
146+
{
147+
from: "Oathkeeper",
148+
to: "Hydra",
149+
label: "authenticates credentials with",
150+
condition: (components) =>
151+
components.includes("hydra") && components.includes("oathkeeper"),
152+
},
153+
{
154+
from: "User",
155+
to: "Kratos",
156+
label: "Registers, log in, <br/> manages profile via API",
157+
},
158+
{
159+
from: "User",
160+
to: "Elements",
161+
label: "Registers, log in, <br/> manages profile via prebuilt UI",
162+
condition: (components) => components.includes("elements"),
163+
},
164+
{
165+
from: "Elements",
166+
to: "Kratos",
167+
condition: (components) => components.includes("elements"),
168+
},
169+
{
170+
from: "Elements",
171+
to: "Hydra",
172+
condition: (components) =>
173+
components.includes("elements") && components.includes("hydra"),
174+
},
175+
{
176+
from: "Oathkeeper",
177+
to: "Kratos",
178+
label: "checks session with",
179+
condition: (components) => components.includes("oathkeeper"),
180+
},
181+
{
182+
from: "Oathkeeper",
183+
to: "Keto",
184+
label: "checks permissions with",
185+
condition: (components) =>
186+
components.includes("keto") && components.includes("oathkeeper"),
187+
},
188+
{
189+
from: "yourCode",
190+
to: "Keto",
191+
label: "Checks permissions with",
192+
condition: (components) =>
193+
components.includes("keto") && !components.includes("oathkeeper"),
194+
},
195+
{
196+
from: "Devices",
197+
to: "yourCode",
198+
condition: (components) => !components.includes("oathkeeper"),
199+
},
200+
{
201+
from: "Kratos",
202+
to: "Polis",
203+
label: "OIDC",
204+
condition: (components) => components.includes("polis"),
205+
},
206+
{
207+
from: "yourCode",
208+
to: "Hydra",
209+
label: "OAuth2",
210+
condition: (components) =>
211+
!components.includes("oathkeeper") &&
212+
!components.includes("elements") &&
213+
components.includes("hydra"),
214+
},
215+
{
216+
from: "yourCode",
217+
to: "Kratos",
218+
label: "Checks session with",
219+
condition: (components) => !components.includes("oathkeeper"),
220+
},
221+
]
222+
223+
// Helper functions
224+
function generateChartNodes(components: ComponentKey[]): string {
225+
return CHART_NODES.filter(
226+
(node) => !node.condition || node.condition(components),
227+
)
228+
.map((node) => `${node.id}${node.config}`)
229+
.join("\n")
230+
}
231+
232+
function generateChartConnections(components: ComponentKey[]): string {
233+
return CHART_CONNECTIONS.filter(
234+
(connection) => !connection.condition || connection.condition(components),
235+
)
236+
.map((connection) => {
237+
const label = connection.label ? `|${connection.label}|` : ""
238+
return `${connection.from} -->${label} ${connection.to}`
239+
})
240+
.join("\n")
241+
}
242+
243+
function generateMermaidChart(components: ComponentKey[]): string {
244+
const nodes = generateChartNodes(components)
245+
const connections = generateChartConnections(components)
246+
247+
return `graph LR
248+
249+
${nodes}
250+
251+
subgraph yourCode[Your Code]
252+
API@{ icon: "tabler:code", label: "API Endpoint 1<br/> API Endpoint 2" }
253+
${components.includes("elements") ? `Elements@{ img: "https://raw.githubusercontent.com/ory/meta/master/static/logos/logo-elements.svg", pos: "b", w: 120, constraint: "on", label: "Ory Elements" }` : ""}
254+
end
255+
256+
${connections}
257+
`
258+
}
259+
260+
// Component
261+
export function ProjectOverviewGraph() {
262+
const [components, setComponents] = useState<ComponentKey[]>(COMPONENT_KEYS)
263+
264+
const toggleComponent = (component: ComponentKey) => () => {
265+
setComponents((prev) =>
266+
prev.includes(component)
267+
? prev.filter((c) => c !== component)
268+
: [...prev, component],
269+
)
270+
}
271+
272+
const chart = generateMermaidChart(components)
273+
274+
return (
275+
<div>
276+
I need:
277+
<br />
278+
{COMPONENT_KEYS.map((component) => (
279+
<div key={component}>
280+
<input
281+
type="checkbox"
282+
id={component}
283+
checked={components.includes(component)}
284+
onChange={toggleComponent(component)}
285+
disabled={component === "kratos"}
286+
/>
287+
<label htmlFor={component}>{LABELS[component]}</label>
288+
</div>
289+
))}
290+
<Mermaid chart={chart} />
291+
</div>
292+
)
293+
}

src/theme/Mermaid.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,12 @@ mermaid.initialize({
3838
useMaxWidth: true,
3939
},
4040
})
41+
mermaid.registerIconPacks([
42+
{
43+
name: "tabler",
44+
loader: () => import("@iconify-json/tabler").then((module) => module.icons),
45+
},
46+
])
4147

4248
const Mermaid = ({ chart }) => {
4349
const [zoomed, setZoomed] = useState(false)

0 commit comments

Comments
 (0)