Extend the Deno + Playwright UI-automation POC to provision the erp-sandbox Dolibarr for the AI agent: - moduleSetup.ts: add enableApiModule(ctx) — toggles the REST API / Web services module on /admin/modules.php (kanban). Resilient: tries the fr_FR card label "API/Web services REST (serveur)" first, falls back to a /API.*REST|REST.*API/i title match if the exact label is absent. - userSetup.ts (new): createUser (returns the new numeric id), assignRights (clicks each addrights link on /user/perms.php, idempotent), generateApiKey (triggers Dolibarr's generate control on the user card and reads the value back). - provisionSandbox.ts (new entrypoint, main.ts untouched): login → enable API → create ai_agent_sandbox (non-admin) → grant write rights → generate API key, then write the key to test/.ai_agent_sandbox.key (gitignored) instead of printing it. - .gitignore (new), .env.example + README.md: sandbox vars, the deno run --allow-all provisionSandbox.ts command, and kubectl one-liners to pull DOLI_ADMIN_PASSWORD (secretkv) / DOLI_DB_PASSWORD (vso-db-credentials) from the erp-sandbox namespace. Why UI not SQL: API keys are encrypted with the instance's DOLI_INSTANCE_UNIQUE_ID, so the key must be generated by the sandbox itself, not INSERTed raw. deno check passes for provisionSandbox.ts and scripts/admin/userSetup.ts. NOT run end-to-end: the sandbox Dolibarr is not installed yet (empty DB / fresh install wizard), so the selectors are best-effort Dolibarr 22 conventions and must be confirmed on the first real run. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
104 lines
3.3 KiB
TypeScript
104 lines
3.3 KiB
TypeScript
// info-box
|
|
|
|
import { Locator, Page } from "playwright";
|
|
import forms from "../forms.ts";
|
|
import login from "../login.ts";
|
|
|
|
// span.info-box-title has-text /moduleName/
|
|
// span.fa-cog[title="Configuration"]
|
|
|
|
type ModuleCtx = {
|
|
page: Page;
|
|
dolibarrAddress: string;
|
|
adminCredentials: { username: string; password: string };
|
|
};
|
|
|
|
async function configureModule(
|
|
globalCtx: ModuleCtx,
|
|
{
|
|
moduleName,
|
|
enabled,
|
|
onConfiguration,
|
|
}: {
|
|
moduleName: string;
|
|
enabled: boolean;
|
|
onConfiguration?: CallableFunction;
|
|
}
|
|
): Promise<void> {
|
|
const {page, dolibarrAddress}= globalCtx;
|
|
await login.doAdminLogin(globalCtx);
|
|
|
|
await page.goto(`${dolibarrAddress}/admin/modules.php?mode=commonkanban`);
|
|
|
|
const moduleDiv = page.locator('.info-box', {
|
|
has: page.locator('.info-box-title',{
|
|
hasText: new RegExp('^'+moduleName+'\n?$','i')
|
|
})
|
|
});
|
|
|
|
await forms.toggleOnOff(moduleDiv, enabled);
|
|
if(enabled && onConfiguration!==undefined) {
|
|
await moduleDiv.locator(`span.fa-cog[title="Configuration"]`).click();
|
|
await onConfiguration();
|
|
}
|
|
}
|
|
|
|
/*
|
|
Enable Dolibarr's REST API / Web services module.
|
|
|
|
Activation maps to llx_const.MAIN_MODULE_API=1; in the UI this is the module
|
|
card on /admin/modules.php (kanban mode). In fr_FR the card title is
|
|
"API/Web services REST (serveur)" (family "Interfaces").
|
|
|
|
NOTE: confirm the exact card title on the first real run against an installed
|
|
sandbox — the label has not been verified live here. To stay resilient we try
|
|
the known fr_FR label first (anchored exact match, same as configureModule),
|
|
and if that card is not present we fall back to matching ANY module card whose
|
|
title contains "API ... REST" / "REST ... API" in either order.
|
|
*/
|
|
async function enableApiModule(globalCtx: ModuleCtx): Promise<void> {
|
|
const {page, dolibarrAddress}= globalCtx;
|
|
|
|
// fr_FR label as observed in the module catalogue. Confirm on first real run.
|
|
const knownLabel = "API/Web services REST (serveur)";
|
|
|
|
await login.doAdminLogin(globalCtx);
|
|
await page.goto(`${dolibarrAddress}/admin/modules.php?mode=commonkanban`);
|
|
|
|
const exactCard = page.locator('.info-box', {
|
|
has: page.locator('.info-box-title',{
|
|
hasText: new RegExp('^'+knownLabel+'\n?$','i')
|
|
})
|
|
});
|
|
|
|
if(await exactCard.count() > 0) {
|
|
await forms.toggleOnOff(exactCard, true);
|
|
return;
|
|
}
|
|
|
|
// Fallback: the exact fr_FR label was not found (different Dolibarr version,
|
|
// locale, or wording). Match any card whose title looks like the REST API
|
|
// module regardless of word order.
|
|
const fuzzyCard = page.locator('.info-box', {
|
|
has: page.locator('.info-box-title',{
|
|
hasText: /API.*REST|REST.*API/i
|
|
})
|
|
});
|
|
|
|
if(await fuzzyCard.count() === 0) {
|
|
throw new Error(
|
|
`REST API module card not found on ${dolibarrAddress}/admin/modules.php `+
|
|
`(tried exact label "${knownLabel}" and /API.*REST|REST.*API/i). `+
|
|
`Confirm the card title in the running sandbox.`,
|
|
);
|
|
}
|
|
|
|
// If several cards match the fuzzy pattern, toggle the first one only.
|
|
await forms.toggleOnOff(fuzzyCard.first() as Locator, true);
|
|
}
|
|
|
|
export default {
|
|
configureModule,
|
|
enableApiModule,
|
|
}
|