fix(analytics): fixed issues with db init and refactor of code and names

This commit is contained in:
hadestructhor 2025-04-10 23:48:46 +02:00
parent 0588acb40a
commit 28e288db2c
10 changed files with 44 additions and 45 deletions

View file

@ -202,10 +202,7 @@ async function createTables() {
table.text("footer_text"); table.text("footer_text");
table.text("custom_css"); table.text("custom_css");
table.boolean("show_powered_by").notNullable().defaultTo(true); table.boolean("show_powered_by").notNullable().defaultTo(true);
table.string("analytics_id"); table.string("google_analytics_tag_id");
table.string("analytics_domain_url");
table.enu("analytics_type", [ "google", "umami", "plausible" ]).defaultTo(null);
}); });
// maintenance_status_page // maintenance_status_page

View file

@ -3,7 +3,7 @@ exports.up = function (knex) {
return knex.schema return knex.schema
.alterTable("status_page", function (table) { .alterTable("status_page", function (table) {
table.renameColumn("google_analytics_tag_id", "analytics_id"); table.renameColumn("google_analytics_tag_id", "analytics_id");
table.string("analytics_domain_url"); table.string("analytics_script_url");
table.enu("analytics_type", [ "google", "umami", "plausible", "matomo" ]).defaultTo(null); table.enu("analytics_type", [ "google", "umami", "plausible", "matomo" ]).defaultTo(null);
}).then(() => { }).then(() => {
@ -17,7 +17,7 @@ exports.up = function (knex) {
exports.down = function (knex) { exports.down = function (knex) {
return knex.schema.alterTable("status_page", function (table) { return knex.schema.alterTable("status_page", function (table) {
table.renameColumn("analytics_id", "google_analytics_tag_id"); table.renameColumn("analytics_id", "google_analytics_tag_id");
table.dropColumn("analytics_domain_url"); table.dropColumn("analytics_script_url");
table.dropColumn("analytics_type"); table.dropColumn("analytics_type");
}); });
}; };

View file

@ -14,11 +14,11 @@ function getAnalyticsScript(statusPage) {
case "google": case "google":
return googleAnalytics.getGoogleAnalyticsScript(statusPage.analyticsId); return googleAnalytics.getGoogleAnalyticsScript(statusPage.analyticsId);
case "umami": case "umami":
return umamiAnalytics.getUmamiAnalyticsScript(statusPage.analyticsDomainUrl, statusPage.analyticsId); return umamiAnalytics.getUmamiAnalyticsScript(statusPage.analyticsScriptUrl, statusPage.analyticsId);
case "plausible": case "plausible":
return plausibleAnalytics.getPlausibleAnalyticsScript(statusPage.analyticsDomainUrl, statusPage.analyticsId); return plausibleAnalytics.getPlausibleAnalyticsScript(statusPage.analyticsScriptUrl, statusPage.analyticsId);
case "matomo": case "matomo":
return matomoAnalytics.getMatomoAnalyticsScript(statusPage.analyticsDomainUrl, statusPage.analyticsId); return matomoAnalytics.getMatomoAnalyticsScript(statusPage.analyticsScriptUrl, statusPage.analyticsId);
default: default:
return null; return null;
} }
@ -36,7 +36,7 @@ function isValidAnalyticsConfig(statusPage) {
case "umami": case "umami":
case "plausible": case "plausible":
case "matomo": case "matomo":
return statusPage.analyticsId != null && statusPage.analyticsDomainUrl != null; return statusPage.analyticsId != null && statusPage.analyticsScriptUrl != null;
default: default:
return false; return false;
} }

View file

@ -4,16 +4,16 @@ const { escape } = require("html-escaper");
/** /**
* Returns a string that represents the javascript that is required to insert the Plausible Analytics script * Returns a string that represents the javascript that is required to insert the Plausible Analytics script
* into a webpage. * into a webpage.
* @param {string} plausibleDomainUrl Domain name with tld to use with the Plausible Analytics script. * @param {string} scriptUrl the Plausible Analytics script url.
* @param {string} domainsToMonitor Domains to track seperated by a ',' to add Plausible Analytics script. * @param {string} domainsToMonitor Domains to track seperated by a ',' to add Plausible Analytics script.
* @returns {string} HTML script tags to inject into page * @returns {string} HTML script tags to inject into page
*/ */
function getPlausibleAnalyticsScript(plausibleDomainUrl, domainsToMonitor) { function getPlausibleAnalyticsScript(scriptUrl, domainsToMonitor) {
let escapedDomainUrlJS = jsesc(plausibleDomainUrl, { isScriptContext: true }); let escapedScriptUrlJS = jsesc(scriptUrl, { isScriptContext: true });
let escapedWebsiteIdJS = jsesc(domainsToMonitor, { isScriptContext: true }); let escapedWebsiteIdJS = jsesc(domainsToMonitor, { isScriptContext: true });
if (escapedDomainUrlJS) { if (escapedScriptUrlJS) {
escapedDomainUrlJS = escapedDomainUrlJS.trim(); escapedScriptUrlJS = escapedScriptUrlJS.trim();
} }
if (escapedWebsiteIdJS) { if (escapedWebsiteIdJS) {
@ -21,13 +21,13 @@ function getPlausibleAnalyticsScript(plausibleDomainUrl, domainsToMonitor) {
} }
// Escape the domain url for use in an HTML attribute. // Escape the domain url for use in an HTML attribute.
let escapedDomainUrlHTMLAttribute = escape(escapedDomainUrlJS); let escapedScriptUrlHTMLAttribute = escape(escapedScriptUrlJS);
// Escape the website id for use in an HTML attribute. // Escape the website id for use in an HTML attribute.
let escapedWebsiteIdHTMLAttribute = escape(escapedWebsiteIdJS); let escapedWebsiteIdHTMLAttribute = escape(escapedWebsiteIdJS);
return ` return `
<script defer src="https://${escapedDomainUrlHTMLAttribute}/js/script.js" data-domain="${escapedWebsiteIdHTMLAttribute}"></script> <script defer src="${escapedScriptUrlHTMLAttribute}" data-domain="${escapedWebsiteIdHTMLAttribute}"></script>
`; `;
} }

View file

@ -4,30 +4,30 @@ const { escape } = require("html-escaper");
/** /**
* Returns a string that represents the javascript that is required to insert the Umami Analytics script * Returns a string that represents the javascript that is required to insert the Umami Analytics script
* into a webpage. * into a webpage.
* @param {string} domainUrl Domain name with tld to use with the Umami Analytics script. * @param {string} scriptUrl the Umami Analytics script url.
* @param {string} websiteId Website ID to use with the Umami Analytics script. * @param {string} websiteId Website ID to use with the Umami Analytics script.
* @returns {string} HTML script tags to inject into page * @returns {string} HTML script tags to inject into page
*/ */
function getUmamiAnalyticsScript(domainUrl, websiteId) { function getUmamiAnalyticsScript(scriptUrl, websiteId) {
let escapedDomainUrlJS = jsesc(domainUrl, { isScriptContext: true }); let escapedScriptUrlJS = jsesc(scriptUrl, { isScriptContext: true });
let escapedWebsiteIdJS = jsesc(websiteId, { isScriptContext: true }); let escapedWebsiteIdJS = jsesc(websiteId, { isScriptContext: true });
if (escapedDomainUrlJS) { if (escapedScriptUrlJS) {
escapedDomainUrlJS = escapedDomainUrlJS.trim(); escapedScriptUrlJS = escapedScriptUrlJS.trim();
} }
if (escapedWebsiteIdJS) { if (escapedWebsiteIdJS) {
escapedWebsiteIdJS = escapedWebsiteIdJS.trim(); escapedWebsiteIdJS = escapedWebsiteIdJS.trim();
} }
// Escape the domain url for use in an HTML attribute. // Escape the Script url for use in an HTML attribute.
let escapedDomainUrlHTMLAttribute = escape(escapedDomainUrlJS); let escapedScriptUrlHTMLAttribute = escape(escapedScriptUrlJS);
// Escape the website id for use in an HTML attribute. // Escape the website id for use in an HTML attribute.
let escapedWebsiteIdHTMLAttribute = escape(escapedWebsiteIdJS); let escapedWebsiteIdHTMLAttribute = escape(escapedWebsiteIdJS);
return ` return `
<script defer src="https://${escapedDomainUrlHTMLAttribute}/script.js" data-website-id="${escapedWebsiteIdHTMLAttribute}"></script> <script defer src="${escapedScriptUrlHTMLAttribute}" data-website-id="${escapedWebsiteIdHTMLAttribute}"></script>
`; `;
} }

View file

@ -408,7 +408,7 @@ class StatusPage extends BeanModel {
footerText: this.footer_text, footerText: this.footer_text,
showPoweredBy: !!this.show_powered_by, showPoweredBy: !!this.show_powered_by,
analyticsId: this.analytics_id, analyticsId: this.analytics_id,
analyticsDomainUrl: this.analytics_domain_url, analyticsScriptUrl: this.analytics_script_url,
analyticsType: this.analytics_type, analyticsType: this.analytics_type,
showCertificateExpiry: !!this.show_certificate_expiry, showCertificateExpiry: !!this.show_certificate_expiry,
}; };
@ -433,7 +433,7 @@ class StatusPage extends BeanModel {
footerText: this.footer_text, footerText: this.footer_text,
showPoweredBy: !!this.show_powered_by, showPoweredBy: !!this.show_powered_by,
analyticsId: this.analytics_id, analyticsId: this.analytics_id,
analyticsDomainUrl: this.analytics_domain_url, analyticsScriptUrl: this.analytics_script_url,
analyticsType: this.analytics_type, analyticsType: this.analytics_type,
showCertificateExpiry: !!this.show_certificate_expiry, showCertificateExpiry: !!this.show_certificate_expiry,
}; };

View file

@ -167,7 +167,7 @@ module.exports.statusPageSocketHandler = (socket) => {
statusPage.show_certificate_expiry = config.showCertificateExpiry; statusPage.show_certificate_expiry = config.showCertificateExpiry;
statusPage.modified_date = R.isoDateTime(); statusPage.modified_date = R.isoDateTime();
statusPage.analytics_id = config.analyticsId; statusPage.analytics_id = config.analyticsId;
statusPage.analytics_domain_url = config.analyticsDomainUrl; statusPage.analytics_script_url = config.analyticsScriptUrl;
statusPage.analytics_type = config.analyticsType; statusPage.analytics_type = config.analyticsType;
await R.store(statusPage); await R.store(statusPage);

View file

@ -789,9 +789,7 @@
"Google Analytics ID": "Google Analytics ID", "Google Analytics ID": "Google Analytics ID",
"Analytics Type": "Analytics Type", "Analytics Type": "Analytics Type",
"Analytics ID": "Analytics ID", "Analytics ID": "Analytics ID",
"Analytics Domain URL": "Analytics Domain URL", "Analytics Script URL": "Analytics Script URL",
"Umami Analytics Domain Url": "Umami Analytics Domain Url",
"Umami Analytics Website ID": "Umami Analytics Website ID",
"Edit Tag": "Edit Tag", "Edit Tag": "Edit Tag",
"Server Address": "Server Address", "Server Address": "Server Address",
"Learn More": "Learn More", "Learn More": "Learn More",
@ -1072,5 +1070,9 @@
"YZJ Robot Token": "YZJ Robot token", "YZJ Robot Token": "YZJ Robot token",
"Plain Text": "Plain Text", "Plain Text": "Plain Text",
"Message Template": "Message Template", "Message Template": "Message Template",
"Template Format": "Template Format" "Template Format": "Template Format",
"Google": "Google",
"Plausible": "Plausible",
"Matomo": "Matomo",
"Umami": "Umami"
} }

View file

@ -124,19 +124,19 @@ const analyticsOptions = [
<label for="analyticsType" class="form-label">{{ $t("Analytics Type") }}</label> <label for="analyticsType" class="form-label">{{ $t("Analytics Type") }}</label>
<select id="analyticsType" v-model="config.analyticsType" class="form-select" data-testid="analytics-type-select"> <select id="analyticsType" v-model="config.analyticsType" class="form-select" data-testid="analytics-type-select">
<option v-for="(analyticOption, index) in analyticsOptions" :key="index" :value="analyticOption.value"> <option v-for="(analyticOption, index) in analyticsOptions" :key="index" :value="analyticOption.value">
{{ analyticOption.name }} {{ $t(analyticOption.name) }}
</option> </option>
</select> </select>
</div> </div>
<div v-if="config.analyticsType !== null && config.analyticsType !== undefined" class="my-3"> <div v-if="!!config.analyticsType" class="my-3">
<label for="analyticsId" class="form-label">{{ $t("Analytics ID") }}</label> <label for="analyticsId" class="form-label">{{ $t("Analytics ID") }}</label>
<input id="analyticsId" v-model="config.analyticsId" type="text" class="form-control" data-testid="analytics-id-input"> <input id="analyticsId" v-model="config.analyticsId" type="text" class="form-control" data-testid="analytics-id-input">
</div> </div>
<div v-if="config.analyticsType !== null && config.analyticsType !== undefined && config.analyticsType !== 'google'" class="my-3"> <div v-if="!!config.analyticsType && config.analyticsType !== 'google'" class="my-3">
<label for="analyticsDomainUrl" class="form-label">{{ $t("Analytics Domain URL") }}</label> <label for="analyticsScriptUrl" class="form-label">{{ $t("Analytics Script URL") }}</label>
<input id="analyticsDomainUrl" v-model="config.analyticsDomainUrl" type="text" class="form-control" data-testid="analytics-domain-url-input"> <input id="analyticsScriptUrl" v-model="config.analyticsScriptUrl" type="url" class="form-control" data-testid="analytics-script-url-input">
</div> </div>
<!-- Custom CSS --> <!-- Custom CSS -->

View file

@ -18,11 +18,11 @@ test.describe("Status Page", () => {
const refreshInterval = 30; const refreshInterval = 30;
const theme = "dark"; const theme = "dark";
const googleAnalyticsId = "G-123"; const googleAnalyticsId = "G-123";
const umamiAnalyticsDomainUrl = "example.com"; const umamiAnalyticsScriptUrl = "https://umami.example.com/script.js";
const umamiAnalyticsWebsiteId = "606487e2-bc25-45f9-9132-fa8b065aad46"; const umamiAnalyticsWebsiteId = "606487e2-bc25-45f9-9132-fa8b065aad46";
const plausibleAnalyticsDomainUrl = "example.com"; const plausibleAnalyticsScriptUrl = "https://plausible.example.com/js/script.js";
const plausibleAnalyticsDomainsUrls = "one.com,two.com"; const plausibleAnalyticsDomainsUrls = "one.com,two.com";
const matomoUrl = "matomo.com"; const matomoUrl = "https://matomoto.example.com";
const matomoSiteId = "123456789"; const matomoSiteId = "123456789";
const customCss = "body { background: rgb(0, 128, 128) !important; }"; const customCss = "body { background: rgb(0, 128, 128) !important; }";
const descriptionText = "This is an example status page."; const descriptionText = "This is an example status page.";
@ -124,7 +124,7 @@ test.describe("Status Page", () => {
// Fill in umami analytics after editing // Fill in umami analytics after editing
await page.getByTestId("analytics-type-select").selectOption("umami"); await page.getByTestId("analytics-type-select").selectOption("umami");
await page.getByTestId("analytics-domain-url-input").fill(umamiAnalyticsDomainUrl); await page.getByTestId("analytics-script-url-input").fill(umamiAnalyticsScriptUrl);
await page.getByTestId("analytics-id-input").fill(umamiAnalyticsWebsiteId); await page.getByTestId("analytics-id-input").fill(umamiAnalyticsWebsiteId);
await page.getByTestId("save-button").click(); await page.getByTestId("save-button").click();
@ -134,23 +134,23 @@ test.describe("Status Page", () => {
await screenshot(testInfo, page); await screenshot(testInfo, page);
expect(await page.locator("head").innerHTML()).toContain(umamiAnalyticsDomainUrl); expect(await page.locator("head").innerHTML()).toContain(umamiAnalyticsScriptUrl);
expect(await page.locator("head").innerHTML()).toContain(umamiAnalyticsWebsiteId); expect(await page.locator("head").innerHTML()).toContain(umamiAnalyticsWebsiteId);
await page.getByTestId("edit-button").click(); await page.getByTestId("edit-button").click();
// Fill in plausible analytics after editing // Fill in plausible analytics after editing
await page.getByTestId("analytics-type-select").selectOption("plausible"); await page.getByTestId("analytics-type-select").selectOption("plausible");
await page.getByTestId("analytics-domain-url-input").fill(plausibleAnalyticsDomainUrl); await page.getByTestId("analytics-script-url-input").fill(plausibleAnalyticsScriptUrl);
await page.getByTestId("analytics-id-input").fill(plausibleAnalyticsDomainsUrls); await page.getByTestId("analytics-id-input").fill(plausibleAnalyticsDomainsUrls);
await page.getByTestId("save-button").click(); await page.getByTestId("save-button").click();
await screenshot(testInfo, page); await screenshot(testInfo, page);
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsDomainUrl); expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsScriptUrl);
expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsDomainsUrls); expect(await page.locator("head").innerHTML()).toContain(plausibleAnalyticsDomainsUrls);
await page.getByTestId("edit-button").click(); await page.getByTestId("edit-button").click();
// Fill in matomo analytics after editing // Fill in matomo analytics after editing
await page.getByTestId("analytics-type-select").selectOption("matomo"); await page.getByTestId("analytics-type-select").selectOption("matomo");
await page.getByTestId("analytics-domain-url-input").fill(matomoUrl); await page.getByTestId("analytics-script-url-input").fill(matomoUrl);
await page.getByTestId("analytics-id-input").fill(matomoSiteId); await page.getByTestId("analytics-id-input").fill(matomoSiteId);
await page.getByTestId("save-button").click(); await page.getByTestId("save-button").click();
await screenshot(testInfo, page); await screenshot(testInfo, page);