diff --git a/src/components/lists/DynamicList.tsx b/src/components/lists/DynamicList.tsx index 6ab2755..eb8e70f 100644 --- a/src/components/lists/DynamicList.tsx +++ b/src/components/lists/DynamicList.tsx @@ -46,6 +46,7 @@ import { AlertDialogTitle, } from '@/components/ui/alert-dialog'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible'; +import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; import { ObjectPicker } from '@/components/common/ObjectPicker'; import { EnterpriseUpsell } from '@/components/common/EnterpriseUpsell'; import { toast } from '@/hooks/use-toast'; @@ -59,7 +60,7 @@ import { useAuthStore } from '@/stores/authStore'; import { useAccountStore } from '@/stores/accountStore'; import { useCacheStore } from '@/stores/cacheStore'; import { resolveObject, resolveSchema, resolveList, getDisplayProperty } from '@/lib/schemaResolver'; -import { jmapGetBatched, jmapQueryAll, jmapQueryAndGet, jmapSet, getAccountId } from '@/services/jmap/client'; +import { jmapGetBatched, jmapQueryAll, jmapQueryAndGet, jmapQueryAllAndGet, jmapSet, getAccountId } from '@/services/jmap/client'; import type { Schema, Field, MassAction, ItemAction, Filter as FilterDef } from '@/types/schema'; import type { JmapSetResponse, JmapSetError } from '@/types/jmap'; @@ -329,6 +330,12 @@ interface ConfirmAction { onConfirm: () => void; } + +function isActiveWebApplication(item: Record): boolean { + const prefixes = item.urlPrefix; + if (!Array.isArray(prefixes)) return false; + return prefixes.includes('/admin') || prefixes.includes('/account'); +} interface DynamicListProps { viewName: string; } @@ -383,6 +390,36 @@ export function DynamicList({ viewName }: DynamicListProps) { const [sort, setSort] = useState(readUrlSort); const [confirmAction, setConfirmAction] = useState(null); + const [activeWebApp, setActiveWebApp] = useState | null>(null); + + const objectName = resolved?.obj.objectName; + const isWebApplications = objectName === 'x:Application'; + + useEffect(() => { + if (!isWebApplications || !schema) return; + let cancelled = false; + + void (async () => { + try { + const accountId = getAccountId('x:Application'); + const result = await jmapQueryAllAndGet( + 'x:Application', + accountId, + {}, + ['id', 'description', 'enabled', 'urlPrefix'], + ); + if (cancelled) return; + const active = result.list.find((item) => isActiveWebApplication(item)); + setActiveWebApp(active ?? null); + } catch (err) { + console.error('Failed to fetch active web application:', err); + } + })(); + + return () => { + cancelled = true; + }; + }, [isWebApplications, schema]); useResetOnChange(viewName, () => { setItems([]); @@ -400,7 +437,6 @@ export function DynamicList({ viewName }: DynamicListProps) { setSort(readUrlSort()); }); - const objectName = resolved?.obj.objectName; const buildFilter = useCallback((): Record => { return buildJmapFilter({ appliedFilters, @@ -1219,6 +1255,25 @@ export function DynamicList({ viewName }: DynamicListProps) { )} + {isWebApplications && activeWebApp && ( + + + + {t('webApplications.activeWebUI', 'Active WebUI')} + + + +

+ {String(activeWebApp.description ?? '-')} +

+
+ {t('webApplications.version', 'Version')}: + {__APP_VERSION__} +
+
+
+ )} +
{/* The scroll container must clip with the parent's inner radius (outer radius minus the 1px border), otherwise filled header rows @@ -1236,6 +1291,11 @@ export function DynamicList({ viewName }: DynamicListProps) { /> )} + {isWebApplications && ( + + {t('list.active', 'Active')} + + )} {displayColumns.map((col) => (
@@ -1255,7 +1315,7 @@ export function DynamicList({ viewName }: DynamicListProps) { {loading && items.length === 0 ? ( @@ -1264,7 +1324,7 @@ export function DynamicList({ viewName }: DynamicListProps) { ) : items.length === 0 ? ( {t('list.noResults', 'No results found')} @@ -1288,6 +1348,15 @@ export function DynamicList({ viewName }: DynamicListProps) { /> )} + {isWebApplications && ( + + {activeWebApp?.id === item.id ? ( + {t('list.active', 'Active')} + ) : ( + - + )} + + )} {displayColumns.map((col) => ( {renderCellValue( diff --git a/src/i18n/en.json b/src/i18n/en.json index 441e3d9..e612a0d 100644 --- a/src/i18n/en.json +++ b/src/i18n/en.json @@ -309,7 +309,8 @@ "showing": "Showing {{from}}-{{to}} of {{total}} {{name}}", "showingItems": "Showing {{count}} items", "sort": "Sort", - "unknownError": "Unknown error" + "unknownError": "Unknown error", + "active": "Active" }, "login": { "continue": "Continue", @@ -397,5 +398,9 @@ "failedToLoad": "Failed to load", "noGetResponse": "No get response", "objectNotFound": "Object not found" + }, + "webApplications": { + "activeWebUI": "Active WebUI", + "version": "Version" } }