diff --git a/src/app/api-keys/page.tsx b/src/app/api-keys/page.tsx
index 8010b36..2593f3f 100644
--- a/src/app/api-keys/page.tsx
+++ b/src/app/api-keys/page.tsx
@@ -2,6 +2,7 @@
import ConfirmActionDialog from "@/components/ConfirmActionDialog";
import GuardedPage from "@/components/GuardedPage";
+import IssueDetailsSummary from "@/components/IssueDetailsSummary";
import {
apiKeys as apiKeysApi,
type ApiKeyExpiryOption,
@@ -529,21 +530,11 @@ export default function ApiKeysPage() {
{error && (
-
+
)}
{loading ? (
diff --git a/src/app/apps/installed/page.tsx b/src/app/apps/installed/page.tsx
index 709b1e6..b2c6380 100644
--- a/src/app/apps/installed/page.tsx
+++ b/src/app/apps/installed/page.tsx
@@ -24,6 +24,7 @@ import {
} from "lucide-react";
import DashboardLayout from "@/components/DashboardLayout";
import ConfirmActionDialog from "@/components/ConfirmActionDialog";
+import IssueDetailsSummary from "@/components/IssueDetailsSummary";
import ToastViewport from "@/components/ToastViewport";
import {
AppInstall,
@@ -1043,19 +1044,11 @@ function InstallOperationsModal({
{install.error ? (
-
- {install.error}
-
+
) : null}
) : null}
@@ -1852,19 +1845,11 @@ export default function InstalledAppsPage() {
{install.error ? (
-
- {install.error}
-
+
) : null}
{error && (
-
+
)}
) : error ? (
-
+
) : appDetail ? (
<>
{runtimeNotice ? (
-
- Runtime stats are unavailable: {runtimeNotice}
-
+
) : null}
{actionError ? (
-
+
) : null}
) : null}
{error ? (
-
+
) : null}
{error && (
-
+
)}
diff --git a/src/app/servers/components/ServerConfigModal.tsx b/src/app/servers/components/ServerConfigModal.tsx
index b20ef51..ea25403 100644
--- a/src/app/servers/components/ServerConfigModal.tsx
+++ b/src/app/servers/components/ServerConfigModal.tsx
@@ -47,6 +47,7 @@ import ServerConfigOverviewPanel from "@/app/servers/components/server-config/Se
import ServerConfigServicesPanel from "@/app/servers/components/server-config/ServerConfigServicesPanel";
import ServerConfigUsersPanel from "@/app/servers/components/server-config/ServerConfigUsersPanel";
import ServerConfigWebServerPanel from "@/app/servers/components/server-config/ServerConfigWebServerPanel";
+import IssueDetailsSummary from "@/components/IssueDetailsSummary";
interface ServerConfigModalProps {
server: ServerType;
@@ -1098,18 +1099,11 @@ export default function ServerConfigModal({
) : null}
{error ? (
-
- {error}
-
+
) : null}
{snapshotLoadError ? (
@@ -1140,15 +1134,11 @@ export default function ServerConfigModal({
available, especially in the Actions tab.
-
- Latest error: {snapshotLoadError}
-
+
) : null}
diff --git a/src/app/users/page.tsx b/src/app/users/page.tsx
index 5bdd388..e1178ea 100644
--- a/src/app/users/page.tsx
+++ b/src/app/users/page.tsx
@@ -2,8 +2,8 @@
import ConfirmActionDialog from "@/components/ConfirmActionDialog";
import GuardedPage from "@/components/GuardedPage";
+import IssueDetailsSummary from "@/components/IssueDetailsSummary";
import ToastViewport from "@/components/ToastViewport";
-import { AlertCircle } from "lucide-react";
import { useCallback, useEffect, useMemo, useState } from "react";
import {
CreateUserInvitationBody,
@@ -383,21 +383,11 @@ export default function UsersPage() {
style={{ display: "flex", flexDirection: "column", gap: 16 }}
>
{error && (
-
+
)}
diff --git a/src/components/IssueDetailsSummary.tsx b/src/components/IssueDetailsSummary.tsx
new file mode 100644
index 0000000..60b7c76
--- /dev/null
+++ b/src/components/IssueDetailsSummary.tsx
@@ -0,0 +1,92 @@
+"use client";
+
+import { useState } from "react";
+import { createPortal } from "react-dom";
+import { AlertTriangle, X } from "lucide-react";
+
+type IssueDetailsSummaryProps = {
+ label: string;
+ message?: string;
+ notes?: string[];
+ description?: string;
+};
+
+export default function IssueDetailsSummary({
+ label,
+ message,
+ notes,
+ description = "Issue details returned by the current operation.",
+}: IssueDetailsSummaryProps) {
+ const [detailOpen, setDetailOpen] = useState(false);
+ const issueNotes = (notes?.length ? notes : message ? [message] : []).filter(
+ Boolean,
+ );
+ const issueCount = issueNotes.length || 1;
+
+ return (
+ <>
+
+
+ {detailOpen && typeof document !== "undefined"
+ ? createPortal(
+
+
+
+
+
+ {label} Issues
+
+
+ {description}
+
+
+
+
+
+ {issueNotes.map((note, index) => (
+
+ {note}
+
+ ))}
+
+
+
,
+ document.body,
+ )
+ : null}
+ >
+ );
+}