WebUI module split; docs

This commit is contained in:
2026-06-24 10:19:21 +02:00
parent 7f25a3ccdf
commit 841d0b4024
3 changed files with 100 additions and 12 deletions

View File

@@ -1,18 +1,59 @@
# govoplan-campaign
GovOPlaN Campaign module.
GovOPlaN Campaign is the campaign authoring, validation, review, sending-control, and reporting module. It bundles backend campaign APIs with the campaign WebUI package.
This repository owns the Campaign module manifest, backend router, campaign schemas, campaign ORM models, campaign JSON validation, persistence, reports, message building, mock-send, queue/send control services, and campaign/operator/template/address-book WebUI package. The module currently has hard runtime dependencies on `govoplan-files` and `govoplan-mail` because managed attachments and SMTP/IMAP execution are imported directly.
## Ownership
The remaining `app.*` imports are transitional core adapters for auth, audit, Celery dispatch, and legacy access models.
This repository owns:
- backend module manifest `campaigns`
- campaign, recipient, report, and campaign-audit permissions
- campaign/version/job/issue/send-attempt/append-attempt models and migrations
- campaign JSON schema, validation, message building, attachment resolution, ZIP handling, reports, queue/control services, and mock-send paths
- WebUI package `@govoplan/campaign-webui`
- route contributions for `/campaigns`, `/campaigns/:campaignId/*`, `/operator`, `/reports`, `/address-book`, and `/templates`
Core owns auth, tenants, RBAC evaluation, database/session primitives, CSRF/API helpers, shell layout, and route rendering. Files and mail own their respective storage and transport capabilities.
## Dependencies
The module has runtime dependencies on:
- `govoplan-core` for platform services
- `govoplan-files` for managed attachment integration
- `govoplan-mail` for SMTP/IMAP profile and delivery integration
Optional UI behavior can still check installed module availability through core module metadata, but the current backend package declares files and mail as required.
## Development
Install through the core development environment:
Install through the core environment:
```bash
cd /mnt/DATA/git/govoplan-core
./.venv/bin/python -m pip install -r requirements-dev.txt
```
The backend module is registered through the `govoplan.modules` entry point `campaigns`. The frontend package is `@govoplan/campaign-webui`.
Run the WebUI from the core host:
```bash
cd /mnt/DATA/git/govoplan-core/webui
PATH=/home/zemion/.nvm/versions/node/v22.22.3/bin:$PATH /home/zemion/.nvm/versions/node/v22.22.3/bin/npm run dev
```
## Module integration
Backend entry point:
```toml
[project.entry-points."govoplan.modules"]
campaigns = "govoplan_campaign.backend.manifest:get_manifest"
```
Frontend package:
```text
@govoplan/campaign-webui
```
Platform RBAC and governance rules are documented in `govoplan-core/docs/`.

View File

@@ -114,7 +114,7 @@ manifest = ModuleManifest(
route_factory=_campaigns_router,
role_templates=ROLE_TEMPLATES,
nav_items=(
NavItem(path="/campaigns", label="Campaigns", icon="mail-check", required_any=("campaigns:campaign:read",), order=20),
NavItem(path="/campaigns", label="Campaigns", icon="mail", required_any=("campaigns:campaign:read",), order=20),
NavItem(
path="/operator",
label="Operator Queue",
@@ -128,13 +128,13 @@ manifest = ModuleManifest(
),
order=30,
),
NavItem(path="/reports", label="Reports", icon="file-text", required_any=("campaigns:report:read",), order=70),
NavItem(path="/reports", label="Reports", icon="reports", required_any=("campaigns:report:read",), order=70),
),
frontend=FrontendModule(
module_id="campaigns",
package_name="@govoplan/campaign-webui",
nav_items=(
NavItem(path="/campaigns", label="Campaigns", icon="mail-check", required_any=("campaigns:campaign:read",), order=20),
NavItem(path="/campaigns", label="Campaigns", icon="mail", required_any=("campaigns:campaign:read",), order=20),
NavItem(
path="/operator",
label="Operator Queue",
@@ -148,7 +148,7 @@ manifest = ModuleManifest(
),
order=30,
),
NavItem(path="/reports", label="Reports", icon="file-text", required_any=("campaigns:report:read",), order=70),
NavItem(path="/reports", label="Reports", icon="reports", required_any=("campaigns:report:read",), order=70),
NavItem(path="/address-book", label="Address Book", icon="users", order=80),
NavItem(path="/templates", label="Templates", icon="form", order=90),
),

View File

@@ -1,5 +1,17 @@
import { Activity, FileText, Form, MailCheck, Users } from "lucide-react";
import type { PlatformWebModule } from "@govoplan/core-webui";
import { createElement, lazy, useCallback } from "react";
import { useParams } from "react-router-dom";
import { Card, ResourceAccessBoundary, type ApiSettings, type AuthInfo, type PlatformWebModule } from "@govoplan/core-webui";
import { getCampaign } from "./api/campaigns";
const AddressBookPage = lazy(() => import("./features/addressbook/AddressBookPage"));
const CampaignListPage = lazy(() => import("./features/campaigns/CampaignListPage"));
const CampaignWorkspace = lazy(() => import("./features/campaigns/CampaignWorkspace"));
const OperatorQueuePage = lazy(() => import("./features/operator/OperatorQueuePage"));
const TemplatesPage = lazy(() => import("./features/templates/TemplatesPage"));
const campaignRead = ["campaigns:campaign:read"];
const operatorScopes = ["campaigns:campaign:queue", "campaigns:campaign:retry", "campaigns:campaign:reconcile", "campaigns:campaign:control", "campaigns:campaign:send"];
export const campaignModule: PlatformWebModule = {
id: "campaigns",
@@ -8,18 +20,53 @@ export const campaignModule: PlatformWebModule = {
dependencies: ["access"],
optionalDependencies: ["files", "mail"],
navItems: [
{ to: "/campaigns", label: "Campaigns", icon: MailCheck, anyOf: ["campaigns:campaign:read"], order: 20 },
{ to: "/campaigns", label: "Campaigns", icon: MailCheck, anyOf: campaignRead, order: 20 },
{
to: "/operator",
label: "Operator Queue",
icon: Activity,
anyOf: ["campaigns:campaign:queue", "campaigns:campaign:retry", "campaigns:campaign:reconcile", "campaigns:campaign:control", "campaigns:campaign:send"],
anyOf: operatorScopes,
order: 30
},
{ to: "/reports", label: "Reports", icon: FileText, anyOf: ["campaigns:report:read"], order: 70 },
{ to: "/address-book", label: "Address Book", icon: Users, order: 80 },
{ to: "/templates", label: "Templates", icon: Form, order: 90 }
],
routes: [
{ path: "/campaigns", anyOf: campaignRead, order: 20, render: ({ settings }) => createElement(CampaignListPage, { settings }) },
{ path: "/campaigns/:campaignId/*", anyOf: campaignRead, order: 21, render: ({ settings, auth }) => createElement(CampaignResourceRoute, { settings, auth }) },
{ path: "/operator", anyOf: operatorScopes, order: 30, render: ({ settings }) => createElement(OperatorQueuePage, { settings }) },
{ path: "/reports", anyOf: ["campaigns:report:read"], order: 70, render: () => createElement(ReportsPage) },
{ path: "/address-book", order: 80, render: () => createElement(AddressBookPage) },
{ path: "/templates", order: 90, render: () => createElement(TemplatesPage) }
]
};
function CampaignResourceRoute({ settings, auth }: { settings: ApiSettings; auth: AuthInfo }) {
const { campaignId = "" } = useParams();
const tenantId = (auth.active_tenant ?? auth.tenant).id;
const probe = useCallback(() => getCampaign(settings, campaignId), [settings.apiBaseUrl, settings.apiKey, settings.accessToken, campaignId, tenantId]);
return createElement(ResourceAccessBoundary, {
probe,
resetKey: `${tenantId}:${campaignId}`,
fallback: "/campaigns",
children: createElement(CampaignWorkspace, { settings, auth })
});
}
function ReportsPage() {
return createElement(
"div",
{ className: "content-pad" },
createElement(
"div",
{ className: "page-heading" },
createElement("h1", null, "Reports"),
createElement("p", null, "This module is prepared but not implemented yet.")
),
createElement(Card, null, createElement("p", { className: "muted" }, "Next passes will add functionality here."))
);
}
export default campaignModule;