Skip to content

feat: Add plugin catalog to AdminCP System page - #880

Merged
aXenDeveloper merged 6 commits into
canaryfrom
feat/installed-plugins-catalog
Oct 15, 2026
Merged

aXenDeveloper merged 6 commits into
canaryfrom
feat/installed-plugins-catalog

Conversation

@aXenDeveloper

Copy link
Copy Markdown
Owner

Description

What?

Adds a new AdminCP → System → Plugins page that displays all installed plugins with their metadata (title, description, version, icon) read from each plugin's package.json at build time.

Key additions:

  • Plugin metadata generation: vitnode build and vitnode dev now generate src/plugin-metadata.gen.ts and src/plugin-icon.gen.ts from each plugin's package.json, reading the vitnode.title, vitnode.icon, description, and version fields
  • Plugin catalog UI: New admin page showing installed plugins with icons, titles, versions, descriptions, and search functionality
  • Version mismatch detection: Displays warnings when API and frontend versions of a plugin differ
  • Icon inlining: Plugin icons are inlined as data URLs (max 32KB) so they travel with the package
  • API endpoint: New /admin/plugins route that lists all installed plugins with their registration status on both API and frontend sides

Why?

Administrators need visibility into what plugins are installed and their versions without manually checking package.json files. This provides a centralized catalog view similar to package managers, making plugin management more transparent and user-friendly.

The metadata is generated at build time from package.json, ensuring the catalog always reflects what's actually installed without requiring manual version updates.

Test Plan

  • Added comprehensive unit tests for metadata generation (generate.test.ts)
  • Added tests for plugin search and filtering (plugin-search.test.ts)
  • Added tests for the plugins content component (plugins-content.test.tsx)
  • Added tests for the installed plugins registry builder (installed-plugins.test.ts)
  • Added tests for the plugin inventory generation (generate.test.ts)
  • Added tests for the admin plugins API route (list.route.test.ts)
  • Added integration tests for metadata file watching (watch-plugin-metadata.test.ts)
  • All existing tests pass with the new plugin metadata validation in buildPlugin and buildApiPlugin

https://claude.ai/code/session_01SqJtNifFvW63sCoYVKgJ7m

Generate plugin metadata (title, description, version, icon) from each
plugin's package.json during `vitnode build` and `vitnode dev`, register it
with buildPlugin/buildApiPlugin, and list every configured plugin once in
AdminCP → System → Plugins with the core version shown separately.

- src/plugin-metadata.gen.ts and src/plugin-icon.gen.ts per plugin, icons
  validated at build time and inlined as data URLs
- web build writes src/plugin-inventory.gen.ts, passed to the API as
  buildApiConfig({ frontendPlugins }) for frontend-only plugins and
  frontend/API version mismatch warnings
- GET /api/@vitnode/core/admin/plugins guarded by plugins.can_view
- Blog, Forum, Example and the plugin scaffold adopt the metadata
- docs: dev/plugins/catalog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqJtNifFvW63sCoYVKgJ7m
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T18:55:07.541521Z 7b14c20 New commits
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

React Doctor found 3 new issues in 2 files · 3 warnings · score 67 / 100 (Needs work) · 0 fixed · vs canary

3 warnings

scripts/cli/builder/compiler-build.ts

  • ⚠️ L115 await inside a loop async-await-in-loop

scripts/write-plugin-manifest.ts

  • ⚠️ L87 Array lookup inside a loop js-set-map-lookups
  • ⚠️ L90 Array lookup inside a loop js-set-map-lookups

Reviewed by React Doctor for commit 7b14c20. See inline comments for fixes.

@aXenDeveloper aXenDeveloper changed the title Add plugin catalog to AdminCP System page feat: Add plugin catalog to AdminCP System page Oct 15, 2026
@github-actions github-actions Bot added the 💡 Feature A new feature label Oct 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5fb4bbb344

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +104 to +106
description: optionalText(manifest.description, "description", source),
id,
title: title ?? id,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Return translation keys for plugin catalog copy

When the AdminCP uses a non-English locale, these literal package.json title and description values are returned by GET /admin/plugins and rendered directly by PluginsContent, so plugin copy remains in the manifest language. Store locale message keys in the metadata/API response and resolve them with use-intl in the web UI, as required for plugin-registered titles and descriptions.

AGENTS.md reference: AGENTS.md:L62-L62

Useful? React with 👍 / 👎.

Comment on lines +164 to +166
} catch (error) {
onError(error);
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Watch a newly declared icon after validation fails

If package.json is changed to reference an icon path before that file is created and the poller observes the manifest first, writePluginMetadata() throws before returning the new input list, and this catch leaves only the manifest and any old icon watched. Creating the new icon afterward therefore emits no event, so metadata remains stale until the manifest is touched again or vitnode dev is restarted; retain/watch the newly declared candidate path even on this missing-file error.

Useful? React with 👍 / 👎.

…ata.ts with Lucide icons

package.json keeps only its standard fields. A plugin's title and Lucide
icon name live in a hand-written src/plugin-metadata.ts, which the
generated src/plugin-metadata.gen.ts imports next to the name, version and
description read from package.json.

- icons are Lucide names, validated at registration and returned by the
  API, so API-only plugins show their own icon too
- drop plugin-icon.gen.ts, SVG assets, data URLs and icon file checks
- DynamicIcon gains a `missing` fallback for unknown icon names
- scaffold writes src/plugin-metadata.ts; docs and tests updated

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01SqJtNifFvW63sCoYVKgJ7m

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a290d82696

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +101 to +103
const [searchParams, setSearchParams] = React.useState(
() => new URLSearchParams(),
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Synchronize the catalog table with the route query string

When the catalog has enough plugins for multiple pages, this state always starts empty and navigation only updates component-local state. Consequently, the pagination links' ?page=... URLs do not reproduce the displayed page when opened in a new tab or refreshed, and browser Back/Forward cannot restore pagination or search state. Initialize from and navigate through the route's actual search parameters rather than maintaining a disconnected URLSearchParams instance.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5095042c02

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 33 to +36
<SecuritySection />
<DevelopersSection />
<PricingSection />
<FaqSection />
<FaqSection /> */}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Restore the omitted homepage sections

This comments out nearly the entire production homepage—outcomes, features, plugins, showcase, community, security, pricing, FAQ, and the final CTA—leaving only the hero, notice, and integrations section. Because this plugin-catalog change does not replace or relocate those sections, every visit to the home route loses the site's primary marketing and navigation content; restore the sections or lazy-load them rather than removing them from the render tree.

Useful? React with 👍 / 👎.

const { container } = renderPlugins({ plugins: [plugin({ icon: null })] });

expect(screen.getByText("Blog")).toBeTruthy();
expect(container.querySelector("svg.lucide-puzzle")).not.toBeNull();

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Test the default icon without relying on its CSS class

This assertion identifies the fallback through Lucide's decorative lucide-puzzle class, so a harmless icon-library class-name change can fail the test even when the same default icon is rendered. Assert observable DOM output or an accessible/click behavior instead of a CSS selector, as required for UI component tests.

AGENTS.md reference: AGENTS.md:L109-L110

Useful? React with 👍 / 👎.

write("package.json", JSON.stringify({ ...BLOG, version: "2.0.0" }));
await vi.waitFor(
() => {
expect(read(PLUGIN_METADATA_PATH)).toContain('"2.0.0"');

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Replace generated-source substring checks with behavior checks

This test reads the generated TypeScript module and searches its source for a quoted version literal (and repeats the same pattern in the recovery case), coupling the regression test to formatting rather than the exported metadata behavior. Import/evaluate the generated module and assert its exported version so generator refactors do not break the test while behavior remains correct.

AGENTS.md reference: AGENTS.md:L113-L119

Useful? React with 👍 / 👎.

Comment on lines +20 to +25
isDataScript(tag) && typeof tag.children === "string" ? (
<script
{...tag.attrs}
// oxlint-disable-next-line @eslint-react/dom-no-dangerously-set-innerhtml
dangerouslySetInnerHTML={{ __html: tag.children }}
suppressHydrationWarning

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Forward the router nonce to data scripts

When a host configures router.options.ssr.nonce for a nonce-based CSP, this production-only branch receives the nonce but drops it while manually rendering data scripts such as the JSON-LD emitted by apps/web/src/site/marketing/metadata.ts. Inline script elements must match the policy's nonce, so these router-managed tags can be rejected under a strict script-src; pass the nonce through just as the Asset branch does. CSP Level 3

Useful? React with 👍 / 👎.

- Introduced `write-plugin-manifest.ts` to handle the generation of plugin manifests from `package.json` and `src/plugin-info.ts`.
- Implemented `bindApiPlugin` and `bindContentPlugin` to facilitate the binding of API and content modules to plugins.
- Created tests for manifest generation to ensure correctness and robustness.
- Added manifest and plugin info files for the core and example plugins, enhancing plugin structure and metadata.
- Updated existing plugins (blog, example, forum) to utilize the new manifest generation and binding features.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: e6da4ed198

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +35 to +39
for (const src of entries) {
const script = document.createElement("script");
script.type = "module";
script.src = src;
document.body.append(script);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Preserve the nonce on deferred module scripts

In production, when a host uses router.options.ssr.nonce with a nonce-only script-src policy, the router-generated module entry initially carries that nonce, but this code reduces it to its src and creates a new <script> without a nonce. Unless the module URL is separately allowed or the policy uses strict-dynamic, the browser blocks the client entry and the application never hydrates; propagate the router nonce to each dynamically created module script.

Useful? React with 👍 / 👎.

Comment on lines 110 to 112
export const LogoVitNodeBrand = () => (
<>
<LogoVitNode
className="hidden w-34 sm:block"
idPrefix="vitnode-brand-wide"
/>
<LogoVitNode
className="size-8 sm:hidden"
idPrefix="vitnode-brand-compact"
small
/>
</>
<LogoVitNode className="w-34" idPrefix="vitnode-brand" />
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Keep the compact logo on small screens

On narrow mobile viewports, this always renders the 136px wordmark inside a non-shrinking header link alongside the non-shrinking search, notifications, and user controls. The removed responsive pair used the 32px mark below sm; without it the header can overflow or collide at small widths, so retain the compact mobile variant and show the wordmark only from sm upward.

AGENTS.md reference: AGENTS.md:L65-L67

Useful? React with 👍 / 👎.

Comment on lines +77 to +78
export const PluginsContent = ({ data }: PluginsContentProps) => {
const t = useTranslations("admin.system.plugins");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Render the catalog metadata returned by the API

The component only consumes data.plugins, leaving both data.core.version and data.frontendInventory unused. Consequently the core version is absent from the catalog, and in split/API-only deployments where frontendInventory is false the page silently presents an API-only list as the complete installed-plugin list; render the core version and the incomplete-inventory notice before the table.

Useful? React with 👍 / 👎.

…ins-catalog

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

const watch = (next: readonly string[]) => {
for (const file of watched) {
if (!next.includes(file)) unwatchFile(file, listener);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/js-set-map-lookups (warning)

This scales poorly because array.includes() inside a loop scans the whole list every time. Use a Set for constant-time lookups.

Fix → Use a Set or Map when you check for the same items over and over. Array.includes/find scans the whole list each time

Docs

if (!next.includes(file)) unwatchFile(file, listener);
}
for (const file of next) {
if (!watched.includes(file)) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

React Doctor · react-doctor/js-set-map-lookups (warning)

This scales poorly because array.includes() inside a loop scans the whole list every time. Use a Set for constant-time lookups.

Fix → Use a Set or Map when you check for the same items over and over. Array.includes/find scans the whole list each time

Docs

@aXenDeveloper
aXenDeveloper merged commit 6513de7 into canary Oct 15, 2026
7 checks passed
@aXenDeveloper
aXenDeveloper deleted the feat/installed-plugins-catalog branch October 15, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💡 Feature A new feature

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants