Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
31c8855
fix(57-feat-req-admin-category-dashboard-admincategory): draft
snomiao May 7, 2025
c82d135
feat: implement admin category dashboard
snomiao Aug 9, 2025
7539fd0
feat: rename category.tsx to categories.tsx and implement server-side…
snomiao Aug 10, 2025
71e18cd
format: Apply prettier --fix changes
snomiao Aug 10, 2025
f9f8b79
refactor: use usePage hook for admin categories pagination
snomiao Aug 10, 2025
eda5382
feat: migrate from categories to tags system
snomiao Sep 30, 2025
4db877a
fix: resolve ESLint warning in claim-my-node useEffect dependencies
snomiao Oct 1, 2025
2b44027
Merge main branch into category dashboard feature branch
snomiao Oct 15, 2025
ec3ba4f
format: Apply prettier --fix changes
snomiao Oct 15, 2025
b1ada46
fix: Show nodes with tags in table instead of uncategorized badge
snomiao Oct 16, 2025
547ee68
format: Apply prettier --fix changes
snomiao Oct 16, 2025
5401b7e
feat: Add tag editing functionality to admin tags page
snomiao Oct 16, 2025
af0d5c2
format: Apply prettier --fix changes
snomiao Oct 16, 2025
f112b5d
fix: remove href from current page breadcrumb items
snomiao Oct 25, 2025
7466472
Merge remote-tracking branch 'origin/main' into 57-feat-req-admin-cat…
snomiao Oct 25, 2025
172d8b8
format: Apply prettier --fix changes
snomiao Oct 25, 2025
8d78ef3
Merge branch 'main' into 57-feat-req-admin-category-dashboard-adminca…
snomiao Oct 25, 2025
b03776c
feat: use admin endpoint for editing admin_tags
snomiao Oct 25, 2025
a993612
format: Apply prettier --fix changes
snomiao Oct 25, 2025
1588a27
Merge branch 'main' into 57-feat-req-admin-category-dashboard-adminca…
snomiao Oct 25, 2025
517306e
format: Apply prettier --fix changes
snomiao Oct 25, 2025
5bb25aa
feat: enable publishers to edit node tags
snomiao Oct 25, 2025
d8171e0
format: Apply prettier --fix changes
snomiao Oct 25, 2025
526a260
fix: admin tags page now always uses admin endpoint
snomiao Oct 26, 2025
ffbfa35
format: Apply prettier --fix changes
snomiao Oct 26, 2025
be5a759
fix: rename CategoriesPage to TagsPage for consistency
snomiao Oct 26, 2025
8425cd3
format: Apply prettier --fix changes
snomiao Oct 26, 2025
6290d24
format: Apply prettier and biome formatting fixes
snomiao Oct 26, 2025
a0d8af0
Merge branch 'main' into feature/admin-tags
snomiao Oct 26, 2025
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
135 changes: 135 additions & 0 deletions pages/admin/categories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import withAdmin from '@/components/common/HOC/authAdmin'
import AdminTreeNavigation from '@/components/admin/AdminTreeNavigation'
import { CustomPagination } from '@/components/common/CustomPagination'
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 { useRouter } from 'next/router'
import React, { useMemo, useState } from 'react'

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

// Handle page from URL
React.useEffect(() => {
if (router.query.page) {
setPage(parseInt(router.query.page as string))
}
}, [router.query.page])

const { data: nodesData } = useListAllNodes({
page: page,
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])

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

const handlePageChange = (newPage: number) => {
setPage(newPage)
router.push(
{
pathname: router.pathname,
query: { ...router.query, page: newPage },
},
undefined,
{ shallow: true }
)
}

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}
totalPages={totalPages}
onPageChange={handlePageChange}
/>
</div>
)}
</Card>
</div>
</div>
</div>
)
}
Loading