refactor: update styling and improve code readability across multiple components

This commit is contained in:
CaIon
2026-06-19 19:29:00 +08:00
parent a0de4b560e
commit 50b8f2a272
19 changed files with 646 additions and 364 deletions
@@ -1,3 +1,5 @@
import { useQuery } from '@tanstack/react-query'
import { Copy, ExternalLink, Loader2, RefreshCcw } from 'lucide-react'
/*
Copyright (C) 2023-2026 QuantumNous
@@ -17,10 +19,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useMemo } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Copy, ExternalLink, Loader2, RefreshCcw } from 'lucide-react'
import { useTranslation } from 'react-i18next'
import { toast } from 'sonner'
import { Dialog } from '@/components/dialog'
import { Button } from '@/components/ui/button'
import {
Collapsible,
@@ -28,7 +30,7 @@ import {
CollapsibleTrigger,
} from '@/components/ui/collapsible'
import { Separator } from '@/components/ui/separator'
import { Dialog } from '@/components/dialog'
import { getDeployment, listDeploymentContainers } from '../../api'
export function ViewDetailsDialog({
@@ -73,10 +75,14 @@ export function ViewDetailsDialog({
const locations = useMemo(() => {
const items = details?.locations
if (!Array.isArray(items)) return []
if (!Array.isArray(items)) {
return []
}
return items
.map((x) => {
if (!x || typeof x !== 'object') return null
if (!x || typeof x !== 'object') {
return null
}
const name = (x as Record<string, unknown>)?.name
const iso2 = (x as Record<string, unknown>)?.iso2
const id = (x as Record<string, unknown>)?.id
@@ -86,7 +92,9 @@ export function ViewDetailsDialog({
}, [details])
const handleCopyId = async () => {
if (deploymentId === null || deploymentId === undefined) return
if (deploymentId === null || deploymentId === undefined) {
return
}
try {
await navigator.clipboard.writeText(String(deploymentId))
toast.success(t('Copied'))
@@ -108,6 +116,9 @@ export function ViewDetailsDialog({
return ''
}
}, [details])
const isDetailsLoading = isLoadingDetails || isLoadingContainers
const showDetailsError = !isDetailsLoading && !detailsRes?.success
const showDetailsContent = !isDetailsLoading && detailsRes?.success
return (
<Dialog
@@ -118,15 +129,13 @@ export function ViewDetailsDialog({
contentHeight='auto'
bodyClassName='space-y-4'
footer={
<>
<Button
variant='outline'
onClick={() => onOpenChange(false)}
className='w-full sm:w-auto'
>
{t('Close')}
</Button>
</>
<Button
variant='outline'
onClick={() => onOpenChange(false)}
className='w-full sm:w-auto'
>
{t('Close')}
</Button>
}
>
<div className='max-h-[calc(100dvh-8.5rem)] space-y-3 overflow-y-auto py-2 pr-1 sm:max-h-[72vh] sm:space-y-4'>
@@ -158,15 +167,17 @@ export function ViewDetailsDialog({
<Separator />
{isLoadingDetails || isLoadingContainers ? (
{isDetailsLoading ? (
<div className='flex items-center justify-center py-10'>
<Loader2 className='text-muted-foreground h-6 w-6 animate-spin' />
</div>
) : !detailsRes?.success ? (
) : null}
{showDetailsError ? (
<div className='text-muted-foreground py-10 text-center text-sm'>
{detailsRes?.message || t('Failed to fetch deployment details')}
</div>
) : (
) : null}
{showDetailsContent ? (
<>
<div className='grid gap-3 sm:grid-cols-2'>
<div className='rounded-lg border p-3'>
@@ -225,7 +236,9 @@ export function ViewDetailsDialog({
<div className='space-y-2'>
{containers.map((c) => {
const id = c?.container_id
if (typeof id !== 'string' || !id) return null
if (typeof id !== 'string' || !id) {
return null
}
const status =
typeof c?.status === 'string' ? c.status : undefined
const url =
@@ -263,13 +276,13 @@ export function ViewDetailsDialog({
{t('Raw JSON')}
</CollapsibleTrigger>
<CollapsibleContent>
<pre className='mt-3 max-h-[360px] overflow-auto rounded-md bg-black p-3 text-xs text-gray-200'>
<pre className='bg-muted text-foreground mt-3 max-h-[360px] overflow-auto rounded-md p-3 text-xs'>
{payloadJson || '-'}
</pre>
</CollapsibleContent>
</Collapsible>
</>
)}
) : null}
</div>
</Dialog>
)
@@ -1,3 +1,5 @@
import { useQuery } from '@tanstack/react-query'
import { Download, Loader2, RefreshCcw, Terminal } from 'lucide-react'
/*
Copyright (C) 2023-2026 QuantumNous
@@ -16,10 +18,10 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
For commercial licensing, please contact support@quantumnous.com
*/
import { useEffect, useMemo, useRef, useState } from 'react'
import { useQuery } from '@tanstack/react-query'
import { Download, Loader2, RefreshCcw, Terminal } from 'lucide-react'
import { type ReactNode, useEffect, useMemo, useRef, useState } from 'react'
import { useTranslation } from 'react-i18next'
import { Dialog } from '@/components/dialog'
import { Button } from '@/components/ui/button'
import {
Select,
@@ -30,7 +32,7 @@ import {
SelectValue,
} from '@/components/ui/select'
import { Switch } from '@/components/ui/switch'
import { Dialog } from '@/components/dialog'
import { getDeploymentLogs, listDeploymentContainers } from '../../api'
interface ViewLogsDialogProps {
@@ -114,9 +116,17 @@ export function ViewLogsDialog({
}, [logsData?.data])
const logLines = useMemo(() => {
const normalized = logsText.replace(/\r\n?/g, '\n')
const normalized = logsText.replaceAll(/\r\n?/g, '\n')
return normalized ? normalized.split('\n') : []
}, [logsText])
const keyedLogLines = useMemo(() => {
const seen = new Map<string, number>()
return logLines.map((line) => {
const count = seen.get(line) ?? 0
seen.set(line, count + 1)
return { key: `${line}-${count}`, line }
})
}, [logLines])
// Auto-scroll to bottom
useEffect(() => {
@@ -135,6 +145,44 @@ export function ViewLogsDialog({
a.click()
URL.revokeObjectURL(url)
}
let containerPlaceholder = t('Select')
if (isLoadingContainers) {
containerPlaceholder = t('Loading...')
} else if (containers.length === 0) {
containerPlaceholder = t('No containers')
}
let logsContent: ReactNode
if (isLoadingContainers || isLoadingLogs) {
logsContent = (
<div className='flex items-center justify-center py-8'>
<Loader2 className='h-6 w-6 animate-spin text-gray-400' />
</div>
)
} else if (containers.length === 0) {
logsContent = (
<div className='py-8 text-center text-gray-400'>{t('No containers')}</div>
)
} else if (!containerId) {
logsContent = (
<div className='py-8 text-center text-gray-400'>
{t('Please select a container')}
</div>
)
} else if (!logsText.trim()) {
logsContent = (
<div className='py-8 text-center text-gray-400'>{t('No logs')}</div>
)
} else {
logsContent = (
<div className='font-mono text-sm'>
{keyedLogLines.map(({ key, line }) => (
<div key={key} className='whitespace-pre-wrap text-gray-200'>
{line}
</div>
))}
</div>
)
}
return (
<Dialog
@@ -191,47 +239,39 @@ export function ViewLogsDialog({
<div className='space-y-1'>
<div className='text-muted-foreground text-xs'>{t('Container')}</div>
<Select
items={[
...containers.flatMap((c) => {
const id = c?.container_id
if (typeof id !== 'string' || !id) return []
const status =
typeof c?.status === 'string' && c.status
? ` (${c.status})`
: ''
return [
{
value: id,
label: (
<>
{id}
{status}
</>
),
},
]
}),
]}
items={containers.flatMap((c) => {
const id = c?.container_id
if (typeof id !== 'string' || !id) return []
const status =
typeof c?.status === 'string' && c.status
? ` (${c.status})`
: ''
return [
{
value: id,
label: (
<>
{id}
{status}
</>
),
},
]
})}
value={containerId}
onValueChange={(v) => v !== null && setContainerId(v)}
disabled={isLoadingContainers || containers.length === 0}
>
<SelectTrigger>
<SelectValue
placeholder={
isLoadingContainers
? t('Loading...')
: containers.length === 0
? t('No containers')
: t('Select')
}
/>
<SelectValue placeholder={containerPlaceholder} />
</SelectTrigger>
<SelectContent alignItemWithTrigger={false}>
<SelectGroup>
{containers.map((c) => {
const id = c?.container_id
if (typeof id !== 'string' || !id) return null
if (typeof id !== 'string' || !id) {
return null
}
const status =
typeof c?.status === 'string' && c.status
? ` (${c.status})`
@@ -279,7 +319,7 @@ export function ViewLogsDialog({
</div>
<div
ref={scrollRef}
className='flex-1 overflow-auto rounded-md border bg-black p-3 sm:p-4'
className='bg-muted flex-1 overflow-auto rounded-md border p-3 sm:p-4'
onScroll={(e) => {
const target = e.target as HTMLDivElement
const isAtBottom =
@@ -287,29 +327,7 @@ export function ViewLogsDialog({
setAutoScroll(isAtBottom)
}}
>
{isLoadingContainers || isLoadingLogs ? (
<div className='flex items-center justify-center py-8'>
<Loader2 className='h-6 w-6 animate-spin text-gray-400' />
</div>
) : containers.length === 0 ? (
<div className='py-8 text-center text-gray-400'>
{t('No containers')}
</div>
) : !containerId ? (
<div className='py-8 text-center text-gray-400'>
{t('Please select a container')}
</div>
) : !logsText.trim() ? (
<div className='py-8 text-center text-gray-400'>{t('No logs')}</div>
) : (
<div className='font-mono text-sm'>
{logLines.map((line, idx) => (
<div key={idx} className='whitespace-pre-wrap text-gray-200'>
{line}
</div>
))}
</div>
)}
{logsContent}
</div>
</Dialog>
)