Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion content/docs/guides/reverse-proxy.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ TODO

To use Traefik as a reverse proxy for OrcaCD, you can add the following labels to your Docker Compose configuration for the OrcaCD Hub service:

```yml
```yml lineNumbers
services:
hub:
image: ghcr.io/orcacd/hub:latest
Expand Down
2 changes: 1 addition & 1 deletion content/docs/helping-out/documentation.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ All markdown pages are under `/content/docs`. The file `meta.json` controls the

3. Add frontmatter:

```yaml
```yaml lineNumbers
---
title: Installation
description: Get OrcaCD running quickly with Docker installation
Expand Down
17 changes: 17 additions & 0 deletions content/docs/troubleshooting/account-recovery.mdx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
---
title: Account Recovery
description: Steps to recover your account if you have lost access
---

There are two ways to reset a password for a user:

1. UI: An admin can reset the password for a user in the admin panel under the "Users" tab by clicking on the three dots next to the user's name and selecting "Edit".
2. Terminal: You can reset a password for a user by running `hub reset-password <user id or email>`. To execute this script with Docker you have to run the following command:

```bash
docker compose exec hub /app/hub reset-password <user id or email>
```

<Callout>The user with the reset password has to change the password on the next login.</Callout>

<AccountRecoveryTerminal />
26 changes: 26 additions & 0 deletions src/components/account-recovery-terminal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AnimatedSpan, Terminal, TypingAnimation } from "./terminal";

export function AccountRecoveryTerminal() {
return (
<Terminal>
<TypingAnimation delay={0} duration={30}>
$ docker compose exec hub /app/hub reset-password 019dc111-5220-77bc-9729-2335f88fa658
</TypingAnimation>
<AnimatedSpan delay={2500} className="text-fd-primary">
{`
╭────────────────────────────────────────────────────────────────────╮
│ │
│ Password Reset Successful │
│ │
│ User ID: 019dc111-5220-77bc-9729-2335f88fa658 │
│ Email: test@orcacd.dev │
│ New temporary password: w&BU6G,WM#!MX9M4eq │
│ │
│ Important: The user must change this password on the next login. │
│ │
╰────────────────────────────────────────────────────────────────────╯
`}
</AnimatedSpan>
</Terminal>
);
}
111 changes: 111 additions & 0 deletions src/components/terminal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
"use client";

import { cn } from "@/lib/utils";
import { type MotionProps, motion } from "motion/react";
import { useEffect, useRef, useState } from "react";

interface AnimatedSpanProps extends MotionProps {
children: React.ReactNode;
delay?: number;
className?: string;
}

export const AnimatedSpan = ({ children, delay = 0, className, ...props }: AnimatedSpanProps) => (
<motion.div
animate={{ opacity: 1, y: 0 }}
className={cn("grid text-sm font-normal tracking-tight", className)}
initial={{ opacity: 0, y: -5 }}
transition={{ duration: 0.3, delay: delay / 1000 }}
{...(props as any)}
>
{children}
</motion.div>
);

interface TypingAnimationProps extends MotionProps {
children: string;
className?: string;
duration?: number;
delay?: number;
as?: React.ElementType;
}

export const TypingAnimation = ({
children,
className,
duration = 60,
delay = 0,
as: Component = "span",
...props
}: TypingAnimationProps) => {
if (typeof children !== "string") {
throw new Error("TypingAnimation: children must be a string. Received:");
}

const MotionComponent = motion.create(Component, {
forwardMotionProps: true,
});

const [displayedText, setDisplayedText] = useState<string>("");
const [started, setStarted] = useState(false);
const elementRef = useRef<HTMLElement | null>(null);

useEffect(() => {
const startTimeout = setTimeout(() => {
setStarted(true);
}, delay);
return () => clearTimeout(startTimeout);
}, [delay]);

useEffect(() => {
if (!started) {
return;
}

let i = 0;
const typingEffect = setInterval(() => {
if (i < children.length) {
setDisplayedText(children.substring(0, i + 1));
i++;
} else {
clearInterval(typingEffect);
}
}, duration);

return () => {
clearInterval(typingEffect);
};
}, [children, duration, started]);

return (
<MotionComponent
className={cn("text-sm font-normal tracking-tight", className)}
ref={elementRef}
{...(props as any)}
>
{displayedText}
</MotionComponent>
);
};

interface TerminalProps {
children: React.ReactNode;
className?: string;
}

export const Terminal = ({ children, className }: TerminalProps) => {
return (
<div className={cn("w-full rounded-xl border border-border bg-background", className)}>
<div className="flex flex-col gap-y-2 border-b border-border p-4 ">
<div className="flex flex-row gap-x-2">
<div className="h-2 w-2 rounded-full bg-red-500" />
<div className="h-2 w-2 rounded-full bg-yellow-500" />
<div className="h-2 w-2 rounded-full bg-green-500" />
</div>
</div>
<pre>
<code className="grid gap-y-1 overflow-auto">{children}</code>
</pre>
</div>
);
};
6 changes: 6 additions & 0 deletions src/lib/utils.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";

export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
2 changes: 2 additions & 0 deletions src/routes/docs/$.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import defaultMdxComponents from "fumadocs-ui/mdx";
import { baseOptions } from "@/lib/layout.shared";
import { getPageMarkdownUrl, source } from "@/lib/source";
import { Suspense } from "react";
import { AccountRecoveryTerminal } from "@/components/account-recovery-terminal";

export const Route = createFileRoute("/docs/$")({
component: Page,
Expand Down Expand Up @@ -77,6 +78,7 @@ const clientLoader = browserCollections.docs.createClientLoader({
<MDX
components={{
...defaultMdxComponents,
AccountRecoveryTerminal,
}}
/>
</DocsBody>
Expand Down