Skip to content

Commit

Permalink
feat: new design for design page
Browse files Browse the repository at this point in the history
  • Loading branch information
tea-artist committed Dec 29, 2024
1 parent 6dacd39 commit 64924e4
Show file tree
Hide file tree
Showing 30 changed files with 557 additions and 473 deletions.
2 changes: 1 addition & 1 deletion apps/nestjs-backend/src/features/base/base.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ export class BaseController {

@Permissions('base|db_connection')
@Post(':baseId/connection')
async createDbConnection(@Param('baseId') baseId: string): Promise<IDbConnectionVo> {
async createDbConnection(@Param('baseId') baseId: string): Promise<IDbConnectionVo | null> {
return await this.dbConnectionService.create(baseId);
}

Expand Down
13 changes: 7 additions & 6 deletions apps/nestjs-backend/src/features/base/db-connection.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
BadRequestException,
Injectable,
InternalServerErrorException,
NotFoundException,
Logger,
} from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import type { IDsn } from '@teable/core';
Expand All @@ -12,17 +12,16 @@ import type { IDbConnectionVo } from '@teable/openapi';
import { Knex } from 'knex';
import { nanoid } from 'nanoid';
import { InjectModel } from 'nest-knexjs';
import { ClsService } from 'nestjs-cls';
import { BaseConfig, type IBaseConfig } from '../../configs/base.config';
import { InjectDbProvider } from '../../db-provider/db.provider';
import { IDbProvider } from '../../db-provider/db.provider.interface';
import type { IClsStore } from '../../types/cls';

@Injectable()
export class DbConnectionService {
private readonly logger = new Logger(DbConnectionService.name);

constructor(
private readonly prismaService: PrismaService,
private readonly cls: ClsService<IClsStore>,
private readonly configService: ConfigService,
@InjectDbProvider() private readonly dbProvider: IDbProvider,
@InjectModel('CUSTOM_KNEX') private readonly knex: Knex,
Expand Down Expand Up @@ -118,7 +117,8 @@ export class DbConnectionService {
const readOnlyRole = `read_only_role_${baseId}`;
const publicDatabaseProxy = this.baseConfig.publicDatabaseProxy;
if (!publicDatabaseProxy) {
throw new NotFoundException('PUBLIC_DATABASE_PROXY is not found in env');
this.logger.error('PUBLIC_DATABASE_PROXY is not found in env');
return null;
}

const { hostname: dbHostProxy, port: dbPortProxy } = new URL(`https://${publicDatabaseProxy}`);
Expand Down Expand Up @@ -185,7 +185,8 @@ export class DbConnectionService {
const password = nanoid();
const publicDatabaseProxy = this.baseConfig.publicDatabaseProxy;
if (!publicDatabaseProxy) {
throw new NotFoundException('PUBLIC_DATABASE_PROXY is not found in env');
this.logger.error('PUBLIC_DATABASE_PROXY is not found in env');
return null;
}

const { hostname: dbHostProxy, port: dbPortProxy } = new URL(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { useQuery } from '@tanstack/react-query';
import { Gauge, Lock, Trash2 } from '@teable/icons';
import { Gauge, Lock, MoreHorizontal, Settings, Trash2 } from '@teable/icons';
import { getBaseUsage, getInstanceUsage } from '@teable/openapi';
import { useBase, useBasePermission } from '@teable/sdk/hooks';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
Tooltip,
TooltipContent,
TooltipProvider,
Expand Down Expand Up @@ -75,12 +79,6 @@ export const BaseSideBar = () => {
hidden: !basePermission?.['base|authority_matrix_config'],
disabled: !advancedPermissionsEnable,
},
{
href: `/base/${baseId}/trash`,
label: t('common:noun.trash'),
Icon: Trash2,
hidden: !basePermission?.['table|delete'],
},
].filter((item) => !item.hidden),
[advancedPermissionsEnable, automationEnable, baseId, basePermission, t]
);
Expand Down Expand Up @@ -138,6 +136,51 @@ export const BaseSideBar = () => {
</li>
);
})}
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button
variant="ghost"
size="xs"
className="my-[2px] w-full justify-start text-sm font-normal"
>
<MoreHorizontal className="size-4 shrink-0" />
<p className="truncate">{t('common:actions.more')}</p>
<div className="grow basis-0"></div>
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="center" className="min-w-[200px]">
{basePermission?.['base|delete'] && (
<DropdownMenuItem asChild>
<Button
variant="ghost"
size="xs"
asChild
className="my-[2px] w-full justify-start text-sm"
>
<Link href={`/base/${baseId}/trash`} className="font-normal">
<Trash2 className="size-4 shrink-0" />
<p className="truncate">{t('common:noun.trash')}</p>
<div className="grow basis-0"></div>
</Link>
</Button>
</DropdownMenuItem>
)}
<DropdownMenuItem asChild>
<Button
variant="ghost"
size="xs"
asChild
className="my-[2px] w-full justify-start text-sm"
>
<Link href={`/base/${baseId}/design`} className="font-normal">
<Settings className="size-4 shrink-0" />
<p className="truncate">{t('table:table.design')}</p>
<div className="grow basis-0"></div>
</Link>
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</ul>
</div>
<TableList />
Expand Down
176 changes: 73 additions & 103 deletions apps/nextjs-app/src/features/app/blocks/db-connection/Panel.tsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,12 @@
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Copy, Database, HelpCircle } from '@teable/icons';
import { Code2, HelpCircle } from '@teable/icons';
import { deleteDbConnection, getDbConnection, createDbConnection } from '@teable/openapi';
import { useBaseId, useBasePermission } from '@teable/sdk/hooks';
import {
Button,
Card,
CardContent,
CardDescription,
CardHeader,
CardTitle,
Input,
Label,
Skeleton,
} from '@teable/ui-lib/shadcn';
import { Button, Skeleton } from '@teable/ui-lib/shadcn';
import { toast } from '@teable/ui-lib/shadcn/ui/sonner';
import { Trans, useTranslation } from 'next-i18next';
import { tableConfig } from '@/features/i18n/table.config';
import { CopyButton } from '../../components/CopyButton';

const ContentCard = () => {
const baseId = useBaseId() as string;
Expand All @@ -26,8 +18,11 @@ const ContentCard = () => {
});

const mutationCreate = useMutation(createDbConnection, {
onSuccess: () => {
onSuccess: (data) => {
queryClient.invalidateQueries(['connection', baseId]);
if (!data.data) {
toast.error(t('table:connection.createFailed'));
}
},
});

Expand All @@ -39,28 +34,29 @@ const ContentCard = () => {
const dataArray = data?.dsn
? Object.entries(data?.dsn).map(([label, value]) => {
if (label === 'params') {
const display = Object.entries(value)
.map((v) => v.join('='))
.join('&');
return {
label,
type: 'text',
value: Object.entries(value)
.map((v) => v.join('='))
.join('&'),
display,
value: display,
};
}
if (label === 'pass') {
return {
label,
type: 'password',
display: '********',
value: String(value ?? ''),
};
}
return { label, type: 'text', value: String(value ?? '') };
return { label, value: String(value ?? ''), display: String(value ?? '') };
})
: [];

dataArray.unshift({
label: 'url',
type: 'text',
display: (data?.url || '').replace(data?.dsn?.pass || '', '********'),
value: data?.url || '',
});

Expand All @@ -71,80 +67,55 @@ const ContentCard = () => {
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
<Skeleton className="h-6 w-full" />
</div>
) : (
<>
<div className="flex flex-col gap-2">
{dataArray.map(({ label, type, value }) => (
<div key={label} className="flex flex-col gap-2">
{data ? (
data && (
<>
<div className="grid gap-2">
{dataArray.map(({ label, value, display }) => (
<div key={label} className="flex items-center justify-between text-sm">
<span className="text-muted-foreground">{label}</span>
<div className="flex items-center gap-2">
<Label className="w-20" htmlFor="subject">
{label}
</Label>
<Input
readOnly
data-pass={label === 'pass' ? true : undefined}
type={type}
value={value}
onMouseEnter={(e) => {
if ((e.target as HTMLInputElement).type === 'password') {
(e.target as HTMLInputElement).type = 'text';
}
}}
onMouseLeave={(e) => {
console.log(e.target);
if ((e.target as HTMLInputElement).getAttribute('data-pass')) {
(e.target as HTMLInputElement).type = 'password';
}
}}
/>
<Button
className="shrink-0"
size="icon"
variant={'outline'}
onClick={() => {
navigator.clipboard.writeText(value);
}}
>
<Copy className="size-4" />
</Button>
</div>
) : (
<div className="flex h-20 justify-center">
<Database className="size-20 text-neutral-600" />
<code className="rounded bg-muted px-1.5 py-0.5 text-xs">
{label === 'pass' || label === 'url' ? (
<span className="group relative">
<span className="group-hover:hidden">{display}</span>
<span className="hidden group-hover:inline">{value}</span>
</span>
) : (
value
)}
</code>
<CopyButton variant="ghost" size="icon" className="size-6" text={value} />
</div>
)}
</div>
))}
</div>
{data && (
<div className="text-sm text-secondary-foreground">
<Trans
ns="table"
i18nKey="connection.connectionCountTip"
components={{ b: <b /> }}
values={{
max: data.connection.max,
current: data.connection.current,
}}
/>
</div>
))}
</div>
)}
<div className="flex justify-end">
{data ? (
<Button size="sm" onClick={() => mutationDelete.mutate(baseId)}>
<div className="flex items-center justify-between text-sm">
<div className="text-sm text-muted-foreground">
<Trans
ns="table"
i18nKey="connection.connectionCountTip"
components={{ b: <b /> }}
values={{
max: data.connection.max,
current: data.connection.current,
}}
/>
</div>
<Button size="sm" variant="link" onClick={() => mutationDelete.mutate(baseId)}>
{t('common:actions.delete')}
</Button>
) : (
<Button size="sm" onClick={() => mutationCreate.mutate(baseId)}>
{t('common:actions.create')}
</Button>
)}
</div>
</>
</div>
</>
)
)}
{!data && (
<div className="flex justify-end">
<Button size="sm" variant="outline" onClick={() => mutationCreate.mutate(baseId)}>
{t('common:actions.create')}
</Button>
</div>
)}
</div>
);
Expand All @@ -155,21 +126,20 @@ export const DbConnectionPanel = ({ className }: { className?: string }) => {
const permissions = useBasePermission();

return (
<Card className={className}>
<CardHeader className="py-4">
<CardTitle>
{t('table:connection.title')}
<Button variant="ghost" size="icon">
<a href={t('table:connection.helpLink')} target="_blank" rel="noreferrer">
<HelpCircle className="size-4" />
</a>
</Button>
</CardTitle>
<CardDescription>{t('table:connection.description')}</CardDescription>
</CardHeader>
<CardContent className="flex flex-col">
{permissions?.['base|db_connection'] ? <ContentCard /> : t('table:connection.noPermission')}
</CardContent>
</Card>
<div className={className}>
<div className="mb-4 flex items-center gap-2">
<div className="flex items-center gap-2">
<Code2 className="size-4" />
<h2 className="font-semibold">{t('table:connection.title')}</h2>
</div>
<Button variant="ghost" size="icon">
<a href={t('table:connection.helpLink')} target="_blank" rel="noreferrer">
<HelpCircle className="size-4" />
</a>
</Button>
</div>
<p className="mb-2 text-sm text-muted-foreground">{t('table:connection.description')}</p>
{permissions?.['base|db_connection'] ? <ContentCard /> : t('table:connection.noPermission')}
</div>
);
};

This file was deleted.

Loading

0 comments on commit 64924e4

Please sign in to comment.