intermittent commit
This commit is contained in:
16
README.md
16
README.md
@@ -47,3 +47,19 @@ Package mutation is intentionally not executed inside the FastAPI request. The
|
|||||||
admin UI records operator intent and queues or renders commands for the trusted
|
admin UI records operator intent and queues or renders commands for the trusted
|
||||||
installer process described in
|
installer process described in
|
||||||
`/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`.
|
`/mnt/DATA/git/govoplan-core/docs/MODULE_ARCHITECTURE.md`.
|
||||||
|
|
||||||
|
## Package Surfaces
|
||||||
|
|
||||||
|
The admin UI intentionally exposes two different package concepts:
|
||||||
|
|
||||||
|
- **Configuration packages** are import/export bundles for module-owned
|
||||||
|
configuration data. They support dry-run diagnostics, approval, apply, and
|
||||||
|
export workflows without installing Python or WebUI packages.
|
||||||
|
- **Module package catalog** and **operator install plan** live under
|
||||||
|
**Modules**. They describe approved release artifacts, install/update/remove
|
||||||
|
plans, preflight blockers, maintenance-mode requirements, installer-daemon
|
||||||
|
requests, and rollback visibility.
|
||||||
|
|
||||||
|
These surfaces should stay separate in navigation and copy. If future UI work
|
||||||
|
combines them visually, it must still preserve the operator distinction between
|
||||||
|
configuration mutation and package installation.
|
||||||
|
|||||||
@@ -59,6 +59,12 @@ from govoplan_core.core.module_installer import (
|
|||||||
read_module_installer_request,
|
read_module_installer_request,
|
||||||
retry_module_installer_request,
|
retry_module_installer_request,
|
||||||
)
|
)
|
||||||
|
from govoplan_core.core.module_installer_notifications import (
|
||||||
|
emit_module_installer_notification,
|
||||||
|
installer_notification_body,
|
||||||
|
installer_notification_priority,
|
||||||
|
installer_notification_subject,
|
||||||
|
)
|
||||||
from govoplan_core.core.module_license import module_license_decision, module_license_diagnostics
|
from govoplan_core.core.module_license import module_license_decision, module_license_diagnostics
|
||||||
from govoplan_core.core.module_package_catalog import record_module_package_catalog_acceptance, validate_module_package_catalog
|
from govoplan_core.core.module_package_catalog import record_module_package_catalog_acceptance, validate_module_package_catalog
|
||||||
from govoplan_core.core.maintenance import MAINTENANCE_ACCESS_SCOPE, MaintenanceMode, saved_maintenance_mode, save_maintenance_mode
|
from govoplan_core.core.maintenance import MAINTENANCE_ACCESS_SCOPE, MaintenanceMode, saved_maintenance_mode, save_maintenance_mode
|
||||||
@@ -147,6 +153,11 @@ def _request_registry(request: Request) -> PlatformRegistry:
|
|||||||
return registry
|
return registry
|
||||||
|
|
||||||
|
|
||||||
|
def _optional_request_registry(request: Request) -> PlatformRegistry | None:
|
||||||
|
registry = getattr(request.app.state, "govoplan_registry", None)
|
||||||
|
return registry if isinstance(registry, PlatformRegistry) else None
|
||||||
|
|
||||||
|
|
||||||
def _request_lifecycle(request: Request) -> ModuleLifecycleManager:
|
def _request_lifecycle(request: Request) -> ModuleLifecycleManager:
|
||||||
lifecycle = getattr(request.app.state, "govoplan_lifecycle", None)
|
lifecycle = getattr(request.app.state, "govoplan_lifecycle", None)
|
||||||
if not isinstance(lifecycle, ModuleLifecycleManager):
|
if not isinstance(lifecycle, ModuleLifecycleManager):
|
||||||
@@ -154,6 +165,29 @@ def _request_lifecycle(request: Request) -> ModuleLifecycleManager:
|
|||||||
return lifecycle
|
return lifecycle
|
||||||
|
|
||||||
|
|
||||||
|
def _emit_installer_request_notification(
|
||||||
|
*,
|
||||||
|
session: Session,
|
||||||
|
registry: PlatformRegistry | None,
|
||||||
|
tenant_id: str | None,
|
||||||
|
request: dict[str, object],
|
||||||
|
event_kind: str,
|
||||||
|
recipient_id: str | None = None,
|
||||||
|
) -> None:
|
||||||
|
status_value = str(request.get("status") or event_kind.rsplit(".", 1)[-1])
|
||||||
|
emit_module_installer_notification(
|
||||||
|
session=session,
|
||||||
|
registry=registry,
|
||||||
|
tenant_id=tenant_id,
|
||||||
|
request=request,
|
||||||
|
event_kind=event_kind,
|
||||||
|
subject=installer_notification_subject(event_kind, request),
|
||||||
|
body_text=installer_notification_body(event_kind, request),
|
||||||
|
recipient_id=recipient_id,
|
||||||
|
priority=installer_notification_priority(status_value),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def _http_admin_error(exc: Exception) -> HTTPException:
|
def _http_admin_error(exc: Exception) -> HTTPException:
|
||||||
if isinstance(exc, AdminConflictError):
|
if isinstance(exc, AdminConflictError):
|
||||||
return HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))
|
return HTTPException(status_code=status.HTTP_409_CONFLICT, detail=str(exc))
|
||||||
@@ -663,6 +697,7 @@ def read_module_install_request_detail(
|
|||||||
@router.post("/system/modules/install-requests", response_model=ModuleInstallerRequestItem)
|
@router.post("/system/modules/install-requests", response_model=ModuleInstallerRequestItem)
|
||||||
def create_module_install_request(
|
def create_module_install_request(
|
||||||
payload: ModuleInstallerRequestCreateRequest,
|
payload: ModuleInstallerRequestCreateRequest,
|
||||||
|
request_context: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
||||||
):
|
):
|
||||||
@@ -675,8 +710,17 @@ def create_module_install_request(
|
|||||||
request = queue_module_installer_request(
|
request = queue_module_installer_request(
|
||||||
runtime_dir=runtime_dir,
|
runtime_dir=runtime_dir,
|
||||||
requested_by=principal.user.id,
|
requested_by=principal.user.id,
|
||||||
|
tenant_id=principal.tenant_id,
|
||||||
options=payload.options.model_dump(exclude_none=True),
|
options=payload.options.model_dump(exclude_none=True),
|
||||||
)
|
)
|
||||||
|
_emit_installer_request_notification(
|
||||||
|
session=session,
|
||||||
|
registry=_optional_request_registry(request_context),
|
||||||
|
tenant_id=principal.tenant_id,
|
||||||
|
request=request,
|
||||||
|
event_kind="module_installer.request.queued",
|
||||||
|
recipient_id=principal.user.id,
|
||||||
|
)
|
||||||
audit_from_principal(
|
audit_from_principal(
|
||||||
session,
|
session,
|
||||||
principal,
|
principal,
|
||||||
@@ -698,6 +742,7 @@ def create_module_install_request(
|
|||||||
@router.post("/system/modules/install-requests/{request_id}/cancel", response_model=ModuleInstallerRequestItem)
|
@router.post("/system/modules/install-requests/{request_id}/cancel", response_model=ModuleInstallerRequestItem)
|
||||||
def cancel_module_install_request(
|
def cancel_module_install_request(
|
||||||
request_id: str,
|
request_id: str,
|
||||||
|
request_context: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
||||||
):
|
):
|
||||||
@@ -715,6 +760,14 @@ def cancel_module_install_request(
|
|||||||
)
|
)
|
||||||
except ModuleInstallerError as exc:
|
except ModuleInstallerError as exc:
|
||||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
|
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
|
||||||
|
_emit_installer_request_notification(
|
||||||
|
session=session,
|
||||||
|
registry=_optional_request_registry(request_context),
|
||||||
|
tenant_id=str(request.get("tenant_id") or principal.tenant_id),
|
||||||
|
request=request,
|
||||||
|
event_kind="module_installer.request.cancelled",
|
||||||
|
recipient_id=str(request.get("requested_by") or principal.user.id),
|
||||||
|
)
|
||||||
audit_from_principal(
|
audit_from_principal(
|
||||||
session,
|
session,
|
||||||
principal,
|
principal,
|
||||||
@@ -736,6 +789,7 @@ def cancel_module_install_request(
|
|||||||
@router.post("/system/modules/install-requests/{request_id}/retry", response_model=ModuleInstallerRequestItem)
|
@router.post("/system/modules/install-requests/{request_id}/retry", response_model=ModuleInstallerRequestItem)
|
||||||
def retry_module_install_request(
|
def retry_module_install_request(
|
||||||
request_id: str,
|
request_id: str,
|
||||||
|
request_context: Request,
|
||||||
session: Session = Depends(get_session),
|
session: Session = Depends(get_session),
|
||||||
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
principal: ApiPrincipal = Depends(require_scope("system:settings:write")),
|
||||||
):
|
):
|
||||||
@@ -753,6 +807,14 @@ def retry_module_install_request(
|
|||||||
)
|
)
|
||||||
except ModuleInstallerError as exc:
|
except ModuleInstallerError as exc:
|
||||||
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
|
raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_CONTENT, detail=str(exc)) from exc
|
||||||
|
_emit_installer_request_notification(
|
||||||
|
session=session,
|
||||||
|
registry=_optional_request_registry(request_context),
|
||||||
|
tenant_id=str(request.get("tenant_id") or principal.tenant_id),
|
||||||
|
request=request,
|
||||||
|
event_kind="module_installer.request.queued",
|
||||||
|
recipient_id=principal.user.id,
|
||||||
|
)
|
||||||
audit_from_principal(
|
audit_from_principal(
|
||||||
session,
|
session,
|
||||||
principal,
|
principal,
|
||||||
|
|||||||
@@ -3,61 +3,10 @@ from __future__ import annotations
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Any, Literal
|
from typing import Any, Literal
|
||||||
|
|
||||||
from pydantic import BaseModel, ConfigDict, Field, field_validator
|
from pydantic import BaseModel, ConfigDict, Field
|
||||||
|
|
||||||
from govoplan_core.api.v1.schemas import DeltaDeletedItem
|
from govoplan_core.api.v1.schemas import DeltaDeletedItem
|
||||||
|
from govoplan_core.privacy.schemas import PrivacyRetentionPolicyItem
|
||||||
|
|
||||||
RETENTION_DAY_KEYS = (
|
|
||||||
"raw_campaign_json_retention_days",
|
|
||||||
"generated_eml_retention_days",
|
|
||||||
"stored_report_detail_retention_days",
|
|
||||||
"mock_mailbox_retention_days",
|
|
||||||
"audit_detail_retention_days",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
RETENTION_POLICY_FIELD_KEYS = (
|
|
||||||
"store_raw_campaign_json",
|
|
||||||
*RETENTION_DAY_KEYS,
|
|
||||||
"audit_detail_level",
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def default_allow_lower_level_limits() -> dict[str, bool]:
|
|
||||||
return {key: True for key in RETENTION_POLICY_FIELD_KEYS}
|
|
||||||
|
|
||||||
|
|
||||||
def normalize_allow_lower_level_limits(value: Any, *, fill_defaults: bool) -> dict[str, bool] | None:
|
|
||||||
if value in (None, ""):
|
|
||||||
return default_allow_lower_level_limits() if fill_defaults else None
|
|
||||||
if not isinstance(value, dict):
|
|
||||||
raise ValueError("allow_lower_level_limits must be an object")
|
|
||||||
normalized = default_allow_lower_level_limits() if fill_defaults else {}
|
|
||||||
for key, allowed in value.items():
|
|
||||||
clean_key = str(key)
|
|
||||||
if clean_key not in RETENTION_POLICY_FIELD_KEYS:
|
|
||||||
raise ValueError(f"Unknown retention policy field: {clean_key}")
|
|
||||||
normalized[clean_key] = bool(allowed)
|
|
||||||
return normalized
|
|
||||||
|
|
||||||
|
|
||||||
class PrivacyRetentionPolicyItem(BaseModel):
|
|
||||||
model_config = ConfigDict(extra="forbid")
|
|
||||||
|
|
||||||
store_raw_campaign_json: bool = True
|
|
||||||
raw_campaign_json_retention_days: int | None = Field(default=None, ge=0)
|
|
||||||
generated_eml_retention_days: int | None = Field(default=None, ge=0)
|
|
||||||
stored_report_detail_retention_days: int | None = Field(default=None, ge=0)
|
|
||||||
mock_mailbox_retention_days: int | None = Field(default=None, ge=0)
|
|
||||||
audit_detail_retention_days: int | None = Field(default=None, ge=0)
|
|
||||||
audit_detail_level: Literal["full", "redacted", "minimal"] = "full"
|
|
||||||
allow_lower_level_limits: dict[str, bool] = Field(default_factory=default_allow_lower_level_limits)
|
|
||||||
|
|
||||||
@field_validator("allow_lower_level_limits", mode="before")
|
|
||||||
@classmethod
|
|
||||||
def _normalize_allow_lower_level_limits(cls, value: Any) -> Any:
|
|
||||||
return normalize_allow_lower_level_limits(value, fill_defaults=True)
|
|
||||||
|
|
||||||
|
|
||||||
class MaintenanceModeItem(BaseModel):
|
class MaintenanceModeItem(BaseModel):
|
||||||
|
|||||||
@@ -1,74 +1,16 @@
|
|||||||
import type { ApiSettings, DeltaDeletedItem } from "@govoplan/core-webui";
|
import type { ApiSettings, DeltaDeletedItem, PrivacyRetentionPolicy } from "@govoplan/core-webui";
|
||||||
import { apiFetch } from "@govoplan/core-webui";
|
import { apiFetch, apiGetList, apiPath, apiQuery } from "@govoplan/core-webui";
|
||||||
|
export { fetchAdminOverview, fetchPermissionCatalog, fetchTenants } from "@govoplan/core-webui";
|
||||||
export type PermissionItem = {
|
export type {
|
||||||
scope: string;
|
AdminOverview,
|
||||||
label: string;
|
PrivacyRetentionLimitPermissionPatch,
|
||||||
description: string;
|
PrivacyRetentionLimitPermissions,
|
||||||
category: string;
|
PrivacyRetentionPolicy,
|
||||||
level: "tenant" | "system";
|
PrivacyRetentionPolicyFieldKey,
|
||||||
system_template_id?: string | null;
|
PrivacyRetentionPolicyPatch,
|
||||||
system_required?: boolean;
|
PermissionItem,
|
||||||
};
|
TenantAdminItem
|
||||||
|
} from "@govoplan/core-webui";
|
||||||
export type AdminOverview = {
|
|
||||||
active_tenant_id: string;
|
|
||||||
active_tenant_name: string;
|
|
||||||
tenant_count?: number | null;
|
|
||||||
system_account_count?: number | null;
|
|
||||||
system_group_template_count?: number | null;
|
|
||||||
system_role_template_count?: number | null;
|
|
||||||
user_count: number;
|
|
||||||
active_user_count: number;
|
|
||||||
group_count: number;
|
|
||||||
role_count: number;
|
|
||||||
active_api_key_count: number;
|
|
||||||
capabilities: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
export type TenantAdminItem = {
|
|
||||||
id: string;
|
|
||||||
slug: string;
|
|
||||||
name: string;
|
|
||||||
description?: string | null;
|
|
||||||
default_locale: string;
|
|
||||||
settings: Record<string, unknown>;
|
|
||||||
allow_custom_groups?: boolean | null;
|
|
||||||
allow_custom_roles?: boolean | null;
|
|
||||||
allow_api_keys?: boolean | null;
|
|
||||||
effective_governance: Record<string, boolean>;
|
|
||||||
is_active: boolean;
|
|
||||||
counts: Record<string, number>;
|
|
||||||
created_at: string;
|
|
||||||
updated_at: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PrivacyRetentionPolicyFieldKey =
|
|
||||||
| "store_raw_campaign_json"
|
|
||||||
| "raw_campaign_json_retention_days"
|
|
||||||
| "generated_eml_retention_days"
|
|
||||||
| "stored_report_detail_retention_days"
|
|
||||||
| "mock_mailbox_retention_days"
|
|
||||||
| "audit_detail_retention_days"
|
|
||||||
| "audit_detail_level";
|
|
||||||
|
|
||||||
export type PrivacyRetentionLimitPermissions = Record<PrivacyRetentionPolicyFieldKey, boolean>;
|
|
||||||
export type PrivacyRetentionLimitPermissionPatch = Partial<PrivacyRetentionLimitPermissions>;
|
|
||||||
|
|
||||||
export type PrivacyRetentionPolicy = {
|
|
||||||
store_raw_campaign_json: boolean;
|
|
||||||
raw_campaign_json_retention_days?: number | null;
|
|
||||||
generated_eml_retention_days?: number | null;
|
|
||||||
stored_report_detail_retention_days?: number | null;
|
|
||||||
mock_mailbox_retention_days?: number | null;
|
|
||||||
audit_detail_retention_days?: number | null;
|
|
||||||
audit_detail_level: "full" | "redacted" | "minimal";
|
|
||||||
allow_lower_level_limits: PrivacyRetentionLimitPermissions;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type PrivacyRetentionPolicyPatch = Partial<Omit<PrivacyRetentionPolicy, "allow_lower_level_limits">> & {
|
|
||||||
allow_lower_level_limits?: PrivacyRetentionLimitPermissionPatch;
|
|
||||||
};
|
|
||||||
|
|
||||||
export type MaintenanceMode = {
|
export type MaintenanceMode = {
|
||||||
enabled: boolean;
|
enabled: boolean;
|
||||||
@@ -192,10 +134,7 @@ type DeltaResponseFields = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
function deltaSuffix(options: { since?: string | null; limit?: number } = {}): string {
|
function deltaSuffix(options: { since?: string | null; limit?: number } = {}): string {
|
||||||
const params = new URLSearchParams();
|
return apiQuery(options);
|
||||||
if (options.since) params.set("since", options.since);
|
|
||||||
if (options.limit) params.set("limit", String(options.limit));
|
|
||||||
return params.toString() ? `?${params.toString()}` : "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SystemSettingsDeltaResponse = {
|
export type SystemSettingsDeltaResponse = {
|
||||||
@@ -541,20 +480,6 @@ export type GovernanceTemplateItem = {
|
|||||||
updated_at: string;
|
updated_at: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function fetchAdminOverview(settings: ApiSettings): Promise<AdminOverview> {
|
|
||||||
return apiFetch(settings, "/api/v1/admin/overview");
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchPermissionCatalog(settings: ApiSettings): Promise<PermissionItem[]> {
|
|
||||||
const response = await apiFetch<{ permissions: PermissionItem[] }>(settings, "/api/v1/admin/permissions");
|
|
||||||
return response.permissions;
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function fetchTenants(settings: ApiSettings): Promise<TenantAdminItem[]> {
|
|
||||||
const response = await apiFetch<{ tenants: TenantAdminItem[] }>(settings, "/api/v1/admin/tenants");
|
|
||||||
return response.tenants;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function fetchSystemSettings(settings: ApiSettings): Promise<SystemSettingsItem> {
|
export function fetchSystemSettings(settings: ApiSettings): Promise<SystemSettingsItem> {
|
||||||
return apiFetch(settings, "/api/v1/admin/system/settings");
|
return apiFetch(settings, "/api/v1/admin/system/settings");
|
||||||
}
|
}
|
||||||
@@ -584,19 +509,11 @@ export function fetchModuleInstallPlan(settings: ApiSettings): Promise<ModuleIns
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function fetchModuleInstallerRuns(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRunListResponse> {
|
export function fetchModuleInstallerRuns(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRunListResponse> {
|
||||||
const params = new URLSearchParams();
|
return apiFetch(settings, apiPath("/api/v1/admin/system/modules/install-runs", options));
|
||||||
if (options.page_size) params.set("page_size", String(options.page_size));
|
|
||||||
if (options.cursor) params.set("cursor", options.cursor);
|
|
||||||
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
||||||
return apiFetch(settings, `/api/v1/admin/system/modules/install-runs${suffix}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchModuleInstallerRequests(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRequestListResponse> {
|
export function fetchModuleInstallerRequests(settings: ApiSettings, options: { page_size?: number; cursor?: string | null } = {}): Promise<ModuleInstallerRequestListResponse> {
|
||||||
const params = new URLSearchParams();
|
return apiFetch(settings, apiPath("/api/v1/admin/system/modules/install-requests", options));
|
||||||
if (options.page_size) params.set("page_size", String(options.page_size));
|
|
||||||
if (options.cursor) params.set("cursor", options.cursor);
|
|
||||||
const suffix = params.toString() ? `?${params.toString()}` : "";
|
|
||||||
return apiFetch(settings, `/api/v1/admin/system/modules/install-requests${suffix}`);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createModuleInstallerRequest(settings: ApiSettings, options: ModuleInstallerRequestOptions): Promise<ModuleInstallerRequestItem> {
|
export function createModuleInstallerRequest(settings: ApiSettings, options: ModuleInstallerRequestOptions): Promise<ModuleInstallerRequestItem> {
|
||||||
@@ -638,9 +555,7 @@ export function clearModuleInstallPlan(settings: ApiSettings): Promise<ModuleIns
|
|||||||
}
|
}
|
||||||
|
|
||||||
export async function fetchGovernanceTemplates(settings: ApiSettings, kind?: "group" | "role"): Promise<GovernanceTemplateItem[]> {
|
export async function fetchGovernanceTemplates(settings: ApiSettings, kind?: "group" | "role"): Promise<GovernanceTemplateItem[]> {
|
||||||
const suffix = kind ? `?kind=${encodeURIComponent(kind)}` : "";
|
return apiGetList<GovernanceTemplateItem, "templates">(settings, "/api/v1/admin/system/governance-templates", "templates", { kind });
|
||||||
const response = await apiFetch<{ templates: GovernanceTemplateItem[] }>(settings, `/api/v1/admin/system/governance-templates${suffix}`);
|
|
||||||
return response.templates;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createGovernanceTemplate(settings: ApiSettings, payload: Omit<GovernanceTemplateItem, "id" | "created_at" | "updated_at" | "effective_permission_count"> & { change_request_id?: string | null }): Promise<GovernanceTemplateItem> {
|
export function createGovernanceTemplate(settings: ApiSettings, payload: Omit<GovernanceTemplateItem, "id" | "created_at" | "updated_at" | "effective_permission_count"> & { change_request_id?: string | null }): Promise<GovernanceTemplateItem> {
|
||||||
@@ -652,8 +567,7 @@ export function updateGovernanceTemplate(settings: ApiSettings, templateId: stri
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function deleteGovernanceTemplate(settings: ApiSettings, templateId: string, changeRequestId?: string | null): Promise<void> {
|
export function deleteGovernanceTemplate(settings: ApiSettings, templateId: string, changeRequestId?: string | null): Promise<void> {
|
||||||
const suffix = changeRequestId ? `?change_request_id=${encodeURIComponent(changeRequestId)}` : "";
|
return apiFetch(settings, apiPath(`/api/v1/admin/system/governance-templates/${templateId}`, { change_request_id: changeRequestId }), { method: "DELETE" });
|
||||||
return apiFetch(settings, `/api/v1/admin/system/governance-templates/${templateId}${suffix}`, { method: "DELETE" });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function fetchConfigurationChanges(settings: ApiSettings): Promise<{ requests: ConfigurationChangeRequest[]; history: ConfigurationChangeRecord[] }> {
|
export function fetchConfigurationChanges(settings: ApiSettings): Promise<{ requests: ConfigurationChangeRequest[]; history: ConfigurationChangeRecord[] }> {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export default function AdminOverviewPanel({ settings, onSelect, availableSectio
|
|||||||
{hasAnySection(availableSections, platformSectionIds) && <Card title="ADMINISTRATION">
|
{hasAnySection(availableSections, platformSectionIds) && <Card title="ADMINISTRATION">
|
||||||
<div className="admin-overview-grid">
|
<div className="admin-overview-grid">
|
||||||
{availableSections.has("system-modules") && <AreaLink title="i18n:govoplan-admin.modules.04e9462c" text="i18n:govoplan-admin.installed_modules_runtime_state_and_startup_stat.38dd7028" onClick={() => onSelect("system-modules")} />}
|
{availableSections.has("system-modules") && <AreaLink title="i18n:govoplan-admin.modules.04e9462c" text="i18n:govoplan-admin.installed_modules_runtime_state_and_startup_stat.38dd7028" onClick={() => onSelect("system-modules")} />}
|
||||||
{availableSections.has("system-configuration-packages") && <AreaLink title="i18n:govoplan-admin.packages.0a999012" text="i18n:govoplan-admin.preflight_approve_apply_and_export_configuration.276bd0f8" onClick={() => onSelect("system-configuration-packages")} />}
|
{availableSections.has("system-configuration-packages") && <AreaLink title="i18n:govoplan-admin.configuration_packages.eb2f05f1" text="i18n:govoplan-admin.preflight_approve_apply_and_export_configuration.276bd0f8" onClick={() => onSelect("system-configuration-packages")} />}
|
||||||
{availableSections.has("system-settings") && <AreaLink title="i18n:govoplan-admin.maintenance.94de303b" text="i18n:govoplan-admin.instance_defaults_and_tenant_governance_capabili.99d6b2fa" onClick={() => onSelect("system-settings")} />}
|
{availableSections.has("system-settings") && <AreaLink title="i18n:govoplan-admin.maintenance.94de303b" text="i18n:govoplan-admin.instance_defaults_and_tenant_governance_capabili.99d6b2fa" onClick={() => onSelect("system-settings")} />}
|
||||||
{availableSections.has("system-configuration-changes") && <AreaLink title="i18n:govoplan-admin.changes.8aa57de6" text="i18n:govoplan-admin.configuration_requests_approvals_and_version_his.19f37335" onClick={() => onSelect("system-configuration-changes")} />}
|
{availableSections.has("system-configuration-changes") && <AreaLink title="i18n:govoplan-admin.changes.8aa57de6" text="i18n:govoplan-admin.configuration_requests_approvals_and_version_his.19f37335" onClick={() => onSelect("system-configuration-changes")} />}
|
||||||
{availableSections.has("system-audit") && <AreaLink title="i18n:govoplan-admin.audit.fa1703dd" text="i18n:govoplan-admin.system_level_administrative_history.49f76723" onClick={() => onSelect("system-audit")} />}
|
{availableSections.has("system-audit") && <AreaLink title="i18n:govoplan-admin.audit.fa1703dd" text="i18n:govoplan-admin.system_level_administrative_history.49f76723" onClick={() => onSelect("system-audit")} />}
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ const adminSections: AdminSectionsUiCapability = {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
id: "system-configuration-packages",
|
id: "system-configuration-packages",
|
||||||
label: "i18n:govoplan-admin.packages.0a999012",
|
label: "i18n:govoplan-admin.configuration_packages.eb2f05f1",
|
||||||
group: "SYSTEM",
|
group: "SYSTEM",
|
||||||
order: 30,
|
order: 30,
|
||||||
allOf: ["system:settings:read"],
|
allOf: ["system:settings:read"],
|
||||||
@@ -115,4 +115,4 @@ export const adminModule: PlatformWebModule = {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default adminModule;
|
export default adminModule;
|
||||||
|
|||||||
Reference in New Issue
Block a user