mirror of
https://github.com/kevinveenbirkenbach/computer-playbook.git
synced 2026-11-08 11:46:35 +00:00
feat(web-app-xwiki): carry the palette through an own color theme
XWiki compiles its skin on the server from a color theme, so the palette goes where the app reads it: - A role task keeps a theme page "Infinito" under FlamingoThemes. Its object is derived from the stock theme plus the light palette and is written only when it differs. The lockup for the navbar is attached to that page, the theme becomes the wiki default and the wiki title takes the configured one. - The preferences the wiki had before are stored on the theme page. A deploy with the design service off puts them back. - A value less4j cannot type breaks the whole skin stylesheet, so the task asserts six-digit hex colors before it writes. - The compiled theme knows one mode. The role stylesheet maps the compiled skin colors onto the design tokens and so supplies the dark mode. Its image URLs carry the origin of the wiki, because the sheet is served from the CDN and a relative target would resolve there. Without SSO and LDAP the administrator had no password and the member object of the admin group had a shape XWiki ignored, so nobody could administer the wiki and the signed-in interface could not be verified. The role now sets the password when the sign-in fails and writes the member once. XWiki answers a wrong basic auth as guest with 200, so the probe reads the XWiki-User response header. The spec asserts tokens, contrast, logo and title in both modes, the theme without the stylesheet, the administrator's sign-in, and that every image the stylesheet references is served. Findings outside the design are listed in TODO.md. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
9a48dbfd83
commit
b1dfff370a
20 files changed
+6479
-3
No files matched your search
@@ -135,6 +135,7 @@ The OIDC/LDAP runtime configuration (provider URLs, bind DN, secrets) lives in t
|
||||
- [XWiki Official Website](https://www.xwiki.org/)
|
||||
- [XWiki Documentation](https://www.xwiki.org/xwiki/bin/view/Documentation/)
|
||||
- [XWiki GitHub Repository](https://github.com/xwiki/xwiki-platform)
|
||||
- [Corporate design review: before/after screenshots in light, dark, desktop and mobile](https://claude.ai/artifact/F8XKp2u85ENaT8NVoVTmg6)
|
||||
|
||||
## Persona contract opt-outs
|
||||
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
# TODO
|
||||
|
||||
- `tasks/02_manager_ops.yml:30` passes `XWIKI_SUPERADMIN_PASSWORD` as an argument of `shell/debug_superadmin.sh`, so the secret is in the process list of the stack host and in the deploy log while that task runs. Options: feed it through stdin, hand the script a file path, or let the script read it from the container environment.
|
||||
- `templates/compose.yml.j2:28` puts `XWIKI_LDAP_BIND_PASS` into `JAVA_OPTS` as a `-D` system property, so it is visible in the process list of the container. Options: write it into `xwiki.cfg` or `xwiki.properties` from the entrypoint, or mount it as a file the configuration reads.
|
||||
- `tasks/03_administrator.yml` posted the group member object on every deploy with a body XWiki ignored (`<properties>` wrapper, text outside `<value>`), so an existing installation carries one empty `XWiki.XWikiGroups` object on `XWiki.XWikiAdminGroup` per earlier deploy (measured: two). The post now carries the member and runs once. Options: delete the empty member objects through REST, or leave them (they grant nothing).
|
||||
- `templates/xml/user/xwikiusers_object.xml.j2` has the same shape, so first name, last name, email and `active` of the administrator are not stored (measured: `first_name` empty). Options: write the properties as `<property name="..."><value>...</value></property>` directly below `<object>`, and update an existing object with a `PUT` on the object.
|
||||
- `templates/playwright.env.j2` blocks the administrator persona with the reason that the account has no password. Without SSO and LDAP the role now sets one. Options: unblock the persona for that case and teach the admin flow the native sign-in form, or keep the block and reword its reason.
|
||||
@@ -4,6 +4,8 @@ const { resolveTimeout } = require("./timeouts");
|
||||
const { decodeDotenvQuotedValue, normalizeBaseUrl, runAdminFlow, runBiberFlow, runGuestFlow , expectHstsWhenTls, gotoOnion } = require("./personas");
|
||||
test.use({ ignoreHTTPSErrors: true });
|
||||
|
||||
require("./test-design").register();
|
||||
|
||||
const appBaseUrl = normalizeBaseUrl(process.env.APP_BASE_URL || "");
|
||||
const canonicalDomain = decodeDotenvQuotedValue(process.env.CANONICAL_DOMAIN || "");
|
||||
|
||||
|
||||
@@ -0,0 +1,341 @@
|
||||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
const {
|
||||
assertDesignTokens,
|
||||
assertLightAndDark,
|
||||
assertReadable,
|
||||
assertToken,
|
||||
captureDesignGallery,
|
||||
galleryEnabled,
|
||||
tokenValue,
|
||||
} = require("./design");
|
||||
const { apiFetchOnion, apiGetOnion, decodeDotenvQuotedValue, gotoOnion, normalizeBaseUrl } = require("./personas");
|
||||
const { skipUnlessServiceEnabled } = require("./service-gating");
|
||||
const { resolveTimeout } = require("./timeouts");
|
||||
|
||||
const base = normalizeBaseUrl(process.env.APP_BASE_URL);
|
||||
const adminUsername = decodeDotenvQuotedValue(process.env.ADMIN_USERNAME);
|
||||
const adminPassword = decodeDotenvQuotedValue(process.env.ADMIN_PASSWORD);
|
||||
const SHOWCASE = "Sandbox/DesignShowcase";
|
||||
const SHOWCASE_REST = `${base}/rest/wikis/xwiki/spaces/Sandbox/pages/DesignShowcase`;
|
||||
const ADMIN = "/bin/admin/XWiki/XWikiPreferences?editor=globaladmin§ion=";
|
||||
const SEARCH = "/bin/view/Main/Search?f_type=DOCUMENT&f_locale=en&f_locale=&r=1&text=";
|
||||
const INJECTED_SNIPPETS = /<!--infinito-inj-->[\s\S]*?<!--\/infinito-inj-->/g;
|
||||
const NAVBAR = ".navbar";
|
||||
|
||||
function adminAuth() {
|
||||
return `Basic ${Buffer.from(`${adminUsername}:${adminPassword}`).toString("base64")}`;
|
||||
}
|
||||
|
||||
async function open(page, path, selector) {
|
||||
await gotoOnion(page, `${base}${path}`);
|
||||
await expect(page.locator(selector).first()).toBeVisible({ timeout: resolveTimeout(120_000) });
|
||||
}
|
||||
|
||||
async function openHome(page) {
|
||||
await open(page, "/bin/view/Main/", "#xwikicontent");
|
||||
}
|
||||
|
||||
async function signIn(page) {
|
||||
test.skip(
|
||||
process.env.SSO_SERVICE_ENABLED === "true" || process.env.LDAP_SERVICE_ENABLED === "true",
|
||||
"the local administrator exists only without SSO and LDAP",
|
||||
);
|
||||
await open(page, "/bin/login/XWiki/XWikiLogin", "#j_password");
|
||||
await page.locator("#j_username").fill(adminUsername);
|
||||
await page.locator("#j_password").fill(adminPassword);
|
||||
await page.locator("#loginForm input[type='submit']").click();
|
||||
await expect(page.locator("html[data-xwiki-user-reference]")).toHaveCount(1, { timeout: resolveTimeout(120_000) });
|
||||
}
|
||||
|
||||
async function seedShowcase(request) {
|
||||
const headers = { Authorization: adminAuth(), "Content-Type": "application/xml", Accept: "application/json" };
|
||||
const page = (text) =>
|
||||
`<page xmlns="http://www.xwiki.org"><title>Design Showcase</title><syntax>xwiki/2.1</syntax><content><![CDATA[${text}]]></content></page>`;
|
||||
const first = "== Showcase ==\n\nA paragraph with a [[link to the home page>>Main.WebHome]].";
|
||||
const second = [
|
||||
first,
|
||||
"{{info}}An information box.{{/info}}",
|
||||
"{{success}}A success box.{{/success}}",
|
||||
"{{warning}}A warning box.{{/warning}}",
|
||||
"{{error}}An error box.{{/error}}",
|
||||
"|=Name|=Value\n|One|1\n|Two|2\n|Three|3",
|
||||
'{{code language="js"}}const answer = 42;{{/code}}',
|
||||
"----",
|
||||
"* first item\n* second item",
|
||||
].join("\n\n");
|
||||
const existing = await apiGetOnion(request, SHOWCASE_REST, { headers });
|
||||
if (existing.status() === 404) {
|
||||
for (const text of [first, second]) {
|
||||
const saved = await apiFetchOnion(request, SHOWCASE_REST, { method: "PUT", headers, data: page(text) });
|
||||
expect(saved.ok(), `showcase page save answers ${saved.status()}`).toBe(true);
|
||||
}
|
||||
}
|
||||
const attachments = await (await apiGetOnion(request, `${SHOWCASE_REST}/attachments`, { headers })).json();
|
||||
if (attachments.attachments.length === 0) {
|
||||
const attached = await apiFetchOnion(request, `${SHOWCASE_REST}/attachments/showcase.txt`, {
|
||||
method: "PUT",
|
||||
headers: { Authorization: adminAuth(), "Content-Type": "text/plain" },
|
||||
data: "A showcase attachment.",
|
||||
});
|
||||
expect(attached.ok(), `showcase attachment answers ${attached.status()}`).toBe(true);
|
||||
}
|
||||
const comments = await (await apiGetOnion(request, `${SHOWCASE_REST}/comments`, { headers })).json();
|
||||
if (comments.comments.length === 0) {
|
||||
const commented = await apiFetchOnion(request, `${SHOWCASE_REST}/comments`, {
|
||||
method: "POST",
|
||||
headers,
|
||||
data: '<comment xmlns="http://www.xwiki.org"><text>A showcase comment.</text></comment>',
|
||||
});
|
||||
expect(commented.ok(), `showcase comment answers ${commented.status()}`).toBe(true);
|
||||
}
|
||||
}
|
||||
|
||||
async function waitForPanel(panel) {
|
||||
await expect(panel).toBeVisible({ timeout: resolveTimeout(15_000) });
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
panel.evaluate(
|
||||
(el) =>
|
||||
el.getAnimations({ subtree: true }).filter((animation) => animation.playState === "running").length === 0 &&
|
||||
getComputedStyle(el).opacity === "1",
|
||||
),
|
||||
{ timeout: resolveTimeout(15_000) },
|
||||
)
|
||||
.toBe(true);
|
||||
}
|
||||
|
||||
async function dismissTour(page) {
|
||||
const close = page.locator(".popover.tour a.btn-default").first();
|
||||
await close
|
||||
.waitFor({ state: "visible", timeout: resolveTimeout(5_000) })
|
||||
.then(() => close.click())
|
||||
.catch(() => {});
|
||||
await expect(page.locator(".popover.tour")).toHaveCount(0);
|
||||
}
|
||||
|
||||
async function stripInjected(route) {
|
||||
if (route.request().resourceType() !== "document") return route.continue();
|
||||
const response = await route.fetch();
|
||||
return route.fulfill({ response, body: (await response.text()).replace(INJECTED_SNIPPETS, "") });
|
||||
}
|
||||
|
||||
function hex(rgb) {
|
||||
return `#${rgb.match(/\d+/g).slice(0, 3).map((channel) => Number(channel).toString(16).padStart(2, "0")).join("")}`;
|
||||
}
|
||||
|
||||
async function currentThemePath(page) {
|
||||
const href = await page.locator("link[href*='/skins/flamingo/style']").first().getAttribute("href");
|
||||
return new URL(href, base).searchParams.get("colorTheme").replace(/^xwiki:/, "").replace(".", "/");
|
||||
}
|
||||
|
||||
exports.register = function () {
|
||||
test("design: corporate tokens apply in light and dark mode", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await openHome(page);
|
||||
await assertDesignTokens(page, "xwiki");
|
||||
});
|
||||
|
||||
test("design: page, navbar, text and links take the palette", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await openHome(page);
|
||||
for (const colorScheme of ["light", "dark"]) {
|
||||
await page.emulateMedia({ colorScheme });
|
||||
await assertToken(page, "body", "background-color", "--design-surface-1", `xwiki page ${colorScheme}`);
|
||||
await assertToken(page, "#mainContentArea", "background-color", "--design-surface-2", `xwiki content ${colorScheme}`);
|
||||
await assertToken(page, "#xwikicontent", "color", "--design-text", `xwiki body text ${colorScheme}`);
|
||||
await assertToken(page, "#xwikicontent a", "color", "--design-link", `xwiki link ${colorScheme}`);
|
||||
await assertToken(page, NAVBAR, "background-color", "--design-frame", `xwiki navbar ${colorScheme}`);
|
||||
await assertToken(page, "#tmDrawerActivator", "color", "--design-on-frame", `xwiki navbar text ${colorScheme}`);
|
||||
}
|
||||
await page.emulateMedia({ colorScheme: null });
|
||||
await assertLightAndDark(page, "#xwikicontent", "xwiki");
|
||||
await assertReadable(page, ["#xwikicontent", "#xwikicontent a", "#tmDrawerActivator"], "xwiki");
|
||||
});
|
||||
|
||||
test("design: every image the role stylesheet references is served", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await openHome(page);
|
||||
const sheet = await page.locator("link[href*='/roles/web-app-xwiki/'][href*='style.css']").first().getAttribute("href");
|
||||
const css = await (await apiGetOnion(page.request, sheet)).text();
|
||||
const targets = [...new Set([...css.matchAll(/url\(\s*["']?([^"')]+)["']?\s*\)/g)].map((match) => match[1]))]
|
||||
.filter((target) => !target.startsWith("data:"))
|
||||
.map((target) => new URL(target, sheet).href);
|
||||
expect(targets.length, "the role stylesheet references images").toBeGreaterThan(0);
|
||||
for (const target of targets) {
|
||||
const served = await apiGetOnion(page.request, target);
|
||||
expect(
|
||||
`${served.status()} ${served.headers()["content-type"]}`,
|
||||
`${target} must be served as an image; a relative url() resolves against the stylesheet on the CDN`,
|
||||
).toMatch(/^200 image\//);
|
||||
}
|
||||
});
|
||||
|
||||
test("design: the color theme carries the light palette without the role stylesheet", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await page.emulateMedia({ colorScheme: "light" });
|
||||
await openHome(page);
|
||||
const expected = {
|
||||
frame: await tokenValue(page, "--design-frame", "background-color"),
|
||||
surface: await tokenValue(page, "--design-surface-1", "background-color"),
|
||||
text: await tokenValue(page, "--design-text", "color"),
|
||||
};
|
||||
await page.route("**/*", stripInjected);
|
||||
await openHome(page);
|
||||
await expect(page.locator("link[href*='_shared/css/default.css']"), "the injected sheets are stripped").toHaveCount(0);
|
||||
expect(await currentThemePath(page), "the skin sheet is compiled for the corporate theme").toBe(
|
||||
decodeDotenvQuotedValue(process.env.DESIGN_THEME_REFERENCE).replace(".", "/"),
|
||||
);
|
||||
const style = (selector, property) =>
|
||||
page.locator(selector).first().evaluate((el, prop) => getComputedStyle(el).getPropertyValue(prop), property);
|
||||
expect(await style(NAVBAR, "background-color"), "the theme paints the navbar in the frame color").toBe(expected.frame);
|
||||
expect(await style("body", "background-color"), "the theme paints the page in surface-1").toBe(expected.surface);
|
||||
expect(await style("#xwikicontent", "color"), "the theme sets the body text color").toBe(expected.text);
|
||||
});
|
||||
|
||||
test("design: primary action reads on-primary, fields and dividers read the border tokens", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await open(page, `${SEARCH}xwiki`, ".search-results");
|
||||
for (const colorScheme of ["light", "dark"]) {
|
||||
await page.emulateMedia({ colorScheme });
|
||||
await assertToken(page, ".btn-primary", "background-color", "--design-primary", `xwiki primary ${colorScheme}`);
|
||||
await assertToken(page, ".btn-primary", "color", "--design-on-primary", `xwiki on-primary ${colorScheme}`);
|
||||
await assertToken(page, "input.form-control", "border-top-color", "--design-border-strong", `xwiki field ${colorScheme}`);
|
||||
}
|
||||
await open(page, "/bin/view/Main/", ".xwikitabbar");
|
||||
for (const colorScheme of ["light", "dark"]) {
|
||||
await page.emulateMedia({ colorScheme });
|
||||
await assertToken(page, ".xwikitabbar", "border-bottom-color", "--design-border", `xwiki tab divider ${colorScheme}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("design: search facets take their surface and text from the tokens", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await open(page, `${SEARCH}xwiki`, ".search-facet-header label");
|
||||
for (const colorScheme of ["light", "dark"]) {
|
||||
await page.emulateMedia({ colorScheme });
|
||||
await assertToken(page, ".search-facet-header", "background-color", "--design-surface-3", `xwiki facet header ${colorScheme}`);
|
||||
await assertToken(page, ".search-facet-header label", "color", "--design-text", `xwiki facet label ${colorScheme}`);
|
||||
}
|
||||
await page.emulateMedia({ colorScheme: null });
|
||||
await assertReadable(page, [".search-facet-header label"], "xwiki search facets");
|
||||
});
|
||||
|
||||
test("design: focus stops inside the navbar draw in the frame text color", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await open(page, `${SEARCH}xwiki`, ".search-results");
|
||||
const onFrame = await tokenValue(page, "--design-on-frame", "outline-color");
|
||||
let stops = 0;
|
||||
for (let step = 0; step < 25; step += 1) {
|
||||
await page.keyboard.press("Tab");
|
||||
const outline = await page.evaluate((navbar) => {
|
||||
const el = document.activeElement;
|
||||
if (!el || !el.closest(navbar)) return null;
|
||||
const style = getComputedStyle(el);
|
||||
return { color: style.outlineColor, style: style.outlineStyle };
|
||||
}, NAVBAR);
|
||||
if (!outline) continue;
|
||||
stops += 1;
|
||||
expect(outline.style, "a focus stop inside the navbar draws an outline").not.toBe("none");
|
||||
expect(outline.color, "focus outline inside the navbar").toBe(onFrame);
|
||||
}
|
||||
expect(stops, "the Tab walk reaches the navbar").toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("design: logo and title are the configured ones", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await openHome(page);
|
||||
const title = decodeDotenvQuotedValue(process.env.DESIGN_TITLE);
|
||||
if (title) {
|
||||
expect((await page.title()).endsWith(` - ${title}`), `the page title '${await page.title()}' ends with the design title`).toBe(true);
|
||||
}
|
||||
const logoUrl = decodeDotenvQuotedValue(process.env.DESIGN_LOGO_URL);
|
||||
test.skip(!logoUrl, "the logo replacement is disabled");
|
||||
const logo = page.locator("#companylogo img");
|
||||
await expect(logo).toBeVisible();
|
||||
const box = await logo.boundingBox();
|
||||
expect(box.width, "the navbar logo is a lockup, wider than high").toBeGreaterThan(box.height * 1.5);
|
||||
const src = await logo.evaluate((img) => img.src);
|
||||
expect(decodeURIComponent(src), "the logo is the attachment of the corporate theme page").toContain(
|
||||
`/${decodeDotenvQuotedValue(process.env.DESIGN_THEME_REFERENCE).replace(".", "/")}/`,
|
||||
);
|
||||
const served = await apiGetOnion(page.request, src);
|
||||
const generated = await apiGetOnion(page.request, logoUrl);
|
||||
expect(generated.ok(), `the generated logo answers ${generated.status()}`).toBe(true);
|
||||
expect(Buffer.compare(await served.body(), await generated.body()), "XWiki serves the generated lockup").toBe(0);
|
||||
const onFrame = hex(await tokenValue(page, "--design-on-frame", "color"));
|
||||
expect((await generated.text()).toLowerCase(), "the lockup text uses the frame text color").toContain(`fill="${onFrame}"`);
|
||||
});
|
||||
|
||||
test("design: the administrator signs in through the form and administers the wiki", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
await signIn(page);
|
||||
await expect(page.locator("html")).toHaveAttribute("data-xwiki-user-reference", `xwiki:XWiki.${adminUsername}`);
|
||||
await open(page, `${ADMIN}Themes`, "#admin-page-content form");
|
||||
await expect(page.locator("#admin-page-content select[name$='_colorTheme']")).toHaveValue(
|
||||
decodeDotenvQuotedValue(process.env.DESIGN_THEME_REFERENCE),
|
||||
);
|
||||
await assertReadable(page, ["#admin-page-content dt label", "#admin-page-content .xHint"], "xwiki administration form");
|
||||
await seedShowcase(page.request);
|
||||
await open(page, `/bin/view/${SHOWCASE}?viewer=history`, "#historycontent table td");
|
||||
for (const colorScheme of ["light", "dark"]) {
|
||||
await page.emulateMedia({ colorScheme });
|
||||
await assertToken(page, "#historycontent table td", "border-top-color", "--design-border", `xwiki divider ${colorScheme}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("design: gallery of wiki and administration views", async ({ page }) => {
|
||||
skipUnlessServiceEnabled("design");
|
||||
test.skip(!galleryEnabled(), "INFINITO_PLAYWRIGHT_KEEP is not true");
|
||||
test.setTimeout(resolveTimeout(2_400_000));
|
||||
const view = (name, path, selector, prepare) => ({
|
||||
name,
|
||||
url: `${base}${path}`,
|
||||
prepare: async (p) => {
|
||||
await expect(p.locator(selector).first()).toBeVisible({ timeout: resolveTimeout(120_000) });
|
||||
if (prepare) await prepare(p);
|
||||
},
|
||||
});
|
||||
const failures = [];
|
||||
await captureDesignGallery(page, [
|
||||
view("home", "/bin/view/Main/", "#xwikicontent", dismissTour),
|
||||
view("sign-in", "/bin/login/XWiki/XWikiLogin", "#loginForm #j_password"),
|
||||
]).catch((error) => failures.push(error.message));
|
||||
await signIn(page);
|
||||
await seedShowcase(page.request);
|
||||
const theme = await currentThemePath(page);
|
||||
await captureDesignGallery(page, [
|
||||
view("page-view", `/bin/view/${SHOWCASE}`, "#xwikicontent h2"),
|
||||
view("page-edit-wiki", `/bin/edit/${SHOWCASE}?editor=wiki`, ".buttons"),
|
||||
view("page-edit-wysiwyg", `/bin/edit/${SHOWCASE}?editor=wysiwyg`, ".cke_top"),
|
||||
view("page-history", `/bin/view/${SHOWCASE}?viewer=history`, "#historycontent table"),
|
||||
view("page-diff", `/bin/view/${SHOWCASE}?viewer=changes&rev1=1.1&rev2=2.1`, "#changescontent"),
|
||||
view("page-comments", `/bin/view/${SHOWCASE}?viewer=comments`, ".xwikicomment"),
|
||||
view("page-attachments", `/bin/view/${SHOWCASE}?viewer=attachments`, "#attachmentscontent"),
|
||||
view("page-rights", `/bin/edit/${SHOWCASE}?editor=rights`, '#usersandgroupstable :text("XWikiAllGroup")'),
|
||||
view("actions-menu-open", `/bin/view/${SHOWCASE}`, "#tmMoreActions .dropdown-toggle", async (p) => {
|
||||
await p.locator("#tmMoreActions .dropdown-toggle").click();
|
||||
await waitForPanel(p.locator("#tmMoreActions .dropdown-menu"));
|
||||
}),
|
||||
view("drawer-open", `/bin/view/${SHOWCASE}`, "#tmDrawerActivator", async (p) => {
|
||||
await p.locator("#tmDrawerActivator").click();
|
||||
await waitForPanel(p.locator("#tmDrawer"));
|
||||
}),
|
||||
view("search", `${SEARCH}showcase`, ".search-results"),
|
||||
view("page-index", "/bin/view/Main/AllDocs", ".liveData table tbody tr"),
|
||||
view("user-profile", `/bin/view/XWiki/${adminUsername}`, ".userInfo"),
|
||||
view("user-preferences", `/bin/view/XWiki/${adminUsername}?category=preferences`, "#preferencesPane"),
|
||||
view("admin-home", "/bin/admin/XWiki/XWikiPreferences", ".admin-menu"),
|
||||
view("admin-themes", `${ADMIN}Themes`, "#admin-page-content form"),
|
||||
view("admin-presentation", `${ADMIN}Presentation`, "#admin-page-content form"),
|
||||
view("admin-users", `${ADMIN}Users`, "#admin-page-content table tbody tr"),
|
||||
view("admin-groups", `${ADMIN}Groups`, "#admin-page-content table tbody tr"),
|
||||
view("admin-rights", `${ADMIN}Rights`, '#usersandgroupstable :text("XWikiAllGroup")'),
|
||||
view("admin-extensions", `${ADMIN}XWiki.Extensions`, "#admin-page-content form"),
|
||||
view("theme-editor", `/bin/edit/${theme}?editor=inline`, "#inline"),
|
||||
view("not-found", "/bin/view/Sandbox/DesignShowcaseMissing", ".xwikimessage"),
|
||||
]).catch((error) => failures.push(error.message));
|
||||
expect(failures, "every gallery view is captured").toEqual([]);
|
||||
});
|
||||
};
|
||||
@@ -84,6 +84,11 @@ design:
|
||||
bond: 1
|
||||
enabled: "{{ 'web-svc-design' in group_names }}"
|
||||
shared: "{{ 'web-svc-design' in group_names }}"
|
||||
slots:
|
||||
logo:
|
||||
width: 160
|
||||
height: 50
|
||||
frame: true
|
||||
prometheus:
|
||||
bond: 1
|
||||
enabled: "{{ 'web-app-prometheus' in group_names }}"
|
||||
|
||||
@@ -59,6 +59,9 @@
|
||||
- name: "✅ XWIKI | Ensure Main.WebHome exists"
|
||||
include_tasks: 05_main_webhome.yml
|
||||
|
||||
- name: "🎨 XWIKI | Reconcile the corporate color theme"
|
||||
include_tasks: 06_design.yml
|
||||
|
||||
- block:
|
||||
- name: "📁 Create Final Docker Compose File"
|
||||
stack_host_template:
|
||||
|
||||
@@ -39,6 +39,35 @@
|
||||
when: _users_obj_list.status == 404 or ('<object' not in (_users_obj_list.content | default('')))
|
||||
register: _user_obj_created
|
||||
|
||||
- name: "🔎 XWIKI | Probe the sign-in of {{ XWIKI_ADMIN_USER }}"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_XWIKI, 'spaces'] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_ADMIN_USER }}"
|
||||
url_password: "{{ XWIKI_ADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200]
|
||||
return_content: false
|
||||
register: _xwiki_admin_signin
|
||||
changed_when: false
|
||||
|
||||
- name: "🔑 XWIKI | Set the password of {{ XWIKI_ADMIN_USER }}"
|
||||
no_log: "{{ MASK_CREDENTIALS_IN_LOGS | bool }}"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_XWIKI_PAGES, XWIKI_ADMIN_USER | urlencode, 'objects/XWiki.XWikiUsers/0/properties/password'] | url_join }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [202]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/property.xml.j2') }}"
|
||||
vars:
|
||||
xwiki_property_value: "{{ XWIKI_ADMIN_PASSWORD }}"
|
||||
when: _xwiki_admin_signin.xwiki_user is not defined
|
||||
changed_when: true
|
||||
|
||||
- block:
|
||||
- name: "✅ XWIKI | Ensure {{ XWIKI_ADMIN_GROUP_PAGE }} page exists"
|
||||
uri_retry:
|
||||
@@ -50,6 +79,19 @@
|
||||
status_code: [201, 202, 204, 200]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/xwiki_admin_group/page.xml.j2') }}"
|
||||
- name: "🔎 XWIKI | Read the members of {{ XWIKI_ADMIN_GROUP_PAGE }}"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_XWIKI_PAGES, XWIKI_ADMIN_GROUP_PAGE, 'objects/XWiki.XWikiGroups'] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200]
|
||||
headers:
|
||||
Accept: application/json
|
||||
return_content: true
|
||||
register: _xwiki_admin_group_members
|
||||
changed_when: false
|
||||
- name: "✅ XWIKI | Ensure user is in {{ XWIKI_ADMIN_GROUP_PAGE }}"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_XWIKI_PAGES, XWIKI_ADMIN_GROUP_PAGE, 'objects'] | url_join }}"
|
||||
@@ -60,4 +102,8 @@
|
||||
status_code: [201, 200]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/xwiki_admin_group/member_object.xml.j2') }}"
|
||||
when: >-
|
||||
XWIKI_ADMIN_REFERENCE not in
|
||||
(_xwiki_admin_group_members.json.objectSummaries | map(attribute='headline') | list)
|
||||
changed_when: true
|
||||
when: not (XWIKI_SSO_ENABLED | bool)
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
- name: "⬇️ XWIKI | Fetch REST session + form token (design write ops)"
|
||||
include_tasks: utils/rest_session.yml
|
||||
|
||||
- name: "🔎 XWIKI | Read the wiki color theme preference"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_PREFERENCES, 'colorTheme'] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200]
|
||||
headers:
|
||||
Accept: application/json
|
||||
return_content: true
|
||||
register: _xwiki_design_color_theme
|
||||
changed_when: false
|
||||
|
||||
- name: "🔎 XWIKI | Read the wiki title preference"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_PREFERENCES, 'title'] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200]
|
||||
headers:
|
||||
Accept: application/json
|
||||
return_content: true
|
||||
register: _xwiki_design_title
|
||||
changed_when: false
|
||||
|
||||
- name: "🔎 XWIKI | Look for the corporate theme page"
|
||||
uri_retry:
|
||||
url: "{{ XWIKI_REST_THEME_PAGE }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200, 404]
|
||||
return_content: false
|
||||
register: _xwiki_design_page
|
||||
changed_when: false
|
||||
|
||||
- name: "🔎 XWIKI | Read the preferences the wiki had before the corporate theme"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_THEME_PAGE, 'objects/XWiki.XWikiPreferences/0'] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200, 404]
|
||||
headers:
|
||||
Accept: application/json
|
||||
return_content: true
|
||||
register: _xwiki_design_previous
|
||||
changed_when: false
|
||||
|
||||
- name: "🎨 XWIKI | Apply the corporate color theme"
|
||||
include_tasks: 06_design/apply.yml
|
||||
when: XWIKI_DESIGN_ENABLED | bool
|
||||
|
||||
- name: "🧹 XWIKI | Remove the corporate color theme"
|
||||
include_tasks: 06_design/remove.yml
|
||||
vars:
|
||||
xwiki_design_former: "{{ _xwiki_design_previous.json.properties | items2dict(key_name='name', value_name='value') }}"
|
||||
when:
|
||||
- not (XWIKI_DESIGN_ENABLED | bool)
|
||||
- _xwiki_design_previous.status == 200
|
||||
@@ -0,0 +1,93 @@
|
||||
---
|
||||
- name: "🛑 XWIKI | Refuse a theme page this role did not create"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- _xwiki_design_page.status == 404 or _xwiki_design_previous.status == 200
|
||||
fail_msg: >-
|
||||
The page {{ XWIKI_DESIGN_THEME_REFERENCE }} exists without the XWiki.XWikiPreferences object
|
||||
this role stores on it. Rename or delete that page, then deploy again.
|
||||
|
||||
- name: "✅ XWIKI | Assert the theme values are six-digit hex colors"
|
||||
ansible.builtin.assert:
|
||||
that:
|
||||
- XWIKI_DESIGN_THEME_COLORS.values() | reject('match', '^#[0-9a-fA-F]{6}$') | list | length == 0
|
||||
- XWIKI_DESIGN_LESS_COLORS.values() | reject('match', '^#[0-9a-fA-F]{6}$') | list | length == 0
|
||||
fail_msg: "A value less4j cannot type breaks the whole skin stylesheet."
|
||||
|
||||
- name: "📁 XWIKI | Ensure the design directory exists"
|
||||
ansible.builtin.file:
|
||||
path: "{{ XWIKI_DESIGN_DIR }}"
|
||||
state: directory
|
||||
mode: "0755"
|
||||
|
||||
- name: "🖼️ XWIKI | Render the corporate logo set"
|
||||
ansible.builtin.include_role:
|
||||
name: sys-front-inj-design
|
||||
tasks_from: assets.yml
|
||||
vars:
|
||||
design_assets_dest: "{{ XWIKI_DESIGN_DIR }}"
|
||||
|
||||
- name: "📄 XWIKI | Create the corporate theme page"
|
||||
uri_retry:
|
||||
url: "{{ XWIKI_REST_THEME_PAGE }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [201]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/design/theme/page.xml.j2') }}"
|
||||
when: _xwiki_design_page.status == 404
|
||||
changed_when: true
|
||||
|
||||
- name: "💾 XWIKI | Remember the preferences the wiki had"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_THEME_PAGE, 'objects'] | url_join }}"
|
||||
method: POST
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [201]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/design/previous_preferences_object.xml.j2') }}"
|
||||
when: _xwiki_design_previous.status == 404
|
||||
changed_when: true
|
||||
|
||||
- name: "📂 XWIKI | Write the theme object"
|
||||
include_tasks: theme.yml
|
||||
|
||||
- name: "📂 XWIKI | Attach the navbar lockup"
|
||||
include_tasks: logo.yml
|
||||
when: design_branding.logo is string
|
||||
|
||||
- name: "📌 XWIKI | Make the corporate theme the wiki default"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_PREFERENCES, 'colorTheme'] | url_join }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [202]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/property.xml.j2') }}"
|
||||
vars:
|
||||
xwiki_property_value: "{{ XWIKI_DESIGN_THEME_REFERENCE }}"
|
||||
when: _xwiki_design_color_theme.json.value != XWIKI_DESIGN_THEME_REFERENCE
|
||||
changed_when: true
|
||||
|
||||
- name: "🏷️ XWIKI | Set the wiki title"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_PREFERENCES, 'title'] | url_join }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [202]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/property.xml.j2') }}"
|
||||
vars:
|
||||
xwiki_property_value: "{{ XWIKI_DESIGN_TITLE }}"
|
||||
when:
|
||||
- design_branding.title is string
|
||||
- _xwiki_design_title.json.value != XWIKI_DESIGN_TITLE
|
||||
changed_when: true
|
||||
@@ -0,0 +1,32 @@
|
||||
---
|
||||
- name: "📥 XWIKI | Read the generated navbar lockup"
|
||||
ansible.builtin.slurp:
|
||||
src: "{{ [XWIKI_DESIGN_DIR, XWIKI_DESIGN_LOGO_FILE] | path_join }}"
|
||||
register: _xwiki_design_logo
|
||||
|
||||
- name: "🔎 XWIKI | Read the logo attachment of the theme page"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_THEME_PAGE, 'attachments', XWIKI_DESIGN_LOGO_FILE] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200, 404]
|
||||
return_content: true
|
||||
register: _xwiki_design_logo_attachment
|
||||
changed_when: false
|
||||
|
||||
- name: "🖼️ XWIKI | Attach the navbar lockup to the theme page"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_THEME_PAGE, 'attachments', XWIKI_DESIGN_LOGO_FILE] | url_join }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [201, 202]
|
||||
headers: "{{ xwiki_rest_headers_write | combine({'Content-Type': 'image/svg+xml'}) }}"
|
||||
body: "{{ _xwiki_design_logo.content | b64decode }}"
|
||||
when: >-
|
||||
_xwiki_design_logo_attachment.status == 404
|
||||
or _xwiki_design_logo_attachment.content != (_xwiki_design_logo.content | b64decode)
|
||||
changed_when: true
|
||||
@@ -0,0 +1,48 @@
|
||||
---
|
||||
- name: "↩️ XWIKI | Give the wiki its former color theme back"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_PREFERENCES, 'colorTheme'] | url_join }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [202]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/property.xml.j2') }}"
|
||||
vars:
|
||||
xwiki_property_value: "{{ xwiki_design_former.colorTheme }}"
|
||||
when: _xwiki_design_color_theme.json.value == XWIKI_DESIGN_THEME_REFERENCE
|
||||
changed_when: true
|
||||
|
||||
- name: "↩️ XWIKI | Give the wiki its former title back"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_PREFERENCES, 'title'] | url_join }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [202]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/property.xml.j2') }}"
|
||||
vars:
|
||||
xwiki_property_value: "{{ xwiki_design_former.title }}"
|
||||
when:
|
||||
- _xwiki_design_title.json.value == XWIKI_DESIGN_TITLE
|
||||
- xwiki_design_former.title != XWIKI_DESIGN_TITLE
|
||||
changed_when: true
|
||||
|
||||
- name: "🗑️ XWIKI | Delete the corporate theme page"
|
||||
uri_retry:
|
||||
url: "{{ XWIKI_REST_THEME_PAGE }}"
|
||||
method: DELETE
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [204]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
changed_when: true
|
||||
|
||||
- name: "🗑️ XWIKI | Remove the rendered logo set"
|
||||
ansible.builtin.file:
|
||||
path: "{{ XWIKI_DESIGN_DIR }}"
|
||||
state: absent
|
||||
@@ -0,0 +1,69 @@
|
||||
---
|
||||
- name: "🔎 XWIKI | Read the stock theme the corporate theme derives from"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_XWIKI, 'spaces/FlamingoThemes/pages', XWIKI_DESIGN_STOCK_THEME_PAGE, XWIKI_DESIGN_THEME_OBJECT] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200]
|
||||
headers:
|
||||
Accept: application/json
|
||||
return_content: true
|
||||
register: _xwiki_design_stock_theme
|
||||
changed_when: false
|
||||
|
||||
- name: "🔎 XWIKI | Read the corporate theme object"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_THEME_PAGE, XWIKI_DESIGN_THEME_OBJECT] | url_join }}"
|
||||
method: GET
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [200, 404]
|
||||
headers:
|
||||
Accept: application/json
|
||||
return_content: true
|
||||
register: _xwiki_design_theme_object
|
||||
changed_when: false
|
||||
|
||||
- name: "🧮 XWIKI | Derive the corporate theme from the stock theme and the palette"
|
||||
ansible.builtin.set_fact:
|
||||
xwiki_design_theme_properties: >-
|
||||
{{ _xwiki_design_stock_theme.json.properties
|
||||
| items2dict(key_name='name', value_name='value')
|
||||
| combine(XWIKI_DESIGN_THEME_COLORS, {
|
||||
'lessCode': XWIKI_DESIGN_LESS_CODE,
|
||||
'logo': XWIKI_DESIGN_LOGO_FILE if design_branding.logo is string else '',
|
||||
'logo-description': design_branding.label
|
||||
}) }}
|
||||
|
||||
- name: "➕ XWIKI | Add the corporate theme object"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_THEME_PAGE, 'objects'] | url_join }}"
|
||||
method: POST
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [201]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/design/theme/object.xml.j2') }}"
|
||||
when: _xwiki_design_theme_object.status == 404
|
||||
changed_when: true
|
||||
|
||||
- name: "🎨 XWIKI | Update the corporate theme object"
|
||||
uri_retry:
|
||||
url: "{{ [XWIKI_REST_THEME_PAGE, XWIKI_DESIGN_THEME_OBJECT] | url_join }}"
|
||||
method: PUT
|
||||
url_username: "{{ XWIKI_SUPERADMIN_USERNAME }}"
|
||||
url_password: "{{ XWIKI_SUPERADMIN_PASSWORD }}"
|
||||
force_basic_auth: true
|
||||
status_code: [202]
|
||||
headers: "{{ xwiki_rest_headers_write }}"
|
||||
body: "{{ lookup('template', 'xml/design/theme/object.xml.j2') }}"
|
||||
when:
|
||||
- _xwiki_design_theme_object.status == 200
|
||||
- >-
|
||||
(_xwiki_design_theme_object.json.properties | items2dict(key_name='name', value_name='value'))
|
||||
!= xwiki_design_theme_properties
|
||||
changed_when: true
|
||||
@@ -12,6 +12,14 @@ BIBER_USERNAME={{ lookup('users', 'biber').username | dotenv_quote }}
|
||||
|
||||
CANONICAL_DOMAIN={{ lookup('domain', application_id) | dotenv_quote }}
|
||||
|
||||
DESIGN_LOGO_URL={{ (lookup('design', application_id).urls.logo.svg if lookup('design', application_id).logo is string else '') | dotenv_quote }}
|
||||
|
||||
DESIGN_SERVICE_ENABLED={{ lookup('config', application_id, 'services.design.enabled') | string | lower }}
|
||||
|
||||
DESIGN_THEME_REFERENCE={{ XWIKI_DESIGN_THEME_REFERENCE | dotenv_quote }}
|
||||
|
||||
DESIGN_TITLE={{ (lookup('design', application_id).title if lookup('design', application_id).title is string else '') | dotenv_quote }}
|
||||
|
||||
# nocheck: playwright-service-gate — covered by sys-svc-mail integration tests, not the persona surface
|
||||
EMAIL_SERVICE_ENABLED={{ lookup('config', application_id, 'services.email.enabled') | string | lower }}
|
||||
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,6 @@
|
||||
{% set former_theme = _xwiki_design_color_theme.json.value %}
|
||||
<object xmlns="http://www.xwiki.org">
|
||||
<className>XWiki.XWikiPreferences</className>
|
||||
<property name="colorTheme"><value>{{ ('' if former_theme == XWIKI_DESIGN_THEME_REFERENCE else former_theme) | e }}</value></property>
|
||||
<property name="title"><value>{{ _xwiki_design_title.json.value | e }}</value></property>
|
||||
</object>
|
||||
@@ -0,0 +1,6 @@
|
||||
<object xmlns="http://www.xwiki.org">
|
||||
<className>FlamingoThemesCode.ThemeClass</className>
|
||||
{% for name, value in xwiki_design_theme_properties | dictsort %}
|
||||
<property name="{{ name }}"><value>{{ value | e }}</value></property>
|
||||
{% endfor %}
|
||||
</object>
|
||||
@@ -0,0 +1,6 @@
|
||||
<page xmlns="http://www.xwiki.org">
|
||||
<title>{{ design_branding.label | e }}</title>
|
||||
<parent>FlamingoThemes.WebHome</parent>
|
||||
<hidden>true</hidden>
|
||||
<syntax>xwiki/2.1</syntax>
|
||||
</page>
|
||||
@@ -0,0 +1 @@
|
||||
<property xmlns="http://www.xwiki.org"><value>{{ xwiki_property_value | e }}</value></property>
|
||||
@@ -1,6 +1,4 @@
|
||||
<object xmlns="http://www.xwiki.org">
|
||||
<className>XWiki.XWikiGroups</className>
|
||||
<properties>
|
||||
<property name="member">XWiki.{{ XWIKI_ADMIN_USER }}</property>
|
||||
</properties>
|
||||
<property name="member"><value>{{ XWIKI_ADMIN_REFERENCE }}</value></property>
|
||||
</object>
|
||||
@@ -8,12 +8,14 @@ container_hostname: "{{ lookup('container_hostname', application_id) }}"
|
||||
XWIKI_HOST_PORT: "{{ lookup('config', application_id, 'services.xwiki.ports.local.http') }}"
|
||||
|
||||
XWIKI_HOSTNAME: "{{ container_hostname }}"
|
||||
XWIKI_URL: "{{ lookup('tls', application_id, 'url.base') | regex_replace('/+$', '') }}"
|
||||
|
||||
XWIKI_HOST_PROPERTIES_PATH: "{{ [lookup('container', application_id, 'directories.config'), 'xwiki.properties'] | path_join }}"
|
||||
XWIKI_DOCK_DATA_DIR: "{{ lookup('volume', 'web-app-xwiki', 'data').target }}"
|
||||
|
||||
XWIKI_IMAGE: "{{ lookup('config', application_id, 'services.xwiki.image') }}"
|
||||
XWIKI_VERSION: "{{ lookup('config', application_id, 'services.xwiki.version') }}"
|
||||
XWIKI_PLATFORM_VERSION: "{{ XWIKI_VERSION | split('-') | first }}"
|
||||
XWIKI_CONTAINER: "{{ lookup('config', application_id, 'services.xwiki.name') }}"
|
||||
XWIKI_CONTAINER_ADDRESS: "{{ lookup('container_address', application_id, entity_name) }}"
|
||||
|
||||
@@ -22,6 +24,8 @@ XWIKI_OIDC_ENABLED: "{{ lookup('config', application_id, 'services.sso.enabled')
|
||||
XWIKI_SSO_ENABLED: "{{ (XWIKI_OIDC_ENABLED | bool) or (XWIKI_LDAP_ENABLED | bool) }}"
|
||||
|
||||
XWIKI_ADMIN_USER: "{{ lookup('users', 'administrator').username }}"
|
||||
XWIKI_ADMIN_PASSWORD: "{{ lookup('users', 'administrator').password }}"
|
||||
XWIKI_ADMIN_REFERENCE: "XWiki.{{ XWIKI_ADMIN_USER }}"
|
||||
XWIKI_ADMIN_GROUP: "administrator"
|
||||
XWIKI_ADMIN_GROUP_PAGE: "XWikiAdminGroup"
|
||||
|
||||
@@ -55,3 +59,90 @@ XWIKI_OIDC_GROUPS_CLAIM: "{{ RBAC.GROUP.CLAIM }}"
|
||||
XWIKI_OIDC_ADMIN_PROVIDER_GROUP: "{{ lookup('rbac_group_path', application_id=application_id, role='administrator') }}"
|
||||
|
||||
XWIKI_PLUGINS: "{{ lookup('config', application_id, 'addons') | xwiki_enabled_install_items }}"
|
||||
|
||||
XWIKI_DESIGN_ENABLED: "{{ lookup('config', application_id, 'services.design.enabled') | bool }}"
|
||||
XWIKI_DESIGN_DIR: "{{ [lookup('container', application_id, 'directories.config'), 'design'] | path_join }}"
|
||||
XWIKI_DESIGN_THEME_PAGE: "Infinito"
|
||||
XWIKI_DESIGN_THEME_REFERENCE: "FlamingoThemes.{{ XWIKI_DESIGN_THEME_PAGE }}"
|
||||
XWIKI_DESIGN_STOCK_THEME_PAGE: "Iceberg"
|
||||
XWIKI_DESIGN_THEME_OBJECT: "objects/FlamingoThemesCode.ThemeClass/0"
|
||||
XWIKI_DESIGN_LOGO_FILE: "logo.svg"
|
||||
XWIKI_REST_THEME_PAGE: "{{ [XWIKI_REST_XWIKI, 'spaces/FlamingoThemes/pages', XWIKI_DESIGN_THEME_PAGE] | url_join }}"
|
||||
XWIKI_REST_PREFERENCES: "{{ [XWIKI_REST_XWIKI_PAGES, 'XWikiPreferences/objects/XWiki.XWikiPreferences/0/properties'] | url_join }}"
|
||||
XWIKI_DESIGN_TITLE_SUFFIX: " - {{ lookup('design', application_id).label }}"
|
||||
XWIKI_DESIGN_TITLE: "$tdoc.getRenderedTitle('plain/1.0'){{ XWIKI_DESIGN_TITLE_SUFFIX }}"
|
||||
XWIKI_DESIGN_PALETTE: "{{ lookup('design_palette', lookup('config', 'web-svc-design', 'services.design.colors.base')).light }}"
|
||||
XWIKI_DESIGN_THEME_TOKENS:
|
||||
text-color: text
|
||||
body-bg: surface-1
|
||||
xwiki-page-content-bg: surface-2
|
||||
link-color: link
|
||||
headings-color: text
|
||||
brand-primary: primary
|
||||
brand-success: success
|
||||
brand-info: info
|
||||
brand-warning: warning
|
||||
brand-danger: danger
|
||||
component-active-color: text
|
||||
component-active-bg: surface-active
|
||||
btn-default-color: text
|
||||
btn-default-bg: surface-2
|
||||
btn-default-border: border-strong
|
||||
btn-primary-color: on-primary
|
||||
btn-primary-bg: primary
|
||||
btn-success-color: on-success
|
||||
btn-success-bg: success
|
||||
btn-info-color: on-info
|
||||
btn-info-bg: info
|
||||
btn-warning-color: on-warning
|
||||
btn-warning-bg: warning
|
||||
btn-danger-color: on-danger
|
||||
btn-danger-bg: danger
|
||||
navbar-default-bg: frame
|
||||
navbar-default-color: on-frame
|
||||
navbar-default-link-color: on-frame
|
||||
navbar-default-link-hover-color: on-frame
|
||||
navbar-default-link-hover-bg: frame-hover
|
||||
navbar-default-link-active-color: on-frame
|
||||
navbar-default-link-active-bg: frame-active
|
||||
dropdown-bg: surface-2
|
||||
dropdown-border: border
|
||||
dropdown-divider-bg: border
|
||||
dropdown-link-color: text
|
||||
dropdown-link-hover-color: text
|
||||
dropdown-link-hover-bg: surface-hover
|
||||
input-bg: surface-2
|
||||
input-border: border-strong
|
||||
input-color: text
|
||||
input-border-focus: link
|
||||
input-color-placeholder: text-muted
|
||||
legend-color: text
|
||||
legend-border-color: border
|
||||
panel-bg: surface-2
|
||||
panel-header-text: text
|
||||
breadcrumb-bg: surface-3
|
||||
breadcrumb-color: text-muted
|
||||
breadcrumb-link-color: link
|
||||
table-bg: surface-2
|
||||
table-bg-hover: surface-hover
|
||||
table-border-color: border
|
||||
XWIKI_DESIGN_LESS_TOKENS:
|
||||
gray-light: text-muted
|
||||
text-muted: text-muted
|
||||
state-success-bg: success-subtle
|
||||
state-info-bg: info-subtle
|
||||
state-warning-bg: warning-subtle
|
||||
state-danger-bg: danger-subtle
|
||||
xwiki-border-color: border
|
||||
xwiki-drawer-menu-item-hover-bg: surface-hover
|
||||
xwiki-drawer-menu-item-hover-color: text
|
||||
well-bg: surface-1
|
||||
XWIKI_DESIGN_THEME_COLORS: >-
|
||||
{{ dict(XWIKI_DESIGN_THEME_TOKENS.keys()
|
||||
| zip(XWIKI_DESIGN_THEME_TOKENS.values() | map('regex_replace', '^', '--design-') | map('extract', XWIKI_DESIGN_PALETTE))) }}
|
||||
XWIKI_DESIGN_LESS_COLORS: >-
|
||||
{{ dict(XWIKI_DESIGN_LESS_TOKENS.keys()
|
||||
| zip(XWIKI_DESIGN_LESS_TOKENS.values() | map('regex_replace', '^', '--design-') | map('extract', XWIKI_DESIGN_PALETTE))) }}
|
||||
XWIKI_DESIGN_LESS_CODE: >-
|
||||
{% for name, value in XWIKI_DESIGN_LESS_COLORS | dictsort %}@{{ name }}: {{ value }};
|
||||
{% endfor %}@list-group-active-border: @list-group-border; @headings-font-weight: 500;
|
||||
Reference in new issue
Block a user