Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export function FieldHelp({ text }: { text: string }) {
<TooltipTrigger asChild>
<button
type="button"
className="inline-flex h-4 w-4 items-center justify-center text-muted-foreground transition-colors hover:text-foreground"
className="cursor-pointer inline-flex h-4 w-4 items-center justify-center text-muted-foreground transition-colors hover:text-foreground"
aria-label="Show field help"
>
<CircleHelp className="h-4 w-4" />
Expand Down
Original file line number Diff line number Diff line change
@@ -1,19 +1,37 @@
'use client';

import { useRef } from 'react';
import { useTranslations } from 'next-intl';
import { UseFormReturn } from 'react-hook-form';
import { FileUp } from 'lucide-react';
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/registry/new-york-v4/ui/form';
import { Input } from '@/registry/new-york-v4/ui/input';
import { RadioGroup, RadioGroupItem } from '@/registry/new-york-v4/ui/radio-group';
import { Label } from '@/registry/new-york-v4/ui/label';
import { Textarea } from '@/registry/new-york-v4/ui/textarea';
import { Button } from '@/registry/new-york-v4/ui/button';
import { cn } from '@/lib/utils';

export default function SSHConnectionForm(props: { form: UseFormReturn<any> }) {
const { form } = props;
const sshEnabled = form.watch('ssh.enabled');
const authMethod = form.watch('ssh.authMethod');
const t = useTranslations('Connections.ConnectionContent');
const fileInputRef = useRef<HTMLInputElement>(null);

const handleFileSelect = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
const text = reader.result;
if (typeof text === 'string') {
form.setValue('ssh.privateKey', text, { shouldValidate: true });
}
};
reader.readAsText(file);
e.target.value = '';
};

return (
<div className={cn('space-y-4 rounded-lg bg-background/60 p-4', !sshEnabled && 'opacity-50 pointer-events-none')}>
Expand Down Expand Up @@ -84,12 +102,12 @@ export default function SSHConnectionForm(props: { form: UseFormReturn<any> }) {
<FormControl>
<RadioGroup className="flex flex-row gap-4" value={field.value} onValueChange={field.onChange}>
<div className="flex items-center space-x-2">
<RadioGroupItem value="password" id="ssh-auth-password" />
<Label htmlFor="ssh-auth-password">{t('Password')}</Label>
<RadioGroupItem value="password" id="ssh-auth-password" className="cursor-pointer" />
<Label htmlFor="ssh-auth-password" className="cursor-pointer">{t('Password')}</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="private_key" id="ssh-auth-key" />
<Label htmlFor="ssh-auth-key">{t('Private Key')}</Label>
<RadioGroupItem value="private_key" id="ssh-auth-key" className="cursor-pointer" />
<Label htmlFor="ssh-auth-key" className="cursor-pointer">{t('Private Key')}</Label>
</div>
</RadioGroup>
</FormControl>
Expand Down Expand Up @@ -119,7 +137,26 @@ export default function SSHConnectionForm(props: { form: UseFormReturn<any> }) {
name="ssh.privateKey"
render={({ field }) => (
<FormItem className="w-full">
<FormLabel>{t('SSH Private Key')}</FormLabel>
<div className="flex items-center justify-between">
<FormLabel>{t('SSH Private Key')}</FormLabel>
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 gap-1.5 text-xs text-muted-foreground"
onClick={() => fileInputRef.current?.click()}
>
<FileUp className="h-3.5 w-3.5" />
{t('Select File')}
</Button>
<input
ref={fileInputRef}
type="file"
accept=".pem,.key,.pub,.ppk"
className="hidden"
onChange={handleFileSelect}
/>
</div>
<FormControl>
<Textarea rows={4} placeholder={t('SSH Private Key Placeholder')} {...field} />
</FormControl>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ function CatalogHeader({ catalogName, expanded, onToggle }: { catalogName: strin
<button
type="button"
onClick={onToggle}
className="rounded p-0.5 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
className="cursor-pointer rounded p-0.5 hover:bg-sidebar-accent hover:text-sidebar-accent-foreground"
aria-label={`${expanded ? t('Collapse') : t('Expand')} ${catalogName}`}
>
{expanded ? <ChevronDown className="h-3.5 w-3.5" /> : <ChevronRight className="h-3.5 w-3.5" />}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { useTranslations } from 'next-intl';

import { Input } from '@/registry/new-york-v4/ui/input';
import { ScrollArea } from '@/registry/new-york-v4/ui/scroll-area';
import { Skeleton } from '@/registry/new-york-v4/ui/skeleton';
import { useDatabases } from '@/hooks/use-databases';
import type { ResponseObject } from '@/types';
import { getSidebarConfig } from '@/app/(app)/[organization]/components/sql-console-sidebar/sidebar-config';
Expand Down Expand Up @@ -113,7 +114,7 @@ export function ExplorerSidebar({
const defaultSchemaName = sidebarConfig.defaultSchemaName ?? 'public';
const showCatalog = false; // For now, we are hiding the catalog level as it's not commonly used and adds extra complexity to the UI. We can revisit this decision in the future if needed.

const { databases } = useDatabases();
const { databases, loading: databasesLoading } = useDatabases();

const [expandedCatalog, setExpandedCatalog] = useState(false);
const [expandedDatabases, setExpandedDatabases] = useState<Set<string>>(new Set());
Expand Down Expand Up @@ -487,6 +488,22 @@ export function ExplorerSidebar({

<ScrollArea className="mt-1 min-h-0 flex-1 w-[calc(100%+0.75rem)] -mr-3 space-y-2">
<div className="pr-3">
{databasesLoading && databaseEntries.length === 0 ? (
<div className="space-y-3 px-2">
{Array.from({ length: 3 }).map((_, i) => (
<div key={i} className="space-y-2">
<div className="flex items-center gap-2">
<Skeleton className="h-3.5 w-3.5 rounded" />
<Skeleton className="h-4 w-24" />
</div>
<div className="ml-6 space-y-1.5">
<Skeleton className="h-3 w-20" />
<Skeleton className="h-3 w-28" />
</div>
</div>
))}
</div>
) : (
<ExplorerSidebarTree
catalogName={catalogName}
groupKeys={supportedGroupKeys}
Expand Down Expand Up @@ -536,6 +553,7 @@ export function ExplorerSidebar({
filterEntries={filterEntries}
getSchemaObjects={getSchemaObjects}
/>
)}
</div>
</ScrollArea>
</div>
Expand Down
7 changes: 7 additions & 0 deletions apps/web/hooks/use-databases.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export function useDatabases() {
setDatabasesState({
connectionId: null,
items: [],
loading: false,
});
return;
}
Expand All @@ -33,6 +34,7 @@ export function useDatabases() {
setDatabasesState({
connectionId,
items: [],
loading: true,
});
}

Expand All @@ -52,6 +54,7 @@ export function useDatabases() {
}

const request = (async () => {
setDatabasesState(prev => ({ ...prev, loading: true }));
const response = await authFetch(`/api/connection/${requestedConnectionId}/databases`, {
method: 'GET',
headers: {
Expand All @@ -68,8 +71,11 @@ export function useDatabases() {
return {
connectionId: requestedConnectionId,
items: res.data ?? [],
loading: false,
};
});
} else {
setDatabasesState(prev => ({ ...prev, loading: false }));
}
})();

Expand All @@ -83,6 +89,7 @@ export function useDatabases() {

return {
databases: databasesState.connectionId === connectionId ? databasesState.items : [],
loading: databasesState.loading,
refresh,
};
}
1 change: 1 addition & 0 deletions apps/web/public/locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
"Private Key Passphrase (optional)": "Private key passphrase (optional)",
"SSH Private Key": "SSH private key",
"SSH Private Key Placeholder": "Paste your PEM-formatted private key",
"Select File": "Select file",
"Use SSL": "Use SSL",
"Please upload": "Please upload your private key file",
"Please enter your password": "Please enter your password"
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/locales/es.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
"Private Key Passphrase (optional)": "Frase de paso de la clave privada (opcional)",
"SSH Private Key": "Clave privada SSH",
"SSH Private Key Placeholder": "Pega tu clave privada en formato PEM",
"Select File": "Seleccionar archivo",
"Use SSL": "Usar SSL",
"Please upload": "Sube tu archivo de clave privada",
"Please enter your password": "Introduce tu contraseña"
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/locales/ja.json
Original file line number Diff line number Diff line change
Expand Up @@ -303,6 +303,7 @@
"Private Key Passphrase (optional)": "秘密鍵のパスフレーズ(任意)",
"SSH Private Key": "SSH 秘密鍵",
"SSH Private Key Placeholder": "PEM 形式の秘密鍵を貼り付けてください",
"Select File": "ファイルを選択",
"Use SSL": "SSL を使用",
"Please upload": "秘密鍵ファイルをアップロードしてください",
"Please enter your password": "パスワードを入力してください"
Expand Down
1 change: 1 addition & 0 deletions apps/web/public/locales/zh.json
Original file line number Diff line number Diff line change
Expand Up @@ -305,6 +305,7 @@
"Private Key Passphrase (optional)": "私钥密码(可选)",
"SSH Private Key": "SSH 私钥",
"SSH Private Key Placeholder": "粘贴 PEM 格式私钥内容",
"Select File": "选择文件",
"Use SSL": "使用 SSL",
"Please upload": "请上传你的私钥文件",
"Please enter your password": "请输入你的密码"
Expand Down
2 changes: 2 additions & 0 deletions apps/web/shared/stores/app.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const currentConnectionAtom = atomWithStorage<ConnectionListItem | null>(
export type DatabasesState = {
connectionId: string | null;
items: { label: string; value: string }[];
loading: boolean;
};

export type TablesState = {
Expand All @@ -26,6 +27,7 @@ export type TablesState = {
export const databasesAtom = atom<DatabasesState>({
connectionId: null,
items: [],
loading: false,
});
export const tablesAtom = atom<TablesState>({
connectionId: null,
Expand Down
Loading