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
4 changes: 1 addition & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,9 +63,7 @@ jobs:
run: |
echo "## 🧪 Running Full Test Suite" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
if [ -d "tests" ] && [ -n "$(find tests -name '*.test.ts' -o -name '*.spec.ts' 2>/dev/null)" ]; then
pnpm run test:run 2>/dev/null || true
fi
pnpm run test:run
echo "✅ All tests passed!" >> $GITHUB_STEP_SUMMARY

- name: 🔍 Validate code quality
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ jobs:
run: pnpm ci

- name: 🧪 Run unit tests
run: pnpm run test:run 2>/dev/null || echo "No test:run script, skipping"
run: pnpm run test:run

- name: 📊 Upload test results
uses: actions/upload-artifact@v7
Expand Down
15 changes: 9 additions & 6 deletions app/(dashboard)/access-keys/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -184,16 +184,19 @@ export default function AccessKeysPage() {
positiveText: t("Confirm"),
negativeText: t("Cancel"),
onPositiveClick: async () => {
try {
await Promise.all(selectedKeys.map((key) => deleteServiceAccount(key)))
const results = await Promise.allSettled(selectedKeys.map((key) => deleteServiceAccount(key)))
const failures = results.filter((result) => result.status === "rejected")

if (failures.length === 0) {
message.success(t("Delete Success"))
table.resetRowSelection()
await listUserAccounts()
} catch (error) {
} else {
const firstFailure = failures[0]
const error = firstFailure?.status === "rejected" ? firstFailure.reason : undefined
console.error(error)
const msg = (error as Error)?.message || t("Delete Failed")
message.error(msg)
message.error((error as Error)?.message || t("Delete Failed"))
}
await listUserAccounts()
},
})
}
Expand Down
15 changes: 9 additions & 6 deletions app/(dashboard)/users/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -199,16 +199,19 @@ export default function UsersPage() {
positiveText: t("Confirm"),
negativeText: t("Cancel"),
onPositiveClick: async () => {
try {
await Promise.all(selectedKeys.map((key) => deleteUser(key)))
const results = await Promise.allSettled(selectedKeys.map((key) => deleteUser(key)))
const failures = results.filter((result) => result.status === "rejected")

if (failures.length === 0) {
message.success(t("Delete Success"))
table.resetRowSelection()
await getDataList()
} catch (error) {
} else {
const firstFailure = failures[0]
const error = firstFailure?.status === "rejected" ? firstFailure.reason : undefined
console.error(error)
const msg = (error as Error)?.message || t("Delete Failed")
message.error(msg)
message.error((error as Error)?.message || t("Delete Failed"))
}
await getDataList()
},
})
}
Expand Down
1 change: 1 addition & 0 deletions components/license/enterprise-section.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ export function LicenseEnterpriseSection() {
window.open(
"https://ww18.53kf.com/webCompany.php?arg=11003151&kf_sign=DA4MDMTc0Ng4MjE1MjEzODAyNDkyMDAyNzMwMDMxNTE%253D&style=2",
"_blank",
"noopener,noreferrer",
)
}

Expand Down
1 change: 1 addition & 0 deletions components/object/list.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -375,6 +375,7 @@ export function ObjectList({
try {
const url = await getSignedUrl(key)
const response = await fetch(url)
if (!response.ok) throw new Error(t("Download Failed"))
const filename = key.split("/").pop() ?? ""
const headers: Record<string, string> = {
"content-type": getContentType(response.headers, filename),
Expand Down
19 changes: 15 additions & 4 deletions components/object/preview-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -160,13 +160,24 @@ export function ObjectPreviewModal({ show, onShowChange, object }: ObjectPreview

React.useEffect(() => {
if (show && previewMode === "text" && previewUrl) {
const controller = new AbortController()
setLoading(true)
setIsFormatted(true)
fetch(previewUrl)
.then((r) => r.text())
setTextContent("")
fetch(previewUrl, { signal: controller.signal })
.then((response) => {
if (!response.ok) throw new Error(`HTTP ${response.status}`)
return response.text()
})
.then(setTextContent)
.catch(() => setTextContent(t("Preview unavailable")))
.finally(() => setLoading(false))
.catch((error: unknown) => {
if ((error as Error)?.name !== "AbortError") setTextContent(t("Preview unavailable"))
})
.finally(() => {
if (!controller.signal.aborted) setLoading(false)
})

return () => controller.abort()
} else if (!show) {
setTextContent("")
setLoading(false)
Expand Down
36 changes: 25 additions & 11 deletions components/tasks/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as React from "react"
import { useTranslation } from "react-i18next"
import { RiDeleteBinLine } from "@remixicon/react"
import { RiCloseCircleLine, RiDeleteBinLine } from "@remixicon/react"
import { Button } from "@/components/ui/button"
import { Progress } from "@/components/ui/progress"
import { useTaskManager } from "@/contexts/task-context"
Expand All @@ -17,7 +17,6 @@ const statusMap: Record<string, string> = {
running: "in progress",
completed: "success",
failed: "failed",
paused: "paused",
canceled: "canceled",
}

Expand All @@ -27,20 +26,35 @@ export function TaskItem({ task }: TaskItemProps) {

const statusKey = statusMap[task.status] ?? "in progress"
const statusText = `${t(task.actionLabel)}${t(statusKey)}`
const isActive = task.status === "pending" || task.status === "running"
const isFinished = task.status === "completed" || task.status === "failed" || task.status === "canceled"

return (
<div className="flex flex-col gap-1">
<div className="flex items-center justify-between gap-3 group">
<div className="min-w-0 flex-1 truncate text-sm font-medium text-foreground">{task.displayName}</div>
<Button
variant="ghost"
size="sm"
className="h-auto shrink-0 px-2 text-xs opacity-0 group-hover:opacity-100"
aria-label={t("Delete Record")}
onClick={() => taskManager.removeTask(task.id)}
>
<RiDeleteBinLine className="size-4 text-destructive" aria-hidden />
</Button>
{isActive && (
<Button
variant="ghost"
size="sm"
className="h-auto shrink-0 px-2 text-xs opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100"
aria-label={t("Cancel")}
onClick={() => taskManager.cancelTask(task.id)}
>
<RiCloseCircleLine className="size-4" aria-hidden />
</Button>
)}
{isFinished && (
<Button
variant="ghost"
size="sm"
className="h-auto shrink-0 px-2 text-xs opacity-100 sm:opacity-0 sm:group-hover:opacity-100 sm:group-focus-within:opacity-100"
aria-label={t("Delete Record")}
onClick={() => taskManager.removeFinishedTask(task.id)}
>
<RiDeleteBinLine className="size-4 text-destructive" aria-hidden />
</Button>
)}
</div>
<Progress value={task.progress} className="h-[2px]" />
<div className="flex items-center justify-between text-xs text-muted-foreground">
Expand Down
40 changes: 24 additions & 16 deletions components/tasks/panel.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,39 +10,42 @@ import { TaskItem } from "./item"
import type { AnyTask } from "@/contexts/task-context"

interface TaskPanelProps {
tasks: AnyTask[]
onClearTasks: () => void
tasks: readonly AnyTask[]
onClearFinishedTasks: () => void
}

export function TaskPanel({ tasks, onClearTasks }: TaskPanelProps) {
export function TaskPanel({ tasks, onClearFinishedTasks }: TaskPanelProps) {
const { t } = useTranslation()
const [tab, setTab] = React.useState("pending")

const pending = tasks.filter((task) => task.status === "pending")
const processing = tasks.filter((task) => task.status === "running")
const completed = tasks.filter((task) => task.status === "completed")
const failed = tasks.filter((task) => task.status === "failed")
const canceled = tasks.filter((task) => task.status === "canceled")

const total = tasks.length
const percentage = total === 0 ? 100 : Math.floor((completed.length / total) * 100)
const finished = completed.length + failed.length + canceled.length
const percentage = total === 0 ? 100 : Math.floor((finished / total) * 100)

React.useEffect(() => {
if (processing.length > 0) setTab("processing")
else if (pending.length > 0) setTab("pending")
else if (completed.length > 0) setTab("completed")
else if (failed.length > 0) setTab("failed")
else if (canceled.length > 0) setTab("canceled")
else setTab("pending")
}, [processing.length, pending.length, completed.length])

const withCount = (key: string, count: number) => t(key).replace(/\{[^}]*\}/g, String(count))
}, [processing.length, pending.length, completed.length, failed.length, canceled.length])

const tabs = [
{ value: "pending", label: withCount("Pending", pending.length) },
{ value: "processing", label: withCount("Processing (with count)", processing.length) },
{ value: "completed", label: withCount("Completed", completed.length) },
{ value: "failed", label: withCount("Failed", failed.length) },
{ value: "pending", label: t("Pending", { count: pending.length }) },
{ value: "processing", label: t("Processing (with count)", { count: processing.length }) },
{ value: "completed", label: t("Completed", { count: completed.length }) },
{ value: "failed", label: t("Failed", { count: failed.length }) },
{ value: "canceled", label: `${t("Canceled")} (${canceled.length})` },
]

const renderList = (list: AnyTask[]) => (
const renderList = (list: readonly AnyTask[]) => (
<div className="flex flex-col gap-3">
{list.length === 0 ? (
<p className="py-4 text-center text-sm text-muted-foreground">{t("No Data")}</p>
Expand All @@ -69,11 +72,13 @@ export function TaskPanel({ tasks, onClearTasks }: TaskPanelProps) {
<div className="space-y-3">
<div className="flex items-center justify-between text-sm text-muted-foreground">
<div>
{total - pending.length - processing.length}/{total}
{finished}/{total}
</div>
<Button variant="ghost" size="sm" className="h-auto px-2" onClick={onClearTasks}>
{t("Clear Records")}
</Button>
{finished > 0 && (
<Button variant="ghost" size="sm" className="h-auto px-2" onClick={onClearFinishedTasks}>
{t("Clear Records")}
</Button>
)}
</div>
<Progress value={percentage} className="h-[3px]" />
</div>
Expand All @@ -100,6 +105,9 @@ export function TaskPanel({ tasks, onClearTasks }: TaskPanelProps) {
<TabsContent value="failed" className="mt-0">
{renderList(failed)}
</TabsContent>
<TabsContent value="canceled" className="mt-0">
{renderList(canceled)}
</TabsContent>
</Tabs>
</div>
)
Expand Down
6 changes: 4 additions & 2 deletions components/tasks/stats-button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,17 +15,19 @@ export function TaskStatsButton() {
const taskManager = useTaskManager()
const { isTaskPanelOpen, setTaskPanelOpen } = useTaskPanelOpen()

const pending = tasks.filter((task) => task.status === "pending")
const processing = tasks.filter((task) => task.status === "running")
const completed = tasks.filter((task) => task.status === "completed")
const failed = tasks.filter((task) => task.status === "failed")
const active = pending.length + processing.length
const total = tasks.length

if (total === 0) return null

return (
<Drawer open={isTaskPanelOpen} onOpenChange={setTaskPanelOpen} swipeDirection="right">
<DrawerTrigger render={<Button variant="outline" />}>
{processing.length > 0 ? (
{active > 0 ? (
<div className="flex items-center gap-2">
<Spinner className="size-3 text-muted-foreground" />
<span>
Expand All @@ -48,7 +50,7 @@ export function TaskStatsButton() {
<DrawerTitle>{t("Task Management")}</DrawerTitle>
</DrawerHeader>
<div className="overflow-auto p-4">
<TaskPanel tasks={tasks} onClearTasks={() => taskManager.clearTasks()} />
<TaskPanel tasks={tasks} onClearFinishedTasks={() => taskManager.clearFinishedTasks()} />
</div>
</DrawerContent>
</Drawer>
Expand Down
Loading