Expose searchable module catalog compatibility
This commit is contained in:
@@ -419,6 +419,30 @@ export type ModuleInterfaceRequirementItem = {
|
||||
optional: boolean;
|
||||
};
|
||||
|
||||
export type ModulePackageCatalogSource = {
|
||||
repository: string;
|
||||
tag: string;
|
||||
commit: string;
|
||||
repository_url?: string | null;
|
||||
revision_url?: string | null;
|
||||
};
|
||||
|
||||
export type ModulePackageArtifactIdentity = {
|
||||
ref?: string | null;
|
||||
path?: string | null;
|
||||
artifact_path?: string | null;
|
||||
url?: string | null;
|
||||
filename?: string | null;
|
||||
sha256?: string | null;
|
||||
size?: number | null;
|
||||
integrity?: string | null;
|
||||
sbom_url?: string | null;
|
||||
provenance_url?: string | null;
|
||||
registry_identity?: string | null;
|
||||
git_ref?: string | null;
|
||||
source_commit?: string | null;
|
||||
};
|
||||
|
||||
export type ModulePackageCatalogItem = {
|
||||
module_id: string;
|
||||
name: string;
|
||||
@@ -428,6 +452,16 @@ export type ModulePackageCatalogItem = {
|
||||
installed: boolean;
|
||||
installed_version?: string | null;
|
||||
update_available: boolean;
|
||||
availability: "available" | "withdrawn";
|
||||
availability_reason?: string | null;
|
||||
configuration_requirements: string[];
|
||||
release_notes_url?: string | null;
|
||||
source?: ModulePackageCatalogSource | null;
|
||||
artifact_integrity: Record<string, ModulePackageArtifactIdentity>;
|
||||
compatible: boolean;
|
||||
plan_allowed: boolean;
|
||||
compatibility_reasons: string[];
|
||||
catalog_state: "available" | "installed" | "update_available" | "blocked" | "withdrawn";
|
||||
dependencies: string[];
|
||||
optional_dependencies: string[];
|
||||
migration_safety: "automatic" | "requires_review" | "forward_only" | "destructive";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useMemo, useState } from "react";
|
||||
import type { ApiSettings } from "@govoplan/core-webui";
|
||||
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, Button, ConfirmDialog, dispatchPlatformModulesChanged, DocumentationHelpLink, formatDateTime, MetricCard, StageRail, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard, type FormatDateTimeOptions } from "@govoplan/core-webui";
|
||||
import { ActionBlockerHint, AdminPageLayout, adminErrorMessage, Button, ConfirmDialog, dispatchPlatformModulesChanged, DocumentationHelpLink, formatDateTime, MetricCard, SegmentedControl, StageRail, StatusBadge, ToggleSwitch, i18nMessage, useUnsavedDraftGuard, type FormatDateTimeOptions } from "@govoplan/core-webui";
|
||||
import { Check, Clock, FileText, Pencil, Send } from "lucide-react";
|
||||
import {
|
||||
cancelModuleInstallerRequest,
|
||||
@@ -52,6 +52,8 @@ const MODULE_INSTALLER_I18N = {
|
||||
resolutionTarget: "i18n:govoplan-admin.where_to_go.f1a20205"
|
||||
} as const;
|
||||
|
||||
type CatalogModuleFilter = "all" | "available" | "updates" | "installed" | "blocked";
|
||||
|
||||
export default function ModuleManagementPanel({ settings, canWrite, canAccessMaintenance }: {settings: ApiSettings;canWrite: boolean;canAccessMaintenance: boolean;}) {
|
||||
const [catalog, setCatalog] = useState<ModuleCatalogResponse | null>(null);
|
||||
const [installPlan, setInstallPlan] = useState<ModuleInstallPlanResponse | null>(null);
|
||||
@@ -71,6 +73,8 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
const [confirmClearPlan, setConfirmClearPlan] = useState(false);
|
||||
const [confirmMaintenance, setConfirmMaintenance] = useState(false);
|
||||
const [cancelRequestId, setCancelRequestId] = useState("");
|
||||
const [catalogSearch, setCatalogSearch] = useState("");
|
||||
const [catalogFilter, setCatalogFilter] = useState<CatalogModuleFilter>("all");
|
||||
|
||||
async function load() {
|
||||
setLoading(true);
|
||||
@@ -108,6 +112,28 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
const planDirty = Boolean(installPlan && JSON.stringify(normalizePlanItems(draftPlanItems)) !== JSON.stringify(normalizePlanItems(installPlan.items)));
|
||||
const planValid = planValidationError(draftPlanItems) === "";
|
||||
const latestInstallerRequest = installerRequests?.requests[0] ?? null;
|
||||
const visibleCatalogModules = useMemo(() => {
|
||||
const query = catalogSearch.trim().toLocaleLowerCase();
|
||||
return (packageCatalog?.modules ?? []).filter((item) => {
|
||||
const matchesFilter =
|
||||
catalogFilter === "all" ||
|
||||
(catalogFilter === "available" && item.catalog_state === "available") ||
|
||||
(catalogFilter === "updates" && item.update_available) ||
|
||||
(catalogFilter === "installed" && item.installed) ||
|
||||
(catalogFilter === "blocked" && ["blocked", "withdrawn"].includes(item.catalog_state));
|
||||
if (!matchesFilter) return false;
|
||||
if (!query) return true;
|
||||
return [
|
||||
item.name,
|
||||
item.module_id,
|
||||
item.description ?? "",
|
||||
item.python_package ?? "",
|
||||
item.webui_package ?? "",
|
||||
item.source?.repository ?? "",
|
||||
...item.tags,
|
||||
].join(" ").toLocaleLowerCase().includes(query);
|
||||
});
|
||||
}, [catalogFilter, catalogSearch, packageCatalog]);
|
||||
const currentInstallerRequest = latestInstallerRequest && installerRequestMatchesPlan(
|
||||
installPlan?.updated_at,
|
||||
latestInstallerRequest.created_at
|
||||
@@ -494,15 +520,39 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
{packageCatalog.warnings.map((warning) => <p key={warning} className="alert warning">{warning}</p>)}
|
||||
{!packageCatalog.configured && <div className="module-install-plan-empty">i18n:govoplan-admin.no_package_catalog_configured_set_govoplan_modul.2c5fceb3</div>}
|
||||
{packageCatalog.configured && packageCatalog.modules.length === 0 && !packageCatalog.error && <div className="module-install-plan-empty">i18n:govoplan-admin.package_catalog_is_configured_but_contains_no_en.b6b053cb</div>}
|
||||
{packageCatalog.modules.length > 0 && <div className="module-package-catalog-toolbar">
|
||||
<label className="module-package-catalog-search">
|
||||
<span>i18n:govoplan-admin.search_module_directory.7c8f1001</span>
|
||||
<input
|
||||
type="search"
|
||||
value={catalogSearch}
|
||||
onChange={(event) => setCatalogSearch(event.target.value)}
|
||||
placeholder="i18n:govoplan-admin.module_name_package_or_tag.7c8f1002" />
|
||||
|
||||
</label>
|
||||
<SegmentedControl<CatalogModuleFilter>
|
||||
ariaLabel="i18n:govoplan-admin.filter_module_directory.7c8f1003"
|
||||
value={catalogFilter}
|
||||
onChange={setCatalogFilter}
|
||||
options={[
|
||||
{ id: "all", label: "i18n:govoplan-admin.all_catalog_entries.7c8f1004" },
|
||||
{ id: "available", label: "i18n:govoplan-admin.available.7c8f1005" },
|
||||
{ id: "updates", label: "i18n:govoplan-admin.updates.7c8f1006" },
|
||||
{ id: "installed", label: "i18n:govoplan-admin.installed.7bb4405c" },
|
||||
{ id: "blocked", label: "i18n:govoplan-admin.blocked.7c8f1007" },
|
||||
]} />
|
||||
|
||||
</div>}
|
||||
{packageCatalog.modules.length > 0 && visibleCatalogModules.length === 0 && <div className="module-install-plan-empty">i18n:govoplan-admin.no_catalog_entries_match.7c8f1008</div>}
|
||||
{packageCatalog.modules.length > 0 && <div className="module-package-catalog-list">
|
||||
{packageCatalog.modules.map((item) =>
|
||||
{visibleCatalogModules.map((item) =>
|
||||
<div key={`${item.module_id}-${item.version ?? "latest"}`} className="module-package-catalog-row">
|
||||
<div className="module-management-main">
|
||||
<div className="module-management-title">
|
||||
<strong>{item.name}</strong>
|
||||
<code>{item.module_id}</code>
|
||||
{item.version && <span>v{item.version}</span>}
|
||||
{item.installed && <StatusBadge status={item.update_available ? "warning" : "success"} label={item.update_available ? "i18n:govoplan-admin.plan_update.86e6857a" : "i18n:govoplan-admin.installed.7bb4405c"} />}
|
||||
<StatusBadge status={catalogStateTone(item)} label={catalogStateLabel(item)} />
|
||||
</div>
|
||||
<div className="module-management-details">
|
||||
{item.python_package && <span>{item.python_package}</span>}
|
||||
@@ -520,13 +570,24 @@ export default function ModuleManagementPanel({ settings, canWrite, canAccessMai
|
||||
{item.provides_interfaces.length > 0 && <span>i18n:govoplan-admin.provides.221b70d9 {providedInterfacesLabel(item)}</span>}
|
||||
{item.requires_interfaces.length > 0 && <span>i18n:govoplan-admin.requires.a4fc9357 {requiredInterfacesLabel(item)}</span>}
|
||||
</div>
|
||||
{item.source && <div className="module-package-catalog-provenance">
|
||||
<span>i18n:govoplan-admin.source.7c8f1009 {item.source.revision_url ? <a href={item.source.revision_url} target="_blank" rel="noreferrer"><code>{item.source.repository}@{item.source.tag}</code></a> : <code>{item.source.repository}@{item.source.tag}</code>}</span>
|
||||
<code title={item.source.commit}>{shortIdentity(item.source.commit)}</code>
|
||||
</div>}
|
||||
{Object.entries(item.artifact_integrity).map(([kind, artifact]) => <div key={kind} className="module-package-catalog-provenance">
|
||||
<span>i18n:govoplan-admin.artifact.7c8f1010 <code>{kind}</code> {artifact.url ? <a href={artifact.url} target="_blank" rel="noreferrer">{artifact.filename ?? artifact.registry_identity ?? artifact.ref ?? kind}</a> : artifact.filename ?? artifact.registry_identity ?? artifact.ref ?? kind}</span>
|
||||
{artifact.sha256 && <code title={artifact.sha256}>sha256:{shortIdentity(artifact.sha256)}</code>}
|
||||
</div>)}
|
||||
{item.description && <p className="module-package-catalog-description">{item.description}</p>}
|
||||
{item.configuration_requirements.length > 0 && <p className="module-package-catalog-description">i18n:govoplan-admin.configuration_requirements.7c8f1011 {item.configuration_requirements.join(", ")}</p>}
|
||||
{item.migration_notes && <p className="module-package-catalog-description">{item.migration_notes}</p>}
|
||||
{item.bridge_notes && <p className="module-package-catalog-description">{item.bridge_notes}</p>}
|
||||
{item.recovery_notes && <p className="module-package-catalog-description">{item.recovery_notes}</p>}
|
||||
{item.release_notes_url && <p className="module-package-catalog-description"><a href={item.release_notes_url} target="_blank" rel="noreferrer">i18n:govoplan-admin.release_notes.7c8f1012</a></p>}
|
||||
{item.compatibility_reasons.map((compatibilityReason) => <p key={compatibilityReason} className="alert warning">{compatibilityReason}</p>)}
|
||||
{item.license_reason && <p className={`module-package-catalog-description${item.license_allowed ? "" : " alert warning"}`}>{item.license_reason}</p>}
|
||||
</div>
|
||||
<Button onClick={() => void addCatalogItem(item)} disabled={!canWrite || planBusy || !packageCatalog.valid || !item.license_allowed || !["install", "update"].includes(item.action)} disabledReason={planBusy ? ADMIN_INTERFACE_I18N.busy : !canWrite ? ADMIN_INTERFACE_I18N.writeRequired : !packageCatalog.valid || !item.license_allowed || !["install", "update"].includes(item.action) ? ADMIN_INTERFACE_I18N.catalogBlocked : undefined}>{catalogPlanButtonLabel(item)}</Button>
|
||||
<Button onClick={() => void addCatalogItem(item)} disabled={!canWrite || planBusy || !packageCatalog.valid || !item.plan_allowed} disabledReason={planBusy ? ADMIN_INTERFACE_I18N.busy : !canWrite ? ADMIN_INTERFACE_I18N.writeRequired : !packageCatalog.valid || !item.plan_allowed ? ADMIN_INTERFACE_I18N.catalogBlocked : undefined}>{catalogPlanButtonLabel(item)}</Button>
|
||||
</div>
|
||||
)}
|
||||
</div>}
|
||||
@@ -952,6 +1013,25 @@ function currentWindowLabel(item: Pick<ModulePackageCatalogItem, "current_versio
|
||||
].filter(Boolean).join(" ") || "any";
|
||||
}
|
||||
|
||||
function catalogStateTone(item: ModulePackageCatalogItem): string {
|
||||
if (item.catalog_state === "withdrawn") return "locked";
|
||||
if (item.catalog_state === "blocked" || item.catalog_state === "update_available") return "warning";
|
||||
if (item.catalog_state === "installed") return "success";
|
||||
return "inactive";
|
||||
}
|
||||
|
||||
function catalogStateLabel(item: ModulePackageCatalogItem): string {
|
||||
if (item.catalog_state === "withdrawn") return "i18n:govoplan-admin.withdrawn.7c8f1013";
|
||||
if (item.catalog_state === "blocked") return "i18n:govoplan-admin.blocked.7c8f1007";
|
||||
if (item.catalog_state === "update_available") return "i18n:govoplan-admin.update_available.7c8f1014";
|
||||
if (item.catalog_state === "installed") return "i18n:govoplan-admin.installed.7bb4405c";
|
||||
return "i18n:govoplan-admin.available.7c8f1005";
|
||||
}
|
||||
|
||||
function shortIdentity(value: string): string {
|
||||
return value.length > 14 ? `${value.slice(0, 12)}...` : value;
|
||||
}
|
||||
|
||||
function ModuleStatus({ module, desiredEnabled }: {module: ModuleCatalogItem;desiredEnabled: boolean;}) {
|
||||
if (module.current_enabled && !desiredEnabled) return <StatusBadge status="warning" label="i18n:govoplan-admin.will_disable.14bb193a" />;
|
||||
if (!module.current_enabled && desiredEnabled) return <StatusBadge status="warning" label="i18n:govoplan-admin.will_enable.5d52d70b" />;
|
||||
@@ -960,6 +1040,7 @@ function ModuleStatus({ module, desiredEnabled }: {module: ModuleCatalogItem;des
|
||||
}
|
||||
|
||||
function catalogPlanButtonLabel(item: ModulePackageCatalogItem): string {
|
||||
if (item.installed && !item.plan_allowed) return "i18n:govoplan-admin.installed.7bb4405c";
|
||||
return item.action === "update" || item.installed
|
||||
? "i18n:govoplan-admin.plan_update.86e6857a"
|
||||
: "i18n:govoplan-admin.plan_install.e82bffe6";
|
||||
|
||||
@@ -2,6 +2,20 @@ import type { PlatformTranslations } from "@govoplan/core-webui";
|
||||
|
||||
export const generatedTranslations: PlatformTranslations = {
|
||||
"en": {
|
||||
"i18n:govoplan-admin.search_module_directory.7c8f1001": "Search module directory",
|
||||
"i18n:govoplan-admin.module_name_package_or_tag.7c8f1002": "Module name, package, or tag",
|
||||
"i18n:govoplan-admin.filter_module_directory.7c8f1003": "Filter module directory",
|
||||
"i18n:govoplan-admin.all_catalog_entries.7c8f1004": "All",
|
||||
"i18n:govoplan-admin.available.7c8f1005": "Available",
|
||||
"i18n:govoplan-admin.updates.7c8f1006": "Updates",
|
||||
"i18n:govoplan-admin.blocked.7c8f1007": "Blocked",
|
||||
"i18n:govoplan-admin.no_catalog_entries_match.7c8f1008": "No catalog entries match the current search and filter.",
|
||||
"i18n:govoplan-admin.source.7c8f1009": "Source:",
|
||||
"i18n:govoplan-admin.artifact.7c8f1010": "Artifact:",
|
||||
"i18n:govoplan-admin.configuration_requirements.7c8f1011": "Configuration requirements:",
|
||||
"i18n:govoplan-admin.release_notes.7c8f1012": "Release notes",
|
||||
"i18n:govoplan-admin.withdrawn.7c8f1013": "Withdrawn",
|
||||
"i18n:govoplan-admin.update_available.7c8f1014": "Update available",
|
||||
"i18n:govoplan-admin.administration_data_is_loading.6bf3c001": "Administration data is loading.",
|
||||
"i18n:govoplan-admin.an_administration_operation_is_in_progress.6bf3c002": "An administration operation is in progress.",
|
||||
"i18n:govoplan-admin.system_administration_write_permission_is_required.6bf3c003": "System administration write permission is required.",
|
||||
@@ -438,6 +452,20 @@ export const generatedTranslations: PlatformTranslations = {
|
||||
"i18n:govoplan-admin.working.049ac820": "Working..."
|
||||
},
|
||||
"de": {
|
||||
"i18n:govoplan-admin.search_module_directory.7c8f1001": "Modulverzeichnis durchsuchen",
|
||||
"i18n:govoplan-admin.module_name_package_or_tag.7c8f1002": "Modulname, Paket oder Schlagwort",
|
||||
"i18n:govoplan-admin.filter_module_directory.7c8f1003": "Modulverzeichnis filtern",
|
||||
"i18n:govoplan-admin.all_catalog_entries.7c8f1004": "Alle",
|
||||
"i18n:govoplan-admin.available.7c8f1005": "Verfügbar",
|
||||
"i18n:govoplan-admin.updates.7c8f1006": "Aktualisierungen",
|
||||
"i18n:govoplan-admin.blocked.7c8f1007": "Blockiert",
|
||||
"i18n:govoplan-admin.no_catalog_entries_match.7c8f1008": "Keine Katalogeinträge entsprechen der aktuellen Suche und dem Filter.",
|
||||
"i18n:govoplan-admin.source.7c8f1009": "Quelle:",
|
||||
"i18n:govoplan-admin.artifact.7c8f1010": "Artefakt:",
|
||||
"i18n:govoplan-admin.configuration_requirements.7c8f1011": "Konfigurationsvoraussetzungen:",
|
||||
"i18n:govoplan-admin.release_notes.7c8f1012": "Versionshinweise",
|
||||
"i18n:govoplan-admin.withdrawn.7c8f1013": "Zurückgezogen",
|
||||
"i18n:govoplan-admin.update_available.7c8f1014": "Aktualisierung verfügbar",
|
||||
"i18n:govoplan-admin.administration_data_is_loading.6bf3c001": "Administrationsdaten werden geladen.",
|
||||
"i18n:govoplan-admin.an_administration_operation_is_in_progress.6bf3c002": "Eine Administrationsaktion wird gerade ausgeführt.",
|
||||
"i18n:govoplan-admin.system_administration_write_permission_is_required.6bf3c003": "Eine Schreibberechtigung für die Systemadministration ist erforderlich.",
|
||||
|
||||
Reference in New Issue
Block a user