Add login formatting

This commit is contained in:
Owen
2026-05-28 21:38:40 -07:00
parent c1ef5b4fbe
commit 9617eb2bd7
3 changed files with 323 additions and 189 deletions

View File

@@ -15,8 +15,14 @@ import type {
FileInfo FileInfo
} from "@devolutions/iron-remote-desktop-rdp/dist"; } from "@devolutions/iron-remote-desktop-rdp/dist";
import { GetBrowserTargetResponse } from "@server/routers/resource"; import { GetBrowserTargetResponse } from "@server/routers/resource";
import { Card, CardContent } from "@app/components/ui/card"; import {
import LoginCardHeader from "@app/components/LoginCardHeader"; Card,
CardContent,
CardHeader,
CardTitle,
CardDescription
} from "@app/components/ui/card";
import Link from "next/link";
declare module "react" { declare module "react" {
namespace JSX { namespace JSX {
@@ -309,51 +315,87 @@ export default function RdpClient({
if (error) { if (error) {
return ( return (
<Card className="w-full"> <div>
<LoginCardHeader subtitle="RDP" /> <div className="text-center mb-2">
<CardContent className="pt-6"> <span className="text-sm text-muted-foreground">
<p className="text-destructive text-sm">{error}</p> Powered by{" "}
</CardContent> <Link
</Card> href="https://pangolin.net/"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Pangolin
</Link>
</span>
</div>
<Card className="w-full">
<CardHeader>
<CardTitle>RDP</CardTitle>
</CardHeader>
<CardContent>
<p className="text-destructive text-sm">{error}</p>
</CardContent>
</Card>
</div>
); );
} }
return ( return (
<> <>
{showLogin && ( {showLogin && (
<Card className="w-full"> <div>
<LoginCardHeader subtitle="Connect via RDP" /> <div className="text-center mb-2">
<CardContent className="pt-6"> <span className="text-sm text-muted-foreground">
<div className="space-y-4"> Powered by{" "}
<Field label="Domain" id="domain"> <Link
<Input href="https://pangolin.net/"
id="domain" target="_blank"
value={form.domain} rel="noopener noreferrer"
onChange={(e) => className="underline"
update("domain", e.target.value) >
} Pangolin
/> </Link>
</Field> </span>
<Field label="Username" id="username"> </div>
<Input <Card className="w-full">
id="username" <CardHeader>
value={form.username} <CardTitle>Sign in to Remote Desktop</CardTitle>
onChange={(e) => <CardDescription>
update("username", e.target.value) Enter Windows credentials to access xxxx
} </CardDescription>
/> </CardHeader>
</Field> <CardContent>
<Field label="Password" id="password"> <div className="space-y-4">
<Input <Field label="Domain" id="domain">
id="password" <Input
type="password" id="domain"
value={form.password} value={form.domain}
onChange={(e) => onChange={(e) =>
update("password", e.target.value) update("domain", e.target.value)
} }
/> />
</Field> </Field>
{/* <Field label="Username" id="username">
<Input
id="username"
value={form.username}
onChange={(e) =>
update("username", e.target.value)
}
/>
</Field>
<Field label="Password" id="password">
<Input
id="password"
type="password"
value={form.password}
onChange={(e) =>
update("password", e.target.value)
}
/>
</Field>
{/*
<Field label="Pre Connection Blob (optional)" id="pcb"> <Field label="Pre Connection Blob (optional)" id="pcb">
<Input <Input
id="pcb" id="pcb"
@@ -362,7 +404,7 @@ export default function RdpClient({
/> />
</Field> */} </Field> */}
{/* <Field {/* <Field
label="KDC Proxy URL (optional)" label="KDC Proxy URL (optional)"
id="kdcProxyUrl" id="kdcProxyUrl"
> >
@@ -374,7 +416,7 @@ export default function RdpClient({
} }
/> />
</Field> */} </Field> */}
{/* <div className="flex items-center gap-2"> {/* <div className="flex items-center gap-2">
<Checkbox <Checkbox
id="enable_clipboard" id="enable_clipboard"
checked={form.enableClipboard} checked={form.enableClipboard}
@@ -386,17 +428,20 @@ export default function RdpClient({
Enable Clipboard Enable Clipboard
</Label> </Label>
</div> */} </div> */}
<Button <Button
onClick={startSession} onClick={startSession}
disabled={!moduleReady} disabled={!moduleReady}
loading={connecting} loading={connecting}
className="w-full" className="w-full"
> >
{moduleReady ? "Connect" : "Loading module..."} {moduleReady
</Button> ? "Connect"
</div> : "Loading module..."}
</CardContent> </Button>
</Card> </div>
</CardContent>
</Card>
</div>
)} )}
<div <div

View File

@@ -8,8 +8,14 @@ import { Label } from "@/components/ui/label";
import { Textarea } from "@/components/ui/textarea"; import { Textarea } from "@/components/ui/textarea";
import type { SignSshKeyResponse } from "@server/private/routers/ssh"; import type { SignSshKeyResponse } from "@server/private/routers/ssh";
import { GetBrowserTargetResponse } from "@server/routers/resource"; import { GetBrowserTargetResponse } from "@server/routers/resource";
import { Card, CardContent } from "@app/components/ui/card"; import {
import LoginCardHeader from "@app/components/LoginCardHeader"; Card,
CardContent,
CardHeader,
CardTitle,
CardDescription
} from "@app/components/ui/card";
import Link from "next/link";
type FormState = { type FormState = {
username: string; username: string;
@@ -300,126 +306,163 @@ export default function SshClient({
if (error) { if (error) {
return ( return (
<Card className="w-full"> <div>
<LoginCardHeader subtitle="SSH" /> <div className="text-center mb-2">
<CardContent className="pt-6"> <span className="text-sm text-muted-foreground">
<p className="text-destructive text-sm">{error}</p> Powered by{" "}
</CardContent> <Link
</Card> href="https://pangolin.net/"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Pangolin
</Link>
</span>
</div>
<Card className="w-full">
<CardHeader>
<CardTitle>SSH</CardTitle>
</CardHeader>
<CardContent>
<p className="text-destructive text-sm">{error}</p>
</CardContent>
</Card>
</div>
); );
} }
return ( return (
<> <>
{!connected && ( {!connected && (
<Card className="w-full"> <div>
<LoginCardHeader subtitle="Connect via SSH" /> <div className="text-center mb-2">
<CardContent className="pt-6"> <span className="text-sm text-muted-foreground">
<div className="space-y-4"> Powered by{" "}
<Field label="Username" id="username"> <Link
<Input href="https://pangolin.net/"
id="username" target="_blank"
value={form.username} rel="noopener noreferrer"
onChange={(e) => className="underline"
setForm({
...form,
username: e.target.value
})
}
placeholder="root"
/>
</Field>
<Field label="Password" id="password">
<Input
id="password"
type="password"
value={form.password}
onChange={(e) =>
setForm({
...form,
password: e.target.value
})
}
placeholder={
form.privateKey
? "Optional with key auth"
: ""
}
/>
</Field>
<Field
label="Private Key (optional)"
id="privateKey"
> >
<Textarea Pangolin
id="privateKey" </Link>
value={form.privateKey} </span>
onChange={(e) => </div>
setForm({ <Card className="w-full">
...form, <CardHeader>
privateKey: e.target.value <CardTitle>Sign in to SSH</CardTitle>
}) <CardDescription>
} Enter your credentials to access xxxx
placeholder="Paste your private key here (PEM format)…" </CardDescription>
rows={5} </CardHeader>
className="font-mono text-xs" <CardContent>
/> <div className="space-y-4">
<div className="mt-1.5 flex items-center gap-2"> <Field label="Username" id="username">
<Button <Input
type="button" id="username"
variant="outline" value={form.username}
size="sm" onChange={(e) =>
onClick={() => setForm({
fileInputRef.current?.click() ...form,
username: e.target.value
})
} }
> placeholder="root"
Upload key file />
</Button> </Field>
{form.privateKey && ( <Field label="Password" id="password">
<button <Input
id="password"
type="password"
value={form.password}
onChange={(e) =>
setForm({
...form,
password: e.target.value
})
}
placeholder={
form.privateKey
? "Optional with key auth"
: ""
}
/>
</Field>
<Field
label="Private Key (optional)"
id="privateKey"
>
<Textarea
id="privateKey"
value={form.privateKey}
onChange={(e) =>
setForm({
...form,
privateKey: e.target.value
})
}
placeholder="Paste your private key here (PEM format)…"
rows={5}
className="font-mono text-xs"
/>
<div className="mt-1.5 flex items-center gap-2">
<Button
type="button" type="button"
className="text-xs text-muted-foreground underline" variant="outline"
size="sm"
onClick={() => onClick={() =>
setForm((prev) => ({ fileInputRef.current?.click()
...prev,
privateKey: ""
}))
} }
> >
Clear Upload key file
</button> </Button>
)} {form.privateKey && (
</div> <button
<input type="button"
ref={fileInputRef} className="text-xs text-muted-foreground underline"
type="file" onClick={() =>
className="hidden" setForm((prev) => ({
accept=".pem,.key,.pub,*" ...prev,
onChange={handleKeyFile} privateKey: ""
/> }))
</Field> }
>
Clear
</button>
)}
</div>
<input
ref={fileInputRef}
type="file"
className="hidden"
accept=".pem,.key,.pub,*"
onChange={handleKeyFile}
/>
</Field>
{connectError && ( {connectError && (
<p className="text-destructive text-sm"> <p className="text-destructive text-sm">
{connectError} {connectError}
</p> </p>
)} )}
<Button <Button
onClick={() => connect()} onClick={() => connect()}
loading={connecting} loading={connecting}
disabled={ disabled={
!form.username || !form.username ||
(!form.password && !form.privateKey) (!form.password && !form.privateKey)
} }
className="w-full" className="w-full"
> >
{connecting ? "Connecting..." : "Connect"} {connecting ? "Connecting..." : "Connect"}
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div>
)} )}
{connected && ( {connected && (

View File

@@ -6,8 +6,14 @@ import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label"; import { Label } from "@/components/ui/label";
import { toast } from "@app/hooks/useToast"; import { toast } from "@app/hooks/useToast";
import { GetBrowserTargetResponse } from "@server/routers/resource"; import { GetBrowserTargetResponse } from "@server/routers/resource";
import { Card, CardContent } from "@app/components/ui/card"; import {
import LoginCardHeader from "@app/components/LoginCardHeader"; Card,
CardContent,
CardHeader,
CardTitle,
CardDescription
} from "@app/components/ui/card";
import Link from "next/link";
type FormState = { type FormState = {
password: string; password: string;
@@ -148,39 +154,79 @@ export default function VncClient({
if (error) { if (error) {
return ( return (
<Card className="w-full"> <div>
<LoginCardHeader subtitle="VNC" /> <div className="text-center mb-2">
<CardContent className="pt-6"> <span className="text-sm text-muted-foreground">
<p className="text-destructive text-sm">{error}</p> Powered by{" "}
</CardContent> <Link
</Card> href="https://pangolin.net/"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Pangolin
</Link>
</span>
</div>
<Card className="w-full">
<CardHeader>
<CardTitle>VNC</CardTitle>
</CardHeader>
<CardContent>
<p className="text-destructive text-sm">{error}</p>
</CardContent>
</Card>
</div>
); );
} }
return ( return (
<> <>
{!connected && ( {!connected && (
<Card className="w-full"> <div>
<LoginCardHeader subtitle="Connect via VNC" /> <div className="text-center mb-2">
<CardContent className="pt-6"> <span className="text-sm text-muted-foreground">
<div className="space-y-4"> Powered by{" "}
<Field label="Password (optional)" id="password"> <Link
<Input href="https://pangolin.net/"
target="_blank"
rel="noopener noreferrer"
className="underline"
>
Pangolin
</Link>
</span>
</div>
<Card className="w-full">
<CardHeader>
<CardTitle>VNC</CardTitle>
<CardDescription>
Enter your credentials to connect
</CardDescription>
</CardHeader>
<CardContent>
<div className="space-y-4">
<Field
label="Password (optional)"
id="password" id="password"
type="password" >
value={form.password} <Input
onChange={(e) => id="password"
update("password", e.target.value) type="password"
} value={form.password}
/> onChange={(e) =>
</Field> update("password", e.target.value)
}
/>
</Field>
<Button onClick={connect} className="w-full"> <Button onClick={connect} className="w-full">
Connect Connect
</Button> </Button>
</div> </div>
</CardContent> </CardContent>
</Card> </Card>
</div>
)} )}
<div <div