fix(web-applications): show Enabled column and active WebUI info card

This commit is contained in:
Steven RYDELL
2026-07-30 13:43:23 +02:00
parent 87cabf3886
commit bfdf8f4ba2
3 changed files with 47 additions and 11 deletions
+6
View File
@@ -28,3 +28,9 @@ scripts/
!README.md !README.md
!CHANGELOG.md !CHANGELOG.md
/SPEC-* /SPEC-*
# Tool-generated artifacts during agent sessions
.playwright-mcp/
outputs/
.vite_*.log
.tmp_*
*.py
+39 -10
View File
@@ -333,8 +333,22 @@ interface ConfirmAction {
function isActiveWebApplication(item: Record<string, unknown>): boolean { function isActiveWebApplication(item: Record<string, unknown>): boolean {
const prefixes = item.urlPrefix; const prefixes = item.urlPrefix;
if (!Array.isArray(prefixes)) return false; const values: string[] = [];
return prefixes.includes('/admin') || prefixes.includes('/account'); if (Array.isArray(prefixes)) {
values.push(...prefixes.map(String));
} else if (typeof prefixes === 'string') {
values.push(...prefixes.split(',').map((s) => s.trim()));
} else if (prefixes && typeof prefixes === 'object') {
// JMAP exposes urlPrefix as a set object such as { "/admin": true, "/account": true }
for (const [key, value] of Object.entries(prefixes)) {
if (value === true) {
values.push(key);
}
}
} else if (prefixes != null) {
values.push(String(prefixes));
}
return values.some((p) => p === '/admin' || p === '/account');
} }
interface DynamicListProps { interface DynamicListProps {
viewName: string; viewName: string;
@@ -362,22 +376,27 @@ export function DynamicList({ viewName }: DynamicListProps) {
}, [schema, viewName]); }, [schema, viewName]);
const objectName = resolved?.obj.objectName; const objectName = resolved?.obj.objectName;
const isWebApplications = objectName === 'x:Application'; const isWebApplications = viewName === 'x:Application' || objectName === 'x:Application';
const displayColumns = useMemo(() => { const displayColumns = useMemo(() => {
const columns = resolved?.list?.columns ?? []; const columns = resolved?.list?.columns ?? [];
if (!isWebApplications) return columns; if (!isWebApplications) return columns;
// For Web Applications, present Description first and Enabled second // For Web Applications, present Description first and Enabled second
// to match the layout of other tables such as Domains. // to match the layout of other tables such as Domains. If the schema
// does not expose an Enabled column, add a synthetic one.
const ordered = ['description', 'enabled']; const ordered = ['description', 'enabled'];
const rest = columns.filter((c) => !ordered.includes(c.name)); const rest = columns.filter((c) => !ordered.includes(c.name));
const descriptionCol = columns.find((c) => c.name === 'description'); const descriptionCol = columns.find((c) => c.name === 'description');
const enabledCol = columns.find((c) => c.name === 'enabled'); const enabledCol = columns.find((c) => c.name === 'enabled');
const result = []; const result: Array<{ name: string; label: string }> = [];
if (descriptionCol) result.push(descriptionCol); if (descriptionCol) result.push(descriptionCol);
if (enabledCol) result.push(enabledCol); if (enabledCol) {
result.push(enabledCol);
} else {
result.push({ name: 'enabled', label: t('webApplications.enabled', 'Enabled') });
}
result.push(...rest); result.push(...rest);
return result; return result;
}, [resolved?.list?.columns, isWebApplications]); }, [resolved?.list?.columns, isWebApplications]);
@@ -414,9 +433,9 @@ export function DynamicList({ viewName }: DynamicListProps) {
void (async () => { void (async () => {
try { try {
const accountId = getAccountId('x:Application'); const accountId = getAccountId(objectName ?? 'x:Application');
const result = await jmapQueryAllAndGet( const result = await jmapQueryAllAndGet(
'x:Application', objectName ?? 'x:Application',
accountId, accountId,
{}, {},
['id', 'description', 'enabled', 'urlPrefix'], ['id', 'description', 'enabled', 'urlPrefix'],
@@ -475,6 +494,9 @@ export function DynamicList({ viewName }: DynamicListProps) {
try { try {
const accountId = getAccountId(obj.objectName); const accountId = getAccountId(obj.objectName);
const properties = ['id', ...list.columns.map((c) => c.name)]; const properties = ['id', ...list.columns.map((c) => c.name)];
if (isWebApplications && !properties.includes('enabled')) {
properties.push('enabled');
}
const filter = buildFilter(); const filter = buildFilter();
const sortArr = buildSort(); const sortArr = buildSort();
@@ -1358,7 +1380,14 @@ export function DynamicList({ viewName }: DynamicListProps) {
)} )}
{displayColumns.map((col) => ( {displayColumns.map((col) => (
<td key={col.name} className="px-3 py-2"> <td key={col.name} className="px-3 py-2">
{renderCellValue( {isWebApplications && col.name === 'enabled' && !fields[col.name] ? (
item.enabled === true ? (
<Check className="h-4 w-4 text-green-600" />
) : (
<X className="h-4 w-4 text-red-500" />
)
) : (
renderCellValue(
item[col.name], item[col.name],
fields[col.name], fields[col.name],
col.name, col.name,
@@ -1366,7 +1395,7 @@ export function DynamicList({ viewName }: DynamicListProps) {
resolved.obj.objectName, resolved.obj.objectName,
getDisplayName, getDisplayName,
) )
} )}
</td> </td>
))} ))}
{hasItemActions && <td className="px-3 py-2 text-right">{renderItemActions(item)}</td>} {hasItemActions && <td className="px-3 py-2 text-right">{renderItemActions(item)}</td>}
+2 -1
View File
@@ -400,6 +400,7 @@
}, },
"webApplications": { "webApplications": {
"activeWebUI": "Active WebUI", "activeWebUI": "Active WebUI",
"version": "Version" "version": "Version",
"enabled": "Enabled"
} }
} }