Skip to content
Draft
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
6 changes: 6 additions & 0 deletions components/admin/AdminTreeNavigation.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,12 @@ export default function AdminTreeNavigation({
icon: HiOutlineCollection,
href: '/admin/add-unclaimed-node',
},
{
id: 'category-management',
label: t('Category Management'),
icon: HiOutlineCollection,
href: '/admin/categories',
},
],
},
{
Expand Down
4 changes: 4 additions & 0 deletions locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Cannot delete node.": "Cannot delete node.",
"Cannot delete version.": "Cannot delete version.",
"Category": "Category",
"Category Management": "Category Management",
"Checking username...": "Checking username...",
"Choose which publisher account to use when claiming this node.": "Choose which publisher account to use when claiming this node.",
"Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository": "Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository",
Expand Down Expand Up @@ -188,6 +189,7 @@
"New Publisher": "New Publisher",
"Next": "Next",
"No batch ID found for {{id}}@{{version}}": "No batch ID found for {{id}}@{{version}}",
"No categories found": "No categories found",
"No nodes found": "No nodes found",
"No preempted comfy node names added yet": "No preempted comfy node names added yet",
"No publisher information available": "No publisher information available",
Expand All @@ -197,6 +199,7 @@
"No unclaimed nodes found.": "No unclaimed nodes found.",
"No versions selected": "No versions selected",
"Node": "Node",
"Node Categories Overview": "Node Categories Overview",
"Node Claimed Successfully": "Node Claimed Successfully",
"Node ID": "Node ID",
"Node ID is required": "Node ID is required",
Expand Down Expand Up @@ -372,6 +375,7 @@
"Your nodes": "Your nodes",
"approve": "approve",
"at": "at",
"categories": "categories",
"comma separated": "comma separated",
"downloads": "downloads",
"e.g.\\nCUDA\\nROCm\\nMetal\\nCPU": "e.g.\\nCUDA\\nROCm\\nMetal\\nCPU",
Expand Down
4 changes: 4 additions & 0 deletions locales/es/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Cannot delete node.": "No se puede eliminar el nodo.",
"Cannot delete version.": "No se puede eliminar la versión.",
"Category": "Categoría",
"Category Management": "Gestión de Categorías",
"Checking username...": "Verificando nombre de usuario...",
"Choose which publisher account to use when claiming this node.": "Elija qué cuenta de editor utilizar al reclamar este nodo.",
"Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository": "Elija qué cuenta de editor desea utilizar para reclamar este nodo. Debe ser el propietario del repositorio de GitHub",
Expand Down Expand Up @@ -188,6 +189,7 @@
"New Publisher": "Nuevo Editor",
"Next": "Siguiente",
"No batch ID found for {{id}}@{{version}}": "No se encontró ID de lote para {{id}}@{{version}}",
"No categories found": "No se encontraron categorías",
"No nodes found": "No se encontraron nodos",
"No preempted comfy node names added yet": "Aún no se han agregado nombres de nodos cómodos anticipados",
"No publisher information available": "No hay información de editor disponible",
Expand All @@ -197,6 +199,7 @@
"No unclaimed nodes found.": "No se encontraron nodos sin reclamar.",
"No versions selected": "No se han seleccionado versiones",
"Node": "Nodo",
"Node Categories Overview": "Visión General de Categorías de Nodos",
"Node Claimed Successfully": "Nodo reclamado con éxito",
"Node ID": "ID de nodo",
"Node ID is required": "Se requiere ID de nodo",
Expand Down Expand Up @@ -372,6 +375,7 @@
"Your nodes": "Tus nodos",
"approve": "aprobar",
"at": "en",
"categories": "Categorías",
"comma separated": "separado por comas",
"downloads": "descargas",
"e.g.\\nCUDA\\nROCm\\nMetal\\nCPU": "p. ej.\\nCUDA\\nROCm\\nMetal\\nCPU",
Expand Down
4 changes: 4 additions & 0 deletions locales/fr/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Cannot delete node.": "Impossible de supprimer le nœud.",
"Cannot delete version.": "Impossible de supprimer la version.",
"Category": "Catégorie",
"Category Management": "Gestion des catégories",
"Checking username...": "Vérification du nom d'utilisateur...",
"Choose which publisher account to use when claiming this node.": "Choisissez quel compte éditeur utiliser lors de la revendication de ce nœud.",
"Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository": "Choisissez le compte éditeur que vous souhaitez utiliser pour revendiquer ce nœud. Vous devez être le propriétaire du dépôt GitHub.",
Expand Down Expand Up @@ -188,6 +189,7 @@
"New Publisher": "Nouvel Éditeur",
"Next": "Suivant",
"No batch ID found for {{id}}@{{version}}": "Aucun ID de lot trouvé pour {{id}}@{{version}}",
"No categories found": "Aucune catégorie trouvée",
"No nodes found": "Aucun nœud trouvé",
"No preempted comfy node names added yet": "Aucun nom de nœud confort préempté n'a encore été ajouté",
"No publisher information available": "Aucune information sur l'éditeur disponible",
Expand All @@ -197,6 +199,7 @@
"No unclaimed nodes found.": "Aucun nœud non réclamé trouvé.",
"No versions selected": "Aucune version sélectionnée",
"Node": "Nœud",
"Node Categories Overview": "Aperçu des catégories de nœuds",
"Node Claimed Successfully": "Nœud revendiqué avec succès",
"Node ID": "ID de nœud",
"Node ID is required": "L'ID de nœud est requis",
Expand Down Expand Up @@ -372,6 +375,7 @@
"Your nodes": "Vos nœuds",
"approve": "approuver",
"at": "à",
"categories": "Catégories",
"comma separated": "séparés par des virgules",
"downloads": "téléchargements",
"e.g.\\nCUDA\\nROCm\\nMetal\\nCPU": "par exemple :\\nCUDA\\nROCm\\nMetal\\nCPU",
Expand Down
4 changes: 4 additions & 0 deletions locales/ja/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Cannot delete node.": "ノードを削除できません。",
"Cannot delete version.": "バージョンを削除できません。",
"Category": "カテゴリー",
"Category Management": "カテゴリー管理",
"Checking username...": "ユーザー名を確認中...",
"Choose which publisher account to use when claiming this node.": "このノードを請求する際に使用するパブリッシャーアカウントを選択してください。",
"Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository": "このノードを請求するために使用するパブリッシャーアカウントを選択してください。あなたはGitHubリポジトリの所有者でなければなりません。",
Expand Down Expand Up @@ -188,6 +189,7 @@
"New Publisher": "新しいパブリッシャー",
"Next": "次へ",
"No batch ID found for {{id}}@{{version}}": "{{id}}@{{version}}のバッチIDが見つかりません",
"No categories found": "カテゴリーが見つかりません",
"No nodes found": "ノードが見つかりません",
"No preempted comfy node names added yet": "まだ先取りされたComfyノード名は追加されていません",
"No publisher information available": "利用可能なパブリッシャー情報はありません。",
Expand All @@ -197,6 +199,7 @@
"No unclaimed nodes found.": "未請求のノードは見つかりませんでした。",
"No versions selected": "バージョンが選択されていません",
"Node": "ノード",
"Node Categories Overview": "ノードカテゴリー概要",
"Node Claimed Successfully": "ノードが正常に請求されました",
"Node ID": "ノードID",
"Node ID is required": "ノードIDが必要です",
Expand Down Expand Up @@ -372,6 +375,7 @@
"Your nodes": "あなたのノード",
"approve": "承認",
"at": "で",
"categories": "カテゴリー",
"comma separated": "コンマ区切り",
"downloads": "ダウンロード",
"e.g.\\nCUDA\\nROCm\\nMetal\\nCPU": "例:\nCUDA\nROCm\nMetal\nCPU",
Expand Down
4 changes: 4 additions & 0 deletions locales/ko/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Cannot delete node.": "노드를 삭제할 수 없습니다.",
"Cannot delete version.": "버전을 삭제할 수 없습니다.",
"Category": "카테고리",
"Category Management": "카테고리 관리",
"Checking username...": "사용자 이름 확인 중...",
"Choose which publisher account to use when claiming this node.": "이 노드를 청구할 때 사용할 발행인 계정을 선택하세요.",
"Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository": "이 노드를 청구할 때 사용할 발행인 계정을 선택하세요. 당신은 GitHub 저장소의 소유자여야 합니다.",
Expand Down Expand Up @@ -188,6 +189,7 @@
"New Publisher": "새로운 발행인",
"Next": "다음",
"No batch ID found for {{id}}@{{version}}": "{{id}}@{{version}}에 대한 배치 ID를 찾을 수 없습니다",
"No categories found": "카테고리를 찾을 수 없습니다.",
"No nodes found": "노드를 찾을 수 없습니다",
"No preempted comfy node names added yet": "아직 사전 점유된 Comfy 노드 이름이 추가되지 않았습니다.",
"No publisher information available": "사용 가능한 발행인 정보가 없습니다.",
Expand All @@ -197,6 +199,7 @@
"No unclaimed nodes found.": "미청구 노드를 찾을 수 없습니다.",
"No versions selected": "버전이 선택되지 않았습니다.",
"Node": "노드",
"Node Categories Overview": "노드 카테고리 개요",
"Node Claimed Successfully": "노드가 성공적으로 청구되었습니다",
"Node ID": "노드 ID",
"Node ID is required": "노드 ID가 필요합니다",
Expand Down Expand Up @@ -372,6 +375,7 @@
"Your nodes": "당신의 노드",
"approve": "승인",
"at": "에서",
"categories": "카테고리",
"comma separated": "쉼표로 구분된",
"downloads": "다운로드",
"e.g.\\nCUDA\\nROCm\\nMetal\\nCPU": "예:\\nCUDA\\nROCm\\nMetal\\nCPU",
Expand Down
4 changes: 4 additions & 0 deletions locales/ru/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Cannot delete node.": "Невозможно удалить узел.",
"Cannot delete version.": "Невозможно удалить версию.",
"Category": "Категория",
"Category Management": "Управление категорией",
"Checking username...": "Проверка имени пользователя...",
"Choose which publisher account to use when claiming this node.": "Выберите, каким аккаунтом издателя воспользоваться при заявке на этот узел.",
"Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository": "Выберите учетную запись издателя, которую вы хотите использовать для заявки на этот узел. Вы должны быть владельцем репозитория GitHub.",
Expand Down Expand Up @@ -188,6 +189,7 @@
"New Publisher": "Новый издатель",
"Next": "Следующий",
"No batch ID found for {{id}}@{{version}}": "Не найден batch ID для {{id}}@{{version}}",
"No categories found": "Категории не найдены",
"No nodes found": "Узлы не найдены",
"No preempted comfy node names added yet": "Замещенные имена узлов comfy еще не добавлены",
"No publisher information available": "Нет доступной информации об издателе",
Expand All @@ -197,6 +199,7 @@
"No unclaimed nodes found.": "Невостребованные узлы не найдены.",
"No versions selected": "Версии не выбраны",
"Node": "Узел",
"Node Categories Overview": "Обзор категорий узлов",
"Node Claimed Successfully": "Узел успешно заявлен",
"Node ID": "ID узла",
"Node ID is required": "Требуется ID узла",
Expand Down Expand Up @@ -372,6 +375,7 @@
"Your nodes": "Ваши узлы",
"approve": "одобрить",
"at": "в",
"categories": "Категории",
"comma separated": "разделенные запятыми",
"downloads": "загрузки",
"e.g.\\nCUDA\\nROCm\\nMetal\\nCPU": "например:\\nCUDA\\nROCm\\nMetal\\nCPU",
Expand Down
4 changes: 4 additions & 0 deletions locales/zh/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"Cannot delete node.": "无法删除节点。",
"Cannot delete version.": "无法删除版本。",
"Category": "类别",
"Category Management": "类别管理",
"Checking username...": "正在检查用户名...",
"Choose which publisher account to use when claiming this node.": "选择在认领此节点时要使用的发布者账户。",
"Choose which publisher account you want to use to claim this node. You must be the owner of the GitHub repository": "选择您想使用哪个发布者账户来认领此节点。您必须是 GitHub 仓库的所有者。",
Expand Down Expand Up @@ -188,6 +189,7 @@
"New Publisher": "新发布者",
"Next": "下一步",
"No batch ID found for {{id}}@{{version}}": "未找到{{id}}@{{version}}的批次ID",
"No categories found": "未找到类别",
"No nodes found": "未找到节点",
"No preempted comfy node names added yet": "还没有添加预占的 Comfy 节点名称",
"No publisher information available": "没有可用的发布者信息",
Expand All @@ -197,6 +199,7 @@
"No unclaimed nodes found.": "未找到未认领的节点。",
"No versions selected": "未选择版本",
"Node": "节点",
"Node Categories Overview": "节点类别概览",
"Node Claimed Successfully": "节点成功认领",
"Node ID": "节点ID",
"Node ID is required": "需要节点ID",
Expand Down Expand Up @@ -372,6 +375,7 @@
"Your nodes": "您的节点",
"approve": "批准",
"at": "在",
"categories": "类别",
"comma separated": "用逗号分隔",
"downloads": "下载",
"e.g.\\nCUDA\\nROCm\\nMetal\\nCPU": "例如:\nCUDA\nROCm\nMetal\nCPU",
Expand Down
116 changes: 116 additions & 0 deletions pages/admin/categories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import withAdmin from '@/components/common/HOC/authAdmin'
import AdminTreeNavigation from '@/components/admin/AdminTreeNavigation'
import { CustomPagination } from '@/components/common/CustomPagination'
import { usePage } from '@/components/hooks/usePage'
import { useListAllNodes } from '@/src/api/generated'
import { useNextTranslation } from '@/src/hooks/i18n'
import { Breadcrumb, Card, Badge } from 'flowbite-react'
import { HiHome, HiOutlineCollection } from 'react-icons/hi'
import React, { useMemo, useState } from 'react'

export default withAdmin(CategoriesPage)
function CategoriesPage() {
const { t } = useNextTranslation()
const [page, setPage] = usePage()
const [limit] = useState<number>(20)

const { data: nodesData } = useListAllNodes({
page: page || 1,
limit: limit,

sort: ['category'],
})

const categoriesData = useMemo(() => {
if (!nodesData?.nodes) return []

const categoryMap = new Map<string, number>()
nodesData.nodes.forEach((node) => {
const category = node.category || 'Uncategorized'
categoryMap.set(category, (categoryMap.get(category) || 0) + 1)
})

return Array.from(categoryMap.entries())
.map(([category, count]) => ({ category, count }))
.sort((a, b) => b.count - a.count)
}, [nodesData?.nodes])

Comment on lines +24 to +37
Copy link
Preview

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The category calculation processes all nodes on every page, which is inefficient. Consider fetching category data directly from the API or implementing server-side aggregation to avoid processing large datasets on the client.

Suggested change
const categoriesData = useMemo(() => {
if (!nodesData?.nodes) return []
const categoryMap = new Map<string, number>()
nodesData.nodes.forEach((node) => {
const category = node.category || 'Uncategorized'
categoryMap.set(category, (categoryMap.get(category) || 0) + 1)
})
return Array.from(categoryMap.entries())
.map(([category, count]) => ({ category, count }))
.sort((a, b) => b.count - a.count)
}, [nodesData?.nodes])
// Fetch pre-aggregated category data from the API
const { data: categoriesData } = useListCategoriesWithCounts()

Copilot uses AI. Check for mistakes.

const totalPages = Math.ceil((nodesData?.total || 0) / limit)
Copy link
Preview

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The pagination logic is incorrect. The totalPages calculation is based on total nodes, but the displayed data is categories. This will show incorrect pagination when the number of categories differs from the number of nodes.

Copilot uses AI. Check for mistakes.


return (
<div className="p-4">
<Breadcrumb className="py-4">
<Breadcrumb.Item href="/" icon={HiHome} className="dark">
{t('Home')}
</Breadcrumb.Item>
<Breadcrumb.Item href="/admin" className="dark">
{t('Admin Dashboard')}
</Breadcrumb.Item>
<Breadcrumb.Item href="#" className="dark">
{t('Category Management')}
</Breadcrumb.Item>
</Breadcrumb>

<h1 className="text-2xl font-bold text-gray-200 mb-6">
{t('Category Management')}
</h1>

<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div className="lg:col-span-1">
<AdminTreeNavigation />
</div>

<div className="lg:col-span-3">
<Card className="bg-gray-800 border-gray-700">
<div className="flex items-center justify-between mb-4">
<h2 className="text-xl font-semibold text-gray-200">
{t('Node Categories Overview')}
</h2>
<Badge color="info" icon={HiOutlineCollection}>
{categoriesData.length} {t('categories')}
</Badge>
</div>

<div className="space-y-4">
{categoriesData.map(({ category, count }) => (
<div
key={category}
className="flex items-center justify-between p-4 bg-gray-700 rounded-lg hover:bg-gray-600 transition-colors"
>
<div className="flex items-center space-x-3">
<HiOutlineCollection className="h-5 w-5 text-blue-400" />
<span className="text-lg font-medium text-gray-200">
{category}
</span>
</div>
<Badge color="purple" size="sm">
{count} {t('nodes')}
</Badge>
</div>
))}
</div>

{categoriesData.length === 0 && (
<div className="text-center py-8">
<HiOutlineCollection className="h-12 w-12 text-gray-400 mx-auto mb-4" />
<p className="text-gray-400">
{t('No categories found')}
</p>
</div>
)}

{totalPages > 1 && (
<div className="py-8">
<CustomPagination
currentPage={page || 1}
totalPages={totalPages}
onPageChange={setPage}
/>
</div>
)}
</Card>
</div>
</div>
</div>
)
}
8 changes: 7 additions & 1 deletion pages/admin/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,6 @@ function AdminDashboard() {
<h1 className="text-2xl font-bold text-gray-200 mb-6">
{t('Admin Dashboard')}
</h1>

<div className="grid grid-cols-1 lg:grid-cols-4 gap-6">
<div className="lg:col-span-1">
<AdminTreeNavigation />
Expand Down Expand Up @@ -72,6 +71,13 @@ function AdminDashboard() {
<HiOutlineCollection className="h-8 w-8 mr-3" />
<span>{t('Manage All Nodes')}</span>
</Link>
<Link
href="/admin/categories"
Copy link
Preview

Copilot AI Aug 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The href path '/admin/categories' is inconsistent with the file path 'pages/admin/categories.tsx'. This will result in a 404 error when users click the link.

Suggested change
href="/admin/categories"
href="/admin/Categories"

Copilot uses AI. Check for mistakes.

className="bg-indigo-600 hover:bg-indigo-700 text-white rounded-lg p-4 flex items-center transition-colors"
>
<HiOutlineCollection className="h-8 w-8 mr-3" />
<span>{t('Category Management')}</span>
</Link>
</div>
</div>
</div>
Expand Down
Loading