mirror of
https://github.com/rustmailer/bichon.git
synced 2026-08-03 07:48:34 +02:00
update
This commit is contained in:
@@ -62,7 +62,7 @@ export function MailFilterPopover() {
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'h-6 rounded-none px-3 gap-1.5 transition-colors',
|
||||
'h-6 rounded-none px-3 gap-1.5 transition-colors border-l-0',
|
||||
activeCount > 0 && 'bg-primary/10 text-primary hover:bg-primary/20'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -271,7 +271,7 @@ export function MailboxPopover() {
|
||||
variant="outline"
|
||||
disabled={disabled}
|
||||
className={cn(
|
||||
'h-6 rounded-none px-3 gap-1.5 transition-colors',
|
||||
'h-6 rounded-none px-3 gap-1.5 transition-colors border-l-0',
|
||||
selectedMailboxIds.length > 0 && 'bg-primary/10 text-primary border-primary/20'
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -98,7 +98,7 @@ export function TagFilterPopover() {
|
||||
size="sm"
|
||||
variant="outline"
|
||||
className={cn(
|
||||
'h-6 gap-1.5 px-3 rounded-none',
|
||||
'h-6 gap-1.5 px-3 rounded-none border-l-0',
|
||||
selectedTags.length > 0 &&
|
||||
'bg-primary/10 border-primary text-primary'
|
||||
)}
|
||||
|
||||
@@ -25,6 +25,7 @@ import { useCurrentUser } from '@/hooks/use-current-user'
|
||||
import { PermissionsDialog } from './permissions-dialog'
|
||||
import Logo from '@/assets/logo.svg'
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
|
||||
import LongText from '@/components/long-text'
|
||||
|
||||
|
||||
export function AccountAccessList() {
|
||||
@@ -70,13 +71,15 @@ export function AccountAccessList() {
|
||||
if (!email) return null
|
||||
|
||||
return (
|
||||
<Card key={accountId} className="group hover:bg-accent/40 transition-all h-fit">
|
||||
<Card key={accountId} className="group hover:bg-accent/40 transition-all h-fit min-w-0">
|
||||
<CardHeader className="flex flex-row items-center gap-3 space-y-0 pb-3">
|
||||
<div className="flex items-center justify-center w-10 h-10 rounded-full bg-primary/10 text-primary text-sm font-bold shrink-0">
|
||||
{email.charAt(0).toUpperCase()}
|
||||
</div>
|
||||
<div className="flex flex-col min-w-0">
|
||||
<CardTitle className="text-sm font-semibold truncate">{email}</CardTitle>
|
||||
<div className="flex flex-col min-w-0 flex-1">
|
||||
<CardTitle className="text-sm font-semibold">
|
||||
<LongText className="max-w-[248px]">{email}</LongText>
|
||||
</CardTitle>
|
||||
<CardDescription className="text-[10px] font-mono">
|
||||
{t('settings.profile.account.id', { id: accountId })}
|
||||
</CardDescription>
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
import * as React from "react"
|
||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"
|
||||
import { Badge } from "@/components/ui/badge"
|
||||
@@ -35,9 +36,9 @@ import { useQuery } from "@tanstack/react-query"
|
||||
import { useTranslation } from "react-i18next"
|
||||
|
||||
const formatMB = (bytes?: number) => {
|
||||
if (!bytes) return "—";
|
||||
return `${(bytes / 1024 / 1024).toFixed(0)} MB`;
|
||||
};
|
||||
if (!bytes) return "—"
|
||||
return `${(bytes / 1024 / 1024).toFixed(0)} MB`
|
||||
}
|
||||
|
||||
function BooleanBadge({ value }: { value: boolean }) {
|
||||
const { t } = useTranslation()
|
||||
@@ -58,13 +59,13 @@ function SettingRow({
|
||||
description?: string
|
||||
}) {
|
||||
return (
|
||||
<div className="py-1.5 border-b border-border/40 last:border-0">
|
||||
<div className="grid grid-cols-[1fr_auto] items-center gap-3">
|
||||
<div className="text-sm font-medium text-foreground/80 leading-tight font-mono">{label}</div>
|
||||
<div className="text-sm text-right break-all leading-tight font-medium">{value}</div>
|
||||
<div className="py-2.5 border-b border-border/40 last:border-0">
|
||||
<div className="grid grid-cols-[1fr_auto] items-center gap-4">
|
||||
<div className="text-xs font-medium text-muted-foreground font-mono">{label}</div>
|
||||
<div className="text-sm text-right font-medium">{value}</div>
|
||||
</div>
|
||||
{description && (
|
||||
<div className="mt-0.5 text-[11px] leading-tight text-muted-foreground">{description}</div>
|
||||
<div className="mt-1 text-[11px] text-muted-foreground">{description}</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
@@ -82,31 +83,36 @@ function SettingsCard({
|
||||
children: React.ReactNode
|
||||
}) {
|
||||
return (
|
||||
<Card className="h-full shadow-sm">
|
||||
<CardHeader className="flex flex-row items-center gap-2 py-3 bg-muted/20">
|
||||
<Icon className="h-4 w-4 text-primary/70" />
|
||||
<Card className="h-full">
|
||||
<CardHeader className="flex flex-row items-center gap-3 py-4 px-5 border-b bg-muted/20">
|
||||
<div className="p-1.5 rounded-md bg-primary/10">
|
||||
<Icon className="h-3.5 w-3.5 text-primary" />
|
||||
</div>
|
||||
<div className="space-y-0.5">
|
||||
<CardTitle className="text-sm font-semibold">{title}</CardTitle>
|
||||
{description && <CardDescription className="text-[11px] leading-none">{description}</CardDescription>}
|
||||
{description && (
|
||||
<CardDescription className="text-[11px]">{description}</CardDescription>
|
||||
)}
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent className="pt-2 pb-1 px-4">{children}</CardContent>
|
||||
<CardContent className="px-5 py-1">{children}</CardContent>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
|
||||
function PageSkeleton() {
|
||||
return (
|
||||
<div className="p-4 grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||
<div className="grid grid-cols-1 md:grid-cols-2 gap-5">
|
||||
{Array.from({ length: 6 }).map((_, i) => (
|
||||
<Card key={i}>
|
||||
<CardHeader className="py-3">
|
||||
<CardHeader className="py-4 px-5 border-b">
|
||||
<Skeleton className="h-4 w-40" />
|
||||
<Skeleton className="h-3 w-56" />
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-2">
|
||||
<CardContent className="px-5 py-3 space-y-3">
|
||||
<Skeleton className="h-3 w-full" />
|
||||
<Skeleton className="h-3 w-5/6" />
|
||||
<Skeleton className="h-3 w-4/6" />
|
||||
</CardContent>
|
||||
</Card>
|
||||
))}
|
||||
@@ -121,109 +127,119 @@ export default function ServerConfigurationsPage() {
|
||||
queryFn: get_system_configurations,
|
||||
})
|
||||
|
||||
if (isLoading) return <ScrollArea className="h-full"><PageSkeleton /></ScrollArea>
|
||||
if (isError || !data) {
|
||||
return <div className="p-8 text-center text-sm text-destructive">{t("systemConfig.fields.loadError")}</div>
|
||||
if (isError || (!isLoading && !data)) {
|
||||
return (
|
||||
<div className="p-8 text-center text-sm text-destructive">
|
||||
{t("systemConfig.fields.loadError")}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-6xl mx-auto">
|
||||
<div className="w-full">
|
||||
<ScrollArea className="h-full w-full">
|
||||
<div className="px-6 pt-6 pb-2">
|
||||
<div className="flex items-start gap-4 p-5 rounded-xl border bg-gradient-to-br from-secondary/50 to-background shadow-inner">
|
||||
<div className="p-2 bg-primary/10 rounded-lg">
|
||||
<InfoIcon className="h-5 w-5 text-primary" />
|
||||
</div>
|
||||
<div className="max-w-5xl mx-auto px-6 py-8 space-y-6">
|
||||
|
||||
<div className="flex items-start gap-3 p-4 rounded-xl border bg-muted/30">
|
||||
<InfoIcon className="h-4 w-4 text-muted-foreground mt-0.5 shrink-0" />
|
||||
<div>
|
||||
<h4 className="text-base font-bold text-foreground">
|
||||
{t("systemConfig.pageTitle")}
|
||||
</h4>
|
||||
<p className="text-xs text-muted-foreground mt-1 max-w-2xl leading-relaxed">
|
||||
<h4 className="text-sm font-semibold">{t("systemConfig.pageTitle")}</h4>
|
||||
<p className="text-xs text-muted-foreground mt-0.5 leading-relaxed">
|
||||
{t("systemConfig.pageDescription")}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="p-6 grid grid-cols-1 lg:grid-cols-2 gap-6">
|
||||
<SettingsCard
|
||||
icon={Server}
|
||||
title={t("systemConfig.sections.network.title")}
|
||||
description={t("systemConfig.sections.network.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_BIND_IP" value={data.bichon_bind_ip ?? "0.0.0.0"} />
|
||||
<SettingRow label="BICHON_HTTP_PORT" value={data.bichon_http_port} />
|
||||
<SettingRow label="BICHON_BASE_URL" value={data.bichon_base_url} />
|
||||
<SettingRow label="BICHON_PUBLIC_URL" value={data.bichon_public_url} />
|
||||
<SettingRow
|
||||
label="BICHON_ENABLE_REST_HTTPS"
|
||||
value={<BooleanBadge value={data.bichon_enable_rest_https} />}
|
||||
/>
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
icon={Mail}
|
||||
title={t("systemConfig.sections.smtp.title", "SMTP Server")}
|
||||
description={t("systemConfig.sections.smtp.desc", "Incoming mail reception settings")}
|
||||
>
|
||||
<SettingRow label="BICHON_ENABLE_SMTP" value={<BooleanBadge value={data.bichon_enable_smtp} />} />
|
||||
<SettingRow label="BICHON_SMTP_PORT" value={data.bichon_smtp_port} />
|
||||
<SettingRow label="BICHON_SMTP_ENCRYPTION" value={data.bichon_smtp_encryption} />
|
||||
<SettingRow label="BICHON_SMTP_AUTH_REQUIRED" value={<BooleanBadge value={data.bichon_smtp_auth_required} />} />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
icon={Zap}
|
||||
title={t("systemConfig.sections.performance.title")}
|
||||
description={t("systemConfig.sections.performance.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_SYNC_CONCURRENCY" value={data.bichon_sync_concurrency ?? t("systemConfig.status.auto")} />
|
||||
<SettingRow
|
||||
label="BICHON_HTTP_COMPRESSION_ENABLED"
|
||||
value={<BooleanBadge value={data.bichon_http_compression_enabled} />}
|
||||
/>
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
icon={Database}
|
||||
title={t("systemConfig.sections.storage.title")}
|
||||
description={t("systemConfig.sections.storage.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_ROOT_DIR" value={<span className="font-mono">{data.bichon_root_dir}</span>} />
|
||||
<SettingRow label="BICHON_DATA_DIR" value={data.bichon_data_dir ? <span className="font-mono">{data.bichon_data_dir}</span> : "—"} />
|
||||
<SettingRow label="BICHON_INDEX_DIR" value={data.bichon_index_dir ? <span className="font-mono">{data.bichon_index_dir}</span> : "—"} />
|
||||
<SettingRow label="BICHON_METADATA_CACHE_SIZE" value={formatMB(data.bichon_metadata_cache_size)} />
|
||||
<SettingRow label="BICHON_ENVELOPE_CACHE_SIZE" value={formatMB(data.bichon_envelope_cache_size)} />
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
icon={ShieldCheck}
|
||||
title={t("systemConfig.sections.security.title")}
|
||||
description={t("systemConfig.sections.security.desc")}
|
||||
>
|
||||
<SettingRow
|
||||
label="BICHON_ENCRYPT_PASSWORD_SET"
|
||||
value={
|
||||
data.bichon_encrypt_password_set ? (
|
||||
<Badge variant="secondary" className="bg-emerald-500/10 text-emerald-600 border-emerald-500/20">
|
||||
{t("systemConfig.status.configured")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="destructive">{t("systemConfig.status.missing")}</Badge>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="BICHON_WEBUI_TOKEN_EXPIRATION_HOURS"
|
||||
value={`${data.bichon_webui_token_expiration_hours}h`}
|
||||
/>
|
||||
</SettingsCard>
|
||||
<SettingsCard
|
||||
icon={Activity}
|
||||
title={t("systemConfig.sections.logging.title")}
|
||||
description={t("systemConfig.sections.logging.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_LOG_LEVEL" value={<Badge variant="outline" className="uppercase">{data.bichon_log_level}</Badge>} />
|
||||
<SettingRow label="BICHON_ANSI_LOGS" value={<BooleanBadge value={data.bichon_ansi_logs} />} />
|
||||
<SettingRow label="BICHON_JSON_LOGS" value={<BooleanBadge value={data.bichon_json_logs} />} />
|
||||
<SettingRow label="BICHON_LOG_TO_FILE" value={<BooleanBadge value={data.bichon_log_to_file} />} />
|
||||
<SettingRow label="BICHON_MAX_SERVER_LOG_FILES" value={data.bichon_max_server_log_files} />
|
||||
</SettingsCard>
|
||||
|
||||
{isLoading ? (
|
||||
<PageSkeleton />
|
||||
) : (
|
||||
<div className="grid grid-cols-1 lg:grid-cols-2 gap-5">
|
||||
<SettingsCard
|
||||
icon={Server}
|
||||
title={t("systemConfig.sections.network.title")}
|
||||
description={t("systemConfig.sections.network.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_BIND_IP" value={data!.bichon_bind_ip ?? "0.0.0.0"} />
|
||||
<SettingRow label="BICHON_HTTP_PORT" value={data!.bichon_http_port} />
|
||||
<SettingRow label="BICHON_BASE_URL" value={data!.bichon_base_url} />
|
||||
<SettingRow label="BICHON_PUBLIC_URL" value={data!.bichon_public_url} />
|
||||
<SettingRow
|
||||
label="BICHON_ENABLE_REST_HTTPS"
|
||||
value={<BooleanBadge value={data!.bichon_enable_rest_https} />}
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
icon={Mail}
|
||||
title={t("systemConfig.sections.smtp.title")}
|
||||
description={t("systemConfig.sections.smtp.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_ENABLE_SMTP" value={<BooleanBadge value={data!.bichon_enable_smtp} />} />
|
||||
<SettingRow label="BICHON_SMTP_PORT" value={data!.bichon_smtp_port} />
|
||||
<SettingRow label="BICHON_SMTP_ENCRYPTION" value={data!.bichon_smtp_encryption} />
|
||||
<SettingRow label="BICHON_SMTP_AUTH_REQUIRED" value={<BooleanBadge value={data!.bichon_smtp_auth_required} />} />
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
icon={Zap}
|
||||
title={t("systemConfig.sections.performance.title")}
|
||||
description={t("systemConfig.sections.performance.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_SYNC_CONCURRENCY" value={data!.bichon_sync_concurrency ?? t("systemConfig.status.auto")} />
|
||||
<SettingRow
|
||||
label="BICHON_HTTP_COMPRESSION_ENABLED"
|
||||
value={<BooleanBadge value={data!.bichon_http_compression_enabled} />}
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
icon={Database}
|
||||
title={t("systemConfig.sections.storage.title")}
|
||||
description={t("systemConfig.sections.storage.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_ROOT_DIR" value={<span className="font-mono">{data!.bichon_root_dir}</span>} />
|
||||
<SettingRow label="BICHON_DATA_DIR" value={data!.bichon_data_dir ? <span className="font-mono">{data!.bichon_data_dir}</span> : "—"} />
|
||||
<SettingRow label="BICHON_INDEX_DIR" value={data!.bichon_index_dir ? <span className="font-mono">{data!.bichon_index_dir}</span> : "—"} />
|
||||
<SettingRow label="BICHON_METADATA_CACHE_SIZE" value={formatMB(data!.bichon_metadata_cache_size)} />
|
||||
<SettingRow label="BICHON_ENVELOPE_CACHE_SIZE" value={formatMB(data!.bichon_envelope_cache_size)} />
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
icon={ShieldCheck}
|
||||
title={t("systemConfig.sections.security.title")}
|
||||
description={t("systemConfig.sections.security.desc")}
|
||||
>
|
||||
<SettingRow
|
||||
label="BICHON_ENCRYPT_PASSWORD_SET"
|
||||
value={
|
||||
data!.bichon_encrypt_password_set ? (
|
||||
<Badge variant="secondary" className="bg-emerald-500/10 text-emerald-600 border-emerald-500/20">
|
||||
{t("systemConfig.status.configured")}
|
||||
</Badge>
|
||||
) : (
|
||||
<Badge variant="destructive">{t("systemConfig.status.missing")}</Badge>
|
||||
)
|
||||
}
|
||||
/>
|
||||
<SettingRow
|
||||
label="BICHON_WEBUI_TOKEN_EXPIRATION_HOURS"
|
||||
value={`${data!.bichon_webui_token_expiration_hours}h`}
|
||||
/>
|
||||
</SettingsCard>
|
||||
|
||||
<SettingsCard
|
||||
icon={Activity}
|
||||
title={t("systemConfig.sections.logging.title")}
|
||||
description={t("systemConfig.sections.logging.desc")}
|
||||
>
|
||||
<SettingRow label="BICHON_LOG_LEVEL" value={<Badge variant="outline" className="uppercase">{data!.bichon_log_level}</Badge>} />
|
||||
<SettingRow label="BICHON_ANSI_LOGS" value={<BooleanBadge value={data!.bichon_ansi_logs} />} />
|
||||
<SettingRow label="BICHON_JSON_LOGS" value={<BooleanBadge value={data!.bichon_json_logs} />} />
|
||||
<SettingRow label="BICHON_LOG_TO_FILE" value={<BooleanBadge value={data!.bichon_log_to_file} />} />
|
||||
<SettingRow label="BICHON_MAX_SERVER_LOG_FILES" value={data!.bichon_max_server_log_files} />
|
||||
</SettingsCard>
|
||||
</div>
|
||||
)}
|
||||
|
||||
</div>
|
||||
</ScrollArea>
|
||||
|
||||
@@ -24,6 +24,7 @@ import { KeyRound, Palette, SettingsIcon, ShieldCheck, UserCog, Waypoints } from
|
||||
import { FixedHeader } from '@/components/layout/fixed-header'
|
||||
import { useCurrentUser } from '@/hooks/use-current-user'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
|
||||
export default function Settings() {
|
||||
const { t } = useTranslation()
|
||||
@@ -68,6 +69,15 @@ export default function Settings() {
|
||||
<>
|
||||
<FixedHeader />
|
||||
<Main>
|
||||
<div className='space-y-0.5'>
|
||||
<h1 className='text-2xl font-bold tracking-tight md:text-3xl'>
|
||||
{t('settings.header.title')}
|
||||
</h1>
|
||||
<p className='text-muted-foreground'>
|
||||
{t('settings.header.description')}
|
||||
</p>
|
||||
</div>
|
||||
<Separator className='my-4 lg:my-6' />
|
||||
<div className='flex flex-1 flex-col space-y-2 md:space-y-2 overflow-hidden lg:flex-row lg:space-x-12 lg:space-y-0'>
|
||||
<aside className='top-0 lg:sticky lg:w-1/5'>
|
||||
<SidebarNav items={sidebarNavItems} />
|
||||
|
||||
Reference in New Issue
Block a user