6 Commits
v0.1.6 ... main

8 changed files with 42 additions and 46 deletions

View File

@@ -1,5 +1,9 @@
# govoplan-ops # govoplan-ops
<!-- govoplan-repository-type:start -->
**Repository type:** module (platform).
<!-- govoplan-repository-type:end -->
GovOPlaN Ops provides the operator surface for deployment health, runtime GovOPlaN Ops provides the operator surface for deployment health, runtime
profile visibility, worker split assumptions, and sizing guidance. profile visibility, worker split assumptions, and sizing guidance.

View File

@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/ops-webui", "name": "@govoplan/ops-webui",
"version": "0.1.6", "version": "0.1.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "webui/src/index.ts", "main": "webui/src/index.ts",
@@ -17,7 +17,7 @@
"README.md" "README.md"
], ],
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.6", "@govoplan/core-webui": "^0.1.8",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",
@@ -32,4 +32,3 @@
} }
} }
} }

View File

@@ -4,14 +4,14 @@ build-backend = "setuptools.build_meta"
[project] [project]
name = "govoplan-ops" name = "govoplan-ops"
version = "0.1.6" version = "0.1.8"
description = "GovOPlaN operations module for health, deployment profile, and sizing visibility." description = "GovOPlaN operations module for health, deployment profile, and sizing visibility."
readme = "README.md" readme = "README.md"
requires-python = ">=3.12" requires-python = ">=3.12"
authors = [{ name = "GovOPlaN" }] authors = [{ name = "GovOPlaN" }]
dependencies = [ dependencies = [
"govoplan-core>=0.1.6", "govoplan-core>=0.1.8",
"govoplan-access>=0.1.6", "govoplan-access>=0.1.8",
] ]
[tool.setuptools.packages.find] [tool.setuptools.packages.find]

View File

@@ -2,4 +2,4 @@
__all__ = ["__version__"] __all__ = ["__version__"]
__version__ = "0.1.6" __version__ = "0.1.8"

View File

@@ -2,6 +2,7 @@ from __future__ import annotations
from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER from govoplan_core.core.access import CAPABILITY_AUTH_PERMISSION_EVALUATOR, CAPABILITY_AUTH_PRINCIPAL_RESOLVER
from govoplan_core.core.modules import FrontendModule, FrontendRoute, ModuleContext, ModuleManifest, NavItem, PermissionDefinition, RoleTemplate from govoplan_core.core.modules import FrontendModule, FrontendRoute, ModuleContext, ModuleManifest, NavItem, PermissionDefinition, RoleTemplate
from govoplan_core.core.views import ViewSurface
OPS_READ_SCOPE = "ops:operations:read" OPS_READ_SCOPE = "ops:operations:read"
OPS_READ_SCOPES = (OPS_READ_SCOPE, "system:settings:read", "admin:settings:read") OPS_READ_SCOPES = (OPS_READ_SCOPE, "system:settings:read", "admin:settings:read")
@@ -31,7 +32,7 @@ def _route_factory(context: ModuleContext):
manifest = ModuleManifest( manifest = ModuleManifest(
id="ops", id="ops",
name="Ops", name="Ops",
version="0.1.6", version="0.1.8",
required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR), required_capabilities=(CAPABILITY_AUTH_PRINCIPAL_RESOLVER, CAPABILITY_AUTH_PERMISSION_EVALUATOR),
optional_dependencies=("audit", "docs", "notifications"), optional_dependencies=("audit", "docs", "notifications"),
permissions=( permissions=(
@@ -57,6 +58,9 @@ manifest = ModuleManifest(
package_name="@govoplan/ops-webui", package_name="@govoplan/ops-webui",
routes=(FrontendRoute(path="/ops", component="OpsPage", required_any=OPS_READ_SCOPES, order=890),), routes=(FrontendRoute(path="/ops", component="OpsPage", required_any=OPS_READ_SCOPES, order=890),),
nav_items=(NavItem(path="/ops", label="Ops", icon="activity", required_any=OPS_READ_SCOPES, order=890),), nav_items=(NavItem(path="/ops", label="Ops", icon="activity", required_any=OPS_READ_SCOPES, order=890),),
view_surfaces=(
ViewSurface(id="ops.widget.health", module_id="ops", kind="section", label="Operations health widget", order=100),
),
), ),
) )

View File

@@ -1,6 +1,6 @@
{ {
"name": "@govoplan/ops-webui", "name": "@govoplan/ops-webui",
"version": "0.1.6", "version": "0.1.8",
"private": true, "private": true,
"type": "module", "type": "module",
"main": "src/index.ts", "main": "src/index.ts",
@@ -13,7 +13,7 @@
} }
}, },
"peerDependencies": { "peerDependencies": {
"@govoplan/core-webui": "^0.1.6", "@govoplan/core-webui": "^0.1.8",
"lucide-react": "^1.23.0", "lucide-react": "^1.23.0",
"react": "^19.0.0", "react": "^19.0.0",
"react-dom": "^19.0.0", "react-dom": "^19.0.0",

View File

@@ -3,13 +3,16 @@ import { RefreshCw } from "lucide-react";
import { import {
Button, Button,
Card, Card,
DataGrid,
DismissibleAlert, DismissibleAlert,
LoadingFrame, LoadingFrame,
MetricCard, MetricCard,
PageScrollViewport,
PageTitle, PageTitle,
StatusBadge, StatusBadge,
adminErrorMessage, adminErrorMessage,
type ApiSettings } from type ApiSettings,
type DataGridColumn } from
"@govoplan/core-webui"; "@govoplan/core-webui";
import { fetchOpsStatus, type OpsCheck, type OpsDeploymentProfile, type OpsSizingAssumption, type OpsStatus } from "../../api/ops"; import { fetchOpsStatus, type OpsCheck, type OpsDeploymentProfile, type OpsSizingAssumption, type OpsStatus } from "../../api/ops";
@@ -38,6 +41,7 @@ export default function OpsPage({ settings }: {settings: ApiSettings;}) {
const ready = status?.readiness.ready ?? false; const ready = status?.readiness.ready ?? false;
return ( return (
<PageScrollViewport>
<div className="content-pad workspace-data-page"> <div className="content-pad workspace-data-page">
<div className="page-heading split workspace-heading"> <div className="page-heading split workspace-heading">
<div> <div>
@@ -75,7 +79,8 @@ export default function OpsPage({ settings }: {settings: ApiSettings;}) {
</Card> </Card>
</div> </div>
</LoadingFrame> </LoadingFrame>
</div>); </div>
</PageScrollViewport>);
} }
@@ -95,47 +100,27 @@ function CheckList({ checks }: {checks: OpsCheck[];}) {
function ProfileList({ profiles }: {profiles: OpsDeploymentProfile[];}) { function ProfileList({ profiles }: {profiles: OpsDeploymentProfile[];}) {
if (!profiles.length) return <p className="muted">i18n:govoplan-ops.no_deployment_profiles_reported.7c3af1db</p>; if (!profiles.length) return <p className="muted">i18n:govoplan-ops.no_deployment_profiles_reported.7c3af1db</p>;
const columns: DataGridColumn<OpsDeploymentProfile>[] = [
{ id: "profile", header: "i18n:govoplan-ops.profile.ff4fc027", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, sortable: true, filterable: true, value: (profile) => `${profile.name} ${profile.id}`, render: (profile) => <div><strong>{profile.name}</strong><span className="muted block">{profile.id}</span></div> },
{ id: "status", header: "i18n:govoplan-ops.status.bae7d5be", width: 140, sortable: true, filterable: true, value: (profile) => profile.current ? "current" : "reference", render: (profile) => <StatusBadge status={profile.current ? "success" : "inactive"} label={profile.current ? "current" : "reference"} /> },
{ id: "components", header: "i18n:govoplan-ops.components.9289473e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (profile) => profile.components.join(" "), render: (profile) => profile.components.join(", ") },
{ id: "fit", header: "i18n:govoplan-ops.fit.dab564d8", width: "minmax(180px, .8fr)", minWidth: 160, resizable: true, filterable: true, value: (profile) => profile.fit }
];
return ( return (
<div className="admin-table-wrap"> <DataGrid id="ops-deployment-profiles" rows={profiles} columns={columns} getRowKey={(profile) => profile.id} />);
<table className="admin-table">
<thead>
<tr><th>i18n:govoplan-ops.profile.ff4fc027</th><th>i18n:govoplan-ops.status.bae7d5be</th><th>i18n:govoplan-ops.components.9289473e</th><th>i18n:govoplan-ops.fit.dab564d8</th></tr>
</thead>
<tbody>
{profiles.map((profile) =>
<tr key={profile.id}>
<td><strong>{profile.name}</strong><span className="muted block">{profile.id}</span></td>
<td><StatusBadge status={profile.current ? "success" : "inactive"} label={profile.current ? "current" : "reference"} /></td>
<td>{profile.components.join(", ")}</td>
<td>{profile.fit}</td>
</tr>
)}
</tbody>
</table>
</div>);
} }
function SizingTable({ items }: {items: OpsSizingAssumption[];}) { function SizingTable({ items }: {items: OpsSizingAssumption[];}) {
if (!items.length) return <p className="muted">i18n:govoplan-ops.no_sizing_assumptions_reported.17515959</p>; if (!items.length) return <p className="muted">i18n:govoplan-ops.no_sizing_assumptions_reported.17515959</p>;
const columns: DataGridColumn<OpsSizingAssumption>[] = [
{ id: "area", header: "i18n:govoplan-ops.area.2745deba", width: "minmax(180px, .7fr)", minWidth: 160, resizable: true, sortable: true, filterable: true, value: (item) => item.area, render: (item) => <strong>{item.area}</strong> },
{ id: "baseline", header: "i18n:govoplan-ops.baseline.e6ab7982", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => item.baseline },
{ id: "trigger", header: "i18n:govoplan-ops.scale_trigger.1c85e10e", width: "minmax(220px, 1fr)", minWidth: 180, resizable: true, filterable: true, value: (item) => item.scale_trigger },
{ id: "note", header: "i18n:govoplan-ops.operator_note.1dc58f7b", width: "minmax(240px, 1.2fr)", minWidth: 200, resizable: true, filterable: true, value: (item) => item.operator_note }
];
return ( return (
<div className="admin-table-wrap"> <DataGrid id="ops-sizing-assumptions" rows={items} columns={columns} getRowKey={(item) => item.area} />);
<table className="admin-table">
<thead>
<tr><th>i18n:govoplan-ops.area.2745deba</th><th>i18n:govoplan-ops.baseline.e6ab7982</th><th>i18n:govoplan-ops.scale_trigger.1c85e10e</th><th>i18n:govoplan-ops.operator_note.1dc58f7b</th></tr>
</thead>
<tbody>
{items.map((item) =>
<tr key={item.area}>
<td><strong>{item.area}</strong></td>
<td>{item.baseline}</td>
<td>{item.scale_trigger}</td>
<td>{item.operator_note}</td>
</tr>
)}
</tbody>
</table>
</div>);
} }

View File

@@ -14,6 +14,7 @@ const dashboardWidgets: DashboardWidgetsUiCapability = {
widgets: [ widgets: [
{ {
id: "ops.health", id: "ops.health",
surfaceId: "ops.widget.health",
title: "Operations health", title: "Operations health",
description: "Readiness, worker mode, and current warning count.", description: "Readiness, worker mode, and current warning count.",
moduleId: "ops", moduleId: "ops",
@@ -34,6 +35,9 @@ export const opsModule: PlatformWebModule = {
dependencies: ["access"], dependencies: ["access"],
optionalDependencies: ["audit", "docs", "notifications"], optionalDependencies: ["audit", "docs", "notifications"],
translations, translations,
viewSurfaces: [
{ id: "ops.widget.health", moduleId: "ops", kind: "section", label: "Operations health widget", order: 100 }
],
navItems: [{ to: "/ops", label: "i18n:govoplan-ops.ops.907a54c2", iconName: "activity", anyOf: opsReadScopes, order: 890 }], navItems: [{ to: "/ops", label: "i18n:govoplan-ops.ops.907a54c2", iconName: "activity", anyOf: opsReadScopes, order: 890 }],
routes: [ routes: [
{ path: "/ops", anyOf: opsReadScopes, order: 890, render: ({ settings }) => createElement(OpsPage, { settings }) }], { path: "/ops", anyOf: opsReadScopes, order: 890, render: ({ settings }) => createElement(OpsPage, { settings }) }],