mirror of
https://github.com/louislam/uptime-kuma.git
synced 2026-06-23 04:10:14 +00:00
feat: add tracking support on status pages for rybbit analytics (#7525)
This commit is contained in:
committed by
GitHub
parent
a2ac12fb65
commit
a57aabf8c3
@@ -0,0 +1,33 @@
|
|||||||
|
const newValues = ["google", "umami", "plausible", "matomo", "rybbit"];
|
||||||
|
const oldValues = ["google", "umami", "plausible", "matomo"];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Rebuild the status_page.analytics_type enum with the given values, keeping existing data.
|
||||||
|
* The column is dropped and re-created because SQLite's .enu().alter() does not replace the old CHECK constraint.
|
||||||
|
* @param {import("knex").Knex} knex The knex instance
|
||||||
|
* @param {string[]} allowedValues Allowed analytics_type values
|
||||||
|
* @returns {Promise<void>}
|
||||||
|
*/
|
||||||
|
async function rebuildAnalyticsType(knex, allowedValues) {
|
||||||
|
const rows = await knex("status_page").whereNotNull("analytics_type").select("id", "analytics_type");
|
||||||
|
|
||||||
|
await knex.schema.alterTable("status_page", (table) => {
|
||||||
|
table.dropColumn("analytics_type");
|
||||||
|
});
|
||||||
|
await knex.schema.alterTable("status_page", (table) => {
|
||||||
|
table.enu("analytics_type", allowedValues).defaultTo(null);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const row of rows) {
|
||||||
|
await knex("status_page").where("id", row.id).update({ analytics_type: row.analytics_type });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
exports.up = function (knex) {
|
||||||
|
return rebuildAnalyticsType(knex, newValues);
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = async function (knex) {
|
||||||
|
await knex("status_page").where("analytics_type", "rybbit").update({ analytics_type: null });
|
||||||
|
await rebuildAnalyticsType(knex, oldValues);
|
||||||
|
};
|
||||||
@@ -2,6 +2,7 @@ const googleAnalytics = require("./google-analytics");
|
|||||||
const umamiAnalytics = require("./umami-analytics");
|
const umamiAnalytics = require("./umami-analytics");
|
||||||
const plausibleAnalytics = require("./plausible-analytics");
|
const plausibleAnalytics = require("./plausible-analytics");
|
||||||
const matomoAnalytics = require("./matomo-analytics");
|
const matomoAnalytics = require("./matomo-analytics");
|
||||||
|
const rybbitAnalytics = require("./rybbit-analytics");
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a string that represents the javascript that is required to insert the selected Analytics' script
|
* Returns a string that represents the javascript that is required to insert the selected Analytics' script
|
||||||
@@ -22,6 +23,8 @@ function getAnalyticsScript(statusPage) {
|
|||||||
);
|
);
|
||||||
case "matomo":
|
case "matomo":
|
||||||
return matomoAnalytics.getMatomoAnalyticsScript(statusPage.analyticsScriptUrl, statusPage.analyticsId);
|
return matomoAnalytics.getMatomoAnalyticsScript(statusPage.analyticsScriptUrl, statusPage.analyticsId);
|
||||||
|
case "rybbit":
|
||||||
|
return rybbitAnalytics.getRybbitAnalyticsScript(statusPage.analyticsScriptUrl, statusPage.analyticsId);
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -39,6 +42,7 @@ function isValidAnalyticsConfig(statusPage) {
|
|||||||
case "umami":
|
case "umami":
|
||||||
case "plausible":
|
case "plausible":
|
||||||
case "matomo":
|
case "matomo":
|
||||||
|
case "rybbit":
|
||||||
return statusPage.analyticsId != null && statusPage.analyticsScriptUrl != null;
|
return statusPage.analyticsId != null && statusPage.analyticsScriptUrl != null;
|
||||||
default:
|
default:
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
const jsesc = require("jsesc");
|
||||||
|
const { escape } = require("html-escaper");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string that represents the javascript that is required to insert the Rybbit Analytics script
|
||||||
|
* into a webpage.
|
||||||
|
* @param {string} scriptUrl the Rybbit Analytics script url.
|
||||||
|
* @param {string} siteId Site ID to use with the Rybbit Analytics script.
|
||||||
|
* @returns {string} HTML script tags to inject into page
|
||||||
|
*/
|
||||||
|
function getRybbitAnalyticsScript(scriptUrl, siteId) {
|
||||||
|
let escapedScriptUrlJS = jsesc(scriptUrl, { isScriptContext: true });
|
||||||
|
let escapedSiteIdJS = jsesc(siteId, { isScriptContext: true });
|
||||||
|
|
||||||
|
if (escapedScriptUrlJS) {
|
||||||
|
escapedScriptUrlJS = escapedScriptUrlJS.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (escapedSiteIdJS) {
|
||||||
|
escapedSiteIdJS = escapedSiteIdJS.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Escape the Script url for use in an HTML attribute.
|
||||||
|
let escapedScriptUrlHTMLAttribute = escape(escapedScriptUrlJS);
|
||||||
|
|
||||||
|
// Escape the site id for use in an HTML attribute.
|
||||||
|
let escapedSiteIdHTMLAttribute = escape(escapedSiteIdJS);
|
||||||
|
|
||||||
|
return `
|
||||||
|
<script defer src="${escapedScriptUrlHTMLAttribute}" data-site-id="${escapedSiteIdHTMLAttribute}"></script>
|
||||||
|
`;
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getRybbitAnalyticsScript,
|
||||||
|
};
|
||||||
@@ -339,7 +339,7 @@ module.exports.statusPageSocketHandler = (socket) => {
|
|||||||
statusPage.modified_date = R.isoDateTime();
|
statusPage.modified_date = R.isoDateTime();
|
||||||
statusPage.analytics_id = config.analyticsId;
|
statusPage.analytics_id = config.analyticsId;
|
||||||
statusPage.analytics_script_url = config.analyticsScriptUrl;
|
statusPage.analytics_script_url = config.analyticsScriptUrl;
|
||||||
const validAnalyticsTypes = ["google", "umami", "plausible", "matomo"];
|
const validAnalyticsTypes = ["google", "umami", "plausible", "matomo", "rybbit"];
|
||||||
if (config.analyticsType !== null && !validAnalyticsTypes.includes(config.analyticsType)) {
|
if (config.analyticsType !== null && !validAnalyticsTypes.includes(config.analyticsType)) {
|
||||||
throw new Error("Invalid analytics type");
|
throw new Error("Invalid analytics type");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1383,6 +1383,7 @@
|
|||||||
"Plausible": "Plausible",
|
"Plausible": "Plausible",
|
||||||
"Matomo": "Matomo",
|
"Matomo": "Matomo",
|
||||||
"Umami": "Umami",
|
"Umami": "Umami",
|
||||||
|
"Rybbit": "Rybbit",
|
||||||
"Disable URL in Notification": "Disable URL in Notification",
|
"Disable URL in Notification": "Disable URL in Notification",
|
||||||
"Suppress Notifications": "Suppress Notifications",
|
"Suppress Notifications": "Suppress Notifications",
|
||||||
"discordSuppressNotificationsHelptext": "When enabled, messages will be posted to the channel but won't trigger push or desktop notifications for recipients.",
|
"discordSuppressNotificationsHelptext": "When enabled, messages will be posted to the channel but won't trigger push or desktop notifications for recipients.",
|
||||||
|
|||||||
@@ -161,6 +161,7 @@
|
|||||||
<option value="umami">{{ $t("Umami") }}</option>
|
<option value="umami">{{ $t("Umami") }}</option>
|
||||||
<option value="plausible">{{ $t("Plausible") }}</option>
|
<option value="plausible">{{ $t("Plausible") }}</option>
|
||||||
<option value="matomo">{{ $t("Matomo") }}</option>
|
<option value="matomo">{{ $t("Matomo") }}</option>
|
||||||
|
<option value="rybbit">{{ $t("Rybbit") }}</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user