Skip to content

feat: add PluginDetailPage component for detailed plugin information display#7896

Merged
Soulter merged 2 commits intomasterfrom
feat/plugin-view
Apr 29, 2026
Merged

feat: add PluginDetailPage component for detailed plugin information display#7896
Soulter merged 2 commits intomasterfrom
feat/plugin-view

Conversation

@Soulter
Copy link
Copy Markdown
Member

@Soulter Soulter commented Apr 29, 2026

refactor: remove extension preference storage management and related tests

chore: clean up useExtensionPage by removing unused preference storage logic

Modifications / 改动点

  • This is NOT a breaking change. / 这不是一个破坏性变更。

Screenshots or Test Results / 运行截图或测试结果


Checklist / 检查清单

  • 😊 If there are new features added in the PR, I have discussed it with the authors through issues/emails, etc.
    / 如果 PR 中有新加入的功能,已经通过 Issue / 邮件等方式和作者讨论过。

  • 👀 My changes have been well-tested, and "Verification Steps" and "Screenshots" have been provided above.
    / 我的更改经过了良好的测试,并已在上方提供了“验证步骤”和“运行截图”

  • 🤓 I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations in requirements.txt and pyproject.toml.
    / 我确保没有引入新依赖库,或者引入了新依赖库的同时将其添加到 requirements.txtpyproject.toml 文件相应位置。

  • 😮 My changes do not introduce malicious code.
    / 我的更改没有引入恶意代码。

Summary by Sourcery

Introduce a dedicated plugin detail view for installed extensions and simplify the installed plugins list UI by removing pinning, list-view, and preference-based filtering/sorting features.

New Features:

  • Add a PluginDetailPage view that shows detailed information, handlers, and documentation for a selected installed plugin, including rendered README content.
  • Add a dedicated extension details route that allows deep-linking to an individual plugin from the installed plugins list.

Enhancements:

  • Redesign ExtensionCard to be a simpler, clickable card focused on high-level plugin information and activation, with truncated descriptions and improved layout.
  • Adjust default chat provider configuration to collapse the prompt prefix setting in the UI for a cleaner configuration experience.
  • Add an inline floating action button to trigger 'update all' for installed plugins and surface a toast when no updates are available.
  • Extend the icon subset with new symbols used by the plugin detail view and remove unused icons tied to deprecated list/pin controls.

Chores:

  • Remove localStorage-based extension preference storage (view mode, reserved visibility, sorting, pinning) and related state, UI controls, and tests from the extensions page.

…display

refactor: remove extension preference storage management and related tests

chore: clean up useExtensionPage by removing unused preference storage logic
@dosubot dosubot Bot added size:XXL This PR changes 1000+ lines, ignoring generated files. area:webui The bug / feature is about webui(dashboard) of astrbot. feature:plugin The bug / feature is about AstrBot plugin system. labels Apr 29, 2026
Copy link
Copy Markdown
Contributor

@sourcery-ai sourcery-ai Bot left a comment

Choose a reason for hiding this comment

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

Hey - I've found 1 issue, and left some high level feedback:

  • In InstalledPluginsTab and ExtensionCard the navigation is wired via a click on the whole ExtensionCard, but the action buttons inside the card (configure, uninstall, etc.) don’t stop event propagation, so clicking them will also open the detail page; consider moving navigation to a dedicated control or adding @click.stop on those action buttons.
  • This change removes several installed-plugins UX features (list view, status filter, show/hide system plugins, pinning, sort options, etc.); if those behaviours are still needed, it might be better to keep them and integrate the detail view rather than dropping them entirely.
  • PluginDetailPage introduces a direct axios call for /api/plugin/readme; if you already have a shared API/client helper or error-handling pattern elsewhere in the dashboard, consider routing this request through that to keep networking concerns consistent.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In InstalledPluginsTab and ExtensionCard the navigation is wired via a click on the whole ExtensionCard, but the action buttons inside the card (configure, uninstall, etc.) don’t stop event propagation, so clicking them will also open the detail page; consider moving navigation to a dedicated control or adding `@click.stop` on those action buttons.
- This change removes several installed-plugins UX features (list view, status filter, show/hide system plugins, pinning, sort options, etc.); if those behaviours are still needed, it might be better to keep them and integrate the detail view rather than dropping them entirely.
- PluginDetailPage introduces a direct `axios` call for `/api/plugin/readme`; if you already have a shared API/client helper or error-handling pattern elsewhere in the dashboard, consider routing this request through that to keep networking concerns consistent.

## Individual Comments

### Comment 1
<location path="dashboard/src/views/extension/PluginDetailPage.vue" line_range="308-311" />
<code_context>
+    });
+};
+
+const openExternal = (url) => {
+  if (!url) return;
+  window.open(url, "_blank", "noopener,noreferrer");
+};
+
</code_context>
<issue_to_address>
**🚨 suggestion (security):** Validate external URLs before opening them in a new tab

Plugin metadata may be untrusted, and `openExternal` forwards any string to `window.open`. Even with `noopener,noreferrer`, this still allows `javascript:` or other non-HTTP(S) URLs. Please restrict `window.open` calls to an allowlist of safe protocols (e.g., only `http:`/`https:`).

```suggestion
const openExternal = (url) => {
  if (!url) return;

  let parsed: URL;
  try {
    // Support both absolute and relative URLs while enforcing protocol
    parsed = new URL(url, window.location.href);
  } catch {
    // Invalid URL format
    return;
  }

  const protocol = parsed.protocol.toLowerCase();
  const allowedProtocols = new Set(["http:", "https:"]);

  if (!allowedProtocols.has(protocol)) {
    return;
  }

  window.open(parsed.toString(), "_blank", "noopener,noreferrer");
};
```
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +308 to +311
const openExternal = (url) => {
if (!url) return;
window.open(url, "_blank", "noopener,noreferrer");
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🚨 suggestion (security): Validate external URLs before opening them in a new tab

Plugin metadata may be untrusted, and openExternal forwards any string to window.open. Even with noopener,noreferrer, this still allows javascript: or other non-HTTP(S) URLs. Please restrict window.open calls to an allowlist of safe protocols (e.g., only http:/https:).

Suggested change
const openExternal = (url) => {
if (!url) return;
window.open(url, "_blank", "noopener,noreferrer");
};
const openExternal = (url) => {
if (!url) return;
let parsed: URL;
try {
// Support both absolute and relative URLs while enforcing protocol
parsed = new URL(url, window.location.href);
} catch {
// Invalid URL format
return;
}
const protocol = parsed.protocol.toLowerCase();
const allowedProtocols = new Set(["http:", "https:"]);
if (!allowedProtocols.has(protocol)) {
return;
}
window.open(parsed.toString(), "_blank", "noopener,noreferrer");
};

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a dedicated plugin detail page and refactors the extension management interface. Key changes include the removal of pinning, drag-and-drop, and list-view functionalities in favor of a more detailed view that displays plugin handlers and documentation. The extension card was also updated with a new layout and improved metadata display. Feedback for the new PluginDetailPage.vue component suggests refining the Markdown sanitization configuration to follow the principle of least privilege and removing redundant scroll event listeners on both window and document to optimize performance.

Comment on lines +317 to +334
const renderMarkdown = (source) => {
const normalizedSource = String(source || "")
.replace(/[“”]/g, '"')
.replace(/[‘’]/g, "'");
const rawHtml = markdown.render(normalizedSource);
const cleanHtml = DOMPurify.sanitize(rawHtml, MARKDOWN_SANITIZE_OPTIONS);
const container = document.createElement("div");
container.innerHTML = cleanHtml;

container.querySelectorAll("a").forEach((link) => {
const href = link.getAttribute("href") || "";
if (href.startsWith("http") || href.startsWith("//")) {
link.setAttribute("target", "_blank");
link.setAttribute("rel", "noopener noreferrer");
}
});

return container.innerHTML;
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

security-medium medium

The renderMarkdown function uses DOMPurify.sanitize with a broad set of allowed tags and attributes. While this is generally safe, it is recommended to explicitly define the allowed tags and attributes to adhere to the principle of least privilege, especially when rendering user-provided content.

Comment on lines +387 to +401
onMounted(() => {
updateHeaderStuckState();
window.addEventListener("scroll", updateHeaderStuckState, { passive: true });
document.addEventListener("scroll", updateHeaderStuckState, {
capture: true,
passive: true,
});
});

onBeforeUnmount(() => {
window.removeEventListener("scroll", updateHeaderStuckState);
document.removeEventListener("scroll", updateHeaderStuckState, {
capture: true,
});
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The scroll event listener is added to both window and document. This is redundant and can lead to performance issues. Attaching the listener to window is sufficient for tracking scroll position.

onMounted(() => { updateHeaderStuckState(); window.addEventListener('scroll', updateHeaderStuckState, { passive: true }); }); onBeforeUnmount(() => { window.removeEventListener('scroll', updateHeaderStuckState); });

@Soulter Soulter merged commit 7c185f8 into master Apr 29, 2026
20 checks passed
@Soulter Soulter deleted the feat/plugin-view branch April 29, 2026 14:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:webui The bug / feature is about webui(dashboard) of astrbot. feature:plugin The bug / feature is about AstrBot plugin system. size:XXL This PR changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant