-
Notifications
You must be signed in to change notification settings - Fork 9
feat: enhance unclaimed nodes modal with GitHub repository info fetching #204
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
cc10df9
34451f1
fe694aa
e5b3cce
5133d2d
8d38aff
1bb3e62
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,6 @@ | ||
| { | ||
| "lockfileVersion": 1, | ||
| "configVersion": 0, | ||
| "workspaces": { | ||
| "": { | ||
| "name": "comfy-org-registry-web", | ||
|
|
@@ -69,6 +70,7 @@ | |
| "react-use-cookie": "^1.6.1", | ||
| "sflow": "^1.24.5", | ||
| "sharp": "^0.34.3", | ||
| "smol-toml": "^1.4.2", | ||
| "styled-jsx": "^5.1.7", | ||
| "yaml": "^2.8.1", | ||
| "zod": "^3.25.76", | ||
|
|
@@ -2531,6 +2533,8 @@ | |
|
|
||
| "slice-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w=="], | ||
|
|
||
| "smol-toml": ["[email protected]", "", {}, "sha512-rInDH6lCNiEyn3+hH8KVGFdbjc099j47+OSgbMrfDYX1CmXLfdKd7qi6IfcWj2wFxvSVkuI46M+wPGYfEOEj6g=="], | ||
|
|
||
| "source-map": ["[email protected]", "", {}, "sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ=="], | ||
|
|
||
| "source-map-js": ["[email protected]", "", {}, "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA=="], | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,121 @@ | ||
| import type { Meta, StoryObj } from '@storybook/nextjs-vite' | ||
| import { QueryClient, QueryClientProvider } from '@tanstack/react-query' | ||
| import { useState } from 'react' | ||
| import { AdminCreateNodeFormModal } from './AdminCreateNodeFormModal' | ||
|
|
||
| const meta = { | ||
| title: 'Components/Nodes/AdminCreateNodeFormModal', | ||
| component: AdminCreateNodeFormModal, | ||
| parameters: { | ||
| layout: 'centered', | ||
| docs: { | ||
| description: { | ||
| component: ` | ||
| The AdminCreateNodeFormModal is used by administrators to add unclaimed nodes to the registry. | ||
| It features a repository URL input at the top with a "Fetch Info" button that automatically | ||
| populates form fields from the pyproject.toml file in GitHub repositories. | ||
|
|
||
| ## Features | ||
| - Repository URL input with auto-fetch functionality | ||
| - Form validation using Zod schema | ||
| - Duplicate node detection | ||
| - Integration with React Hook Form | ||
| - Toast notifications for success/error states | ||
| `, | ||
| }, | ||
| }, | ||
| }, | ||
| decorators: [ | ||
| (Story) => { | ||
| const queryClient = new QueryClient({ | ||
| defaultOptions: { | ||
| queries: { | ||
| retry: false, | ||
| }, | ||
| }, | ||
| }) | ||
| return ( | ||
| <QueryClientProvider client={queryClient}> | ||
| <div style={{ minHeight: '600px' }}> | ||
| <Story /> | ||
| </div> | ||
| </QueryClientProvider> | ||
| ) | ||
| }, | ||
| ], | ||
| tags: ['autodocs'], | ||
| } satisfies Meta<typeof AdminCreateNodeFormModal> | ||
|
|
||
| export default meta | ||
| type Story = StoryObj<typeof meta> | ||
|
|
||
| // Story wrapper component to handle modal state | ||
| function ModalWrapper( | ||
| args: React.ComponentProps<typeof AdminCreateNodeFormModal> | ||
| ) { | ||
| const [open, setOpen] = useState(true) | ||
|
|
||
| return ( | ||
| <AdminCreateNodeFormModal | ||
| {...args} | ||
| open={open} | ||
| onClose={() => { | ||
| console.log('Modal closed') | ||
| setOpen(false) | ||
| // Reopen after a delay for demo purposes | ||
| setTimeout(() => setOpen(true), 1000) | ||
| }} | ||
| /> | ||
| ) | ||
| } | ||
|
|
||
| export const Default: Story = { | ||
| render: (args) => <ModalWrapper {...args} />, | ||
| args: { | ||
| open: true, | ||
| onClose: () => console.log('onClose'), | ||
| }, | ||
| } | ||
|
|
||
| export const WithGitHubRepo: Story = { | ||
| render: (args) => <ModalWrapper {...args} />, | ||
| args: { | ||
| open: true, | ||
| onClose: () => console.log('onClose'), | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: ` | ||
| This story demonstrates the modal with a GitHub repository URL pre-filled. | ||
| In a real scenario, clicking "Fetch Info" would populate the form fields | ||
| with data from the repository's pyproject.toml file. | ||
| `, | ||
| }, | ||
| }, | ||
| }, | ||
| play: async ({ canvasElement }) => { | ||
| // You could add play interactions here to demonstrate the functionality | ||
| // For example, filling in the repository field and clicking fetch | ||
| }, | ||
| } | ||
|
|
||
| export const Closed: Story = { | ||
| render: () => ( | ||
| <AdminCreateNodeFormModal | ||
| open={false} | ||
| onClose={() => console.log('onClose')} | ||
| /> | ||
| ), | ||
| args: { | ||
| open: false, | ||
| onClose: () => console.log('onClose'), | ||
| }, | ||
| parameters: { | ||
| docs: { | ||
| description: { | ||
| story: 'The modal in its closed state.', | ||
| }, | ||
| }, | ||
| }, | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||
|---|---|---|---|---|---|---|
|
|
@@ -3,9 +3,11 @@ import { useQueryClient } from '@tanstack/react-query' | |||||
| import { AxiosError } from 'axios' | ||||||
| import { Button, Label, Modal, Textarea, TextInput } from 'flowbite-react' | ||||||
| import { useRouter } from 'next/router' | ||||||
| import { useState } from 'react' | ||||||
| import { useForm } from 'react-hook-form' | ||||||
| import { HiPlus } from 'react-icons/hi' | ||||||
| import { HiDownload, HiPlus } from 'react-icons/hi' | ||||||
| import { toast } from 'react-toastify' | ||||||
| import TOML from 'smol-toml' | ||||||
| import { customThemeTModal } from 'utils/comfyTheme' | ||||||
| import { z } from 'zod' | ||||||
| import { | ||||||
|
|
@@ -47,6 +49,104 @@ const adminCreateNodeDefaultValues: Partial< | |||||
| license: '{file="LICENSE"}', | ||||||
| } | ||||||
|
|
||||||
| interface PyProjectData { | ||||||
| name?: string | ||||||
| description?: string | ||||||
| author?: string | ||||||
| license?: string | ||||||
| } | ||||||
|
|
||||||
| async function fetchGitHubRepoInfo( | ||||||
| repoUrl: string | ||||||
| ): Promise<PyProjectData | null> { | ||||||
| try { | ||||||
| // Parse GitHub URL to extract owner and repo | ||||||
| const urlMatch = repoUrl.match( | ||||||
| /github\.com\/([^\/]+)\/([^\/]+?)(?:\.git)?(?:\/|$)/ | ||||||
| ) | ||||||
| if (!urlMatch) { | ||||||
| throw new Error('Invalid GitHub URL format') | ||||||
| } | ||||||
|
|
||||||
| const [, owner, repo] = urlMatch | ||||||
| const apiUrl = `https://api.github.com/repos/${owner}/${repo}/contents/pyproject.toml` | ||||||
|
|
||||||
| const response = await fetch(apiUrl) | ||||||
| if (!response.ok) { | ||||||
| if (response.status === 404) { | ||||||
| throw new Error('pyproject.toml not found in repository') | ||||||
| } | ||||||
| throw new Error(`GitHub API error: ${response.statusText}`) | ||||||
| } | ||||||
|
|
||||||
| const data = await response.json() | ||||||
| // Validate encoding and base64 content | ||||||
| if (data.encoding !== 'base64') { | ||||||
| throw new Error( | ||||||
| `Unexpected encoding for pyproject.toml: ${data.encoding}` | ||||||
| ) | ||||||
| } | ||||||
| // Basic base64 validation regex (allows padding) | ||||||
| const base64Regex = | ||||||
| /^(?:[A-Za-z0-9+\/]{4})*(?:[A-Za-z0-9+\/]{2}==|[A-Za-z0-9+\/]{3}=)?$/ | ||||||
| if (!base64Regex.test(data.content)) { | ||||||
| throw new Error('Invalid base64 content in pyproject.toml') | ||||||
| } | ||||||
|
Comment on lines
+90
to
+94
|
||||||
| const content = atob(data.content) | ||||||
|
||||||
| const content = atob(data.content) | |
| const content = Buffer.from(data.content, 'base64').toString('utf-8') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Missing rate limit handling for GitHub API requests. The GitHub API has rate limits (60 requests/hour for unauthenticated requests, 5000/hour for authenticated). Consider adding error handling for HTTP 403 responses with rate limit information, or implementing request throttling to prevent users from hitting rate limits.