UNPKG

strapi-plugin-logz

Version:

Automatically capture and record the API calls made to your Strapi application

1,371 lines 751 kB
import dayjs from "dayjs"; import defu from "defu"; import { json2csv } from "json-2-csv"; import require$$1 from "crypto"; import require$$0$1 from "child_process"; import * as yup$1 from "yup"; import require$$0$2 from "os"; import require$$0$4 from "path"; import require$$0$3 from "fs"; import require$$0$5 from "assert"; import require$$2 from "events"; import require$$0$7 from "buffer"; import require$$0$6 from "stream"; import require$$2$1 from "util"; import require$$0$8 from "constants"; import "node:stream"; const config = { default: () => ({ // The default configuration for the plugin skipList: [], skipEndpoints: [] }), validator(config2) { if (config2.skipList && !Array.isArray(config2.skipList)) { throw new Error("skipList must be an array"); } if (config2.skipEndpoints && !Array.isArray(config2.skipEndpoints)) { throw new Error("skipEndpoints must be an array"); } }, /** The name of the plugin @default "logz" */ pluginName: "logz" }; const bootstrap = async ({ strapi: strapi2 }) => { const actions = [ { section: "plugins", displayName: "Allow Logz dashboard access", uid: "menu-link", pluginName: config.pluginName } ]; await strapi2.admin.services.permission.actionProvider.registerMany(actions); }; const contentTypes = { logz: { // Configuration for the table that will store the logs schema: { kind: "collectionType", collectionName: "logz", info: { displayName: "Logz", singularName: "logz", pluralName: "logz", description: "The logz collection" }, options: { draftAndPublish: false, timestamps: true }, pluginOptions: { "content-manager": { visible: true }, "content-type-builder": { visible: false } }, attributes: { action: { configurable: false, type: "text", required: false }, method: { type: "string", required: true, configurable: false }, url: { type: "text", configurable: false }, statusCode: { configurable: false, type: "integer" }, user: { configurable: false, type: "relation", relation: "oneToOne", target: "plugin::users-permissions.user" }, data: { configurable: false, type: "json" } } } } }; const pluginUID$1 = `plugin::${config.pluginName}.${config.pluginName}`; const sanitizeOutput = async (data, ctx) => { const schema2 = strapi.getModel(`plugin::${config.pluginName}.${config.pluginName}`); const { auth } = ctx.state; return strapi.contentAPI.sanitize.output(data, schema2, { auth }); }; const validateQuery = async (query, ctx) => { const schema2 = strapi.getModel(`plugin::${config.pluginName}.${config.pluginName}`); const { auth } = ctx.state; return strapi.contentAPI.validate.query(query, schema2, { auth }); }; const sanitizeQuery = async (query, ctx) => { const schema2 = strapi.getModel(`plugin::${config.pluginName}.${config.pluginName}`); const { auth } = ctx.state; return strapi.contentAPI.sanitize.query(query, schema2, { auth }); }; const controller = ({ strapi: strapi2 }) => ({ /** * Download the logs as a CSV file * * Accepts a `start` and `end` query parameter to filter the logs by date */ async download(ctx) { let { start, end } = ctx.query; if (start) start = dayjs(start).startOf("day").toISOString(); if (end) end = dayjs(end).endOf("day").toISOString(); let data = await strapi2.documents(pluginUID$1).findMany({ populate: ["user"], orderBy: { createdAt: "desc" }, where: start && end && { createdAt: { $gte: start, $lt: end } } }); data = await Promise.all(data.map((d) => sanitizeOutput(d, ctx))); const csv = json2csv(data, { keys: [ { field: "id", title: "ID" }, { field: "documentId", title: "Document ID" }, { field: "action", title: "Action" }, { field: "method", title: "Method" }, { field: "url", title: "URL" }, { field: "statusCode", title: "Status Code" }, { field: "user.email", title: "User" }, { field: "data", title: "Data" }, { field: "createdAt", title: "Created At" }, { field: "updatedAt", title: "Updated At" } ], trimHeaderFields: true, excelBOM: true, emptyFieldValue: "N/A" }); const fileName = `logs-${dayjs().format("YYYY-MM-DD")}.csv`; ctx.set("Content-Type", "text/csv"); ctx.set("Content-Disposition", `attachment;filename="${fileName}"`); ctx.body = csv; }, /** * Find all logs */ async find(ctx) { await validateQuery(ctx.query, ctx); const sanitizedQuery = await sanitizeQuery(ctx.query, ctx); const query = defu({}, sanitizedQuery, { orderBy: { createdAt: "desc" } }); let { results, pagination: pagination2 } = await strapi2.plugin(config.pluginName).service("logz").find(query); results = await Promise.all(results.map((d) => sanitizeOutput(d, ctx))); ctx.body = { data: results, pagination: pagination2 }; }, /** * Find one log */ async findOne(ctx) { await validateQuery(ctx.query, ctx); const sanitizedQuery = await sanitizeQuery(ctx.query, ctx); let data = await strapi2.plugin(config.pluginName).service("logz").findOne(ctx.request.params.id, sanitizedQuery); if (data) { data = await sanitizeOutput(data, ctx); } return { data }; }, /** * Get the total number of requests, login requests, register requests, and error requests for the dashboard */ async dashboardTotals(ctx) { const totalRequests = await strapi2.plugin(config.pluginName).service("logz").count(); const totalLoginRequests = await strapi2.plugin(config.pluginName).service("logz").count({ where: { action: { $containsi: "Logged" } } }); const totalRegisterRequests = await strapi2.plugin(config.pluginName).service("logz").count({ where: { action: { $containsi: "Account created" } } }); const totalErrorRequests = await strapi2.plugin(config.pluginName).service("logz").count({ where: { statusCode: { $gte: 400 } } }); ctx.body = { data: { totalRequests, totalLoginRequests, totalRegisterRequests, totalErrorRequests } }; }, /** * Get the total number of requests over time based on the filterBy query parameter * * filterBy: `week` | `month` */ async requestOverTime(ctx) { const { filterBy = "week" } = ctx.query; const now = dayjs(); const data = []; if (filterBy === "week") { for (let i = 0; i < 7; i++) { const startOfDay = now.subtract(i, "day").startOf("day").toISOString(); const endOfDay = now.subtract(i, "day").endOf("day").toISOString(); const day = now.subtract(i, "day").format("dddd"); let total = await strapi2.db.query(pluginUID$1).count({ where: { createdAt: { $gte: startOfDay, $lt: endOfDay } } }); data.push({ name: day, total }); } } else if (filterBy === "month") { for (let i = 0; i < 7; i++) { const startOfMonth = now.subtract(i, "month").startOf("month").toISOString(); const endOfMonth = now.subtract(i, "month").endOf("month").toISOString(); const month = now.subtract(i, "month").format("MMMM"); let total = await strapi2.db.query(pluginUID$1).count({ where: { createdAt: { $gte: startOfMonth, $lt: endOfMonth } } }); data.push({ name: month, total }); } } ctx.body = { data }; }, /** * Get the total number of logins vs register requests over time based on the filterBy query parameter * * filterBy: `week` | `month` */ async loginVsRegister(ctx) { const { filterBy = "week" } = ctx.query; const now = dayjs(); const data = []; if (filterBy === "week") { for (let i = 0; i < 7; i++) { const startOfDay = now.subtract(i, "day").startOf("day").toISOString(); const endOfDay = now.subtract(i, "day").endOf("day").toISOString(); const day = now.subtract(i, "day").format("dddd"); let login = await strapi2.db.query(pluginUID$1).count({ where: { createdAt: { $gte: startOfDay, $lt: endOfDay }, action: { $containsi: "Logged" } } }); let register2 = await strapi2.db.query(pluginUID$1).count({ where: { createdAt: { $gte: startOfDay, $lt: endOfDay }, action: { $containsi: "Account created" } } }); data.push({ name: day, login, register: register2 }); } } else if (filterBy === "month") { for (let i = 0; i < 7; i++) { const startOfMonth = now.subtract(i, "month").startOf("month").toISOString(); const endOfMonth = now.subtract(i, "month").endOf("month").toISOString(); const month = now.subtract(i, "month").format("MMMM"); let login = await strapi2.db.query(pluginUID$1).count({ where: { createdAt: { $gte: startOfMonth, $lt: endOfMonth }, action: { $containsi: "Logged" } } }); let register2 = await strapi2.db.query(pluginUID$1).count({ where: { createdAt: { $gte: startOfMonth, $lt: endOfMonth }, action: { $containsi: "Account created" } } }); data.push({ name: month, login, register: register2 }); } } ctx.body = { data }; }, /** * Get the total number of GET, POST, PUT, DELETE, PATCH, "Logged in" & "Account created" requests over time based on the filterBy query parameter * * filterBy: `week` | `month` */ async mostAccessed(ctx) { const { filterBy = "week" } = ctx.query; const now = dayjs(); const data = []; if (filterBy === "week") { const start = now.endOf("day").toISOString(); const end = now.subtract(7, "day").startOf("day").toISOString(); const createdFilter = { createdAt: { $gte: end, $lt: start } }; let get = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "GET" } }); let post = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "POST", $and: [{ action: { $notContains: "Logged" } }, { action: { $notContains: "Account created" } }] } }); let put = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "PUT" } }); let patch2 = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "PATCH" } }); let del = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "DELETE" } }); let login = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, action: { $containsi: "Logged" } } }); let register2 = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, action: { $containsi: "Account created" } } }); data.push( { name: "GET", total: get }, { name: "POST", total: post }, { name: "PUT", total: put }, { name: "PATCH", total: patch2 }, { name: "DELETE", total: del }, { name: "Logged in", total: login }, { name: "Account created", total: register2 } ); } else if (filterBy === "month") { const start = now.endOf("month").toISOString(); const end = now.subtract(7, "month").startOf("month").toISOString(); const createdFilter = { createdAt: { $gte: end, $lt: start } }; let get = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "GET" } }); let post = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "POST", $and: [{ action: { $notContains: "Logged" } }, { action: { $notContains: "Account created" } }] } }); let put = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "PUT" } }); let patch2 = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "PATCH" } }); let del = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, method: "DELETE" } }); let login = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, action: { $containsi: "Logged" } } }); let register2 = await strapi2.db.query(pluginUID$1).count({ where: { ...createdFilter, action: { $containsi: "Account created" } } }); data.push( { name: "GET", total: get }, { name: "POST", total: post }, { name: "PUT", total: put }, { name: "PATCH", total: patch2 }, { name: "DELETE", total: del }, { name: "Logged in", total: login }, { name: "Account created", total: register2 } ); } ctx.body = { data }; } }); const controllers = { logz: controller }; const destroy = ({ strapi: strapi2 }) => { }; const middleware = async (ctx, next) => { await next(); const apiPrefix = strapi.config.get("api.rest.prefix", "/api"); if (ctx.request.url.startsWith(apiPrefix)) { await strapi.plugin(config.pluginName).service(config.pluginName).create(); } }; const middlewares = { requestLogged: middleware }; const policies = {}; const register = ({ strapi: strapi2 }) => { strapi2.server.use(middlewares.requestLogged); }; const routes = { // The admin only routes admin: { type: "admin", routes: [ { method: "GET", path: "/download", handler: "logz.download", config: { auth: false } }, { method: "GET", path: "/dashboard-totals", handler: "logz.dashboardTotals", config: { auth: false } }, { method: "GET", path: "/requests-over-time", handler: "logz.requestOverTime", config: { auth: false } }, { method: "GET", path: "/login-vs-register", handler: "logz.loginVsRegister", config: { auth: false } }, { method: "GET", path: "/most-accessed", handler: "logz.mostAccessed", config: { auth: false } } ] }, // The content api routes "content-api": { type: "content-api", routes: [ { method: "GET", path: "/download", handler: "logz.download" }, { method: "GET", path: "/", handler: "logz.find" }, { method: "GET", path: "/dashboard-totals", handler: "logz.dashboardTotals" }, { method: "GET", path: "/requests-over-time", handler: "logz.requestOverTime" }, { method: "GET", path: "/login-vs-register", handler: "logz.loginVsRegister" }, { method: "GET", path: "/most-accessed", handler: "logz.mostAccessed" }, { method: "GET", path: "/:id", handler: "logz.findOne" } ] } }; var commonjsGlobal = typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : {}; function getDefaultExportFromCjs(x) { return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, "default") ? x["default"] : x; } var lodash = { exports: {} }; /** * @license * Lodash <https://lodash.com/> * Copyright OpenJS Foundation and other contributors <https://openjsf.org/> * Released under MIT license <https://lodash.com/license> * Based on Underscore.js 1.8.3 <http://underscorejs.org/LICENSE> * Copyright Jeremy Ashkenas, DocumentCloud and Investigative Reporters & Editors */ lodash.exports; (function(module, exports) { (function() { var undefined$1; var VERSION = "4.17.21"; var LARGE_ARRAY_SIZE = 200; var CORE_ERROR_TEXT = "Unsupported core-js use. Try https://npms.io/search?q=ponyfill.", FUNC_ERROR_TEXT = "Expected a function", INVALID_TEMPL_VAR_ERROR_TEXT = "Invalid `variable` option passed into `_.template`"; var HASH_UNDEFINED = "__lodash_hash_undefined__"; var MAX_MEMOIZE_SIZE = 500; var PLACEHOLDER = "__lodash_placeholder__"; var CLONE_DEEP_FLAG = 1, CLONE_FLAT_FLAG = 2, CLONE_SYMBOLS_FLAG = 4; var COMPARE_PARTIAL_FLAG = 1, COMPARE_UNORDERED_FLAG = 2; var WRAP_BIND_FLAG = 1, WRAP_BIND_KEY_FLAG = 2, WRAP_CURRY_BOUND_FLAG = 4, WRAP_CURRY_FLAG = 8, WRAP_CURRY_RIGHT_FLAG = 16, WRAP_PARTIAL_FLAG = 32, WRAP_PARTIAL_RIGHT_FLAG = 64, WRAP_ARY_FLAG = 128, WRAP_REARG_FLAG = 256, WRAP_FLIP_FLAG = 512; var DEFAULT_TRUNC_LENGTH = 30, DEFAULT_TRUNC_OMISSION = "..."; var HOT_COUNT = 800, HOT_SPAN = 16; var LAZY_FILTER_FLAG = 1, LAZY_MAP_FLAG = 2, LAZY_WHILE_FLAG = 3; var INFINITY2 = 1 / 0, MAX_SAFE_INTEGER = 9007199254740991, MAX_INTEGER = 17976931348623157e292, NAN = 0 / 0; var MAX_ARRAY_LENGTH = 4294967295, MAX_ARRAY_INDEX = MAX_ARRAY_LENGTH - 1, HALF_MAX_ARRAY_LENGTH = MAX_ARRAY_LENGTH >>> 1; var wrapFlags = [ ["ary", WRAP_ARY_FLAG], ["bind", WRAP_BIND_FLAG], ["bindKey", WRAP_BIND_KEY_FLAG], ["curry", WRAP_CURRY_FLAG], ["curryRight", WRAP_CURRY_RIGHT_FLAG], ["flip", WRAP_FLIP_FLAG], ["partial", WRAP_PARTIAL_FLAG], ["partialRight", WRAP_PARTIAL_RIGHT_FLAG], ["rearg", WRAP_REARG_FLAG] ]; var argsTag = "[object Arguments]", arrayTag = "[object Array]", asyncTag = "[object AsyncFunction]", boolTag = "[object Boolean]", dateTag = "[object Date]", domExcTag = "[object DOMException]", errorTag = "[object Error]", funcTag = "[object Function]", genTag = "[object GeneratorFunction]", mapTag = "[object Map]", numberTag = "[object Number]", nullTag = "[object Null]", objectTag = "[object Object]", promiseTag = "[object Promise]", proxyTag = "[object Proxy]", regexpTag = "[object RegExp]", setTag = "[object Set]", stringTag = "[object String]", symbolTag2 = "[object Symbol]", undefinedTag = "[object Undefined]", weakMapTag = "[object WeakMap]", weakSetTag = "[object WeakSet]"; var arrayBufferTag = "[object ArrayBuffer]", dataViewTag = "[object DataView]", float32Tag = "[object Float32Array]", float64Tag = "[object Float64Array]", int8Tag = "[object Int8Array]", int16Tag = "[object Int16Array]", int32Tag = "[object Int32Array]", uint8Tag = "[object Uint8Array]", uint8ClampedTag = "[object Uint8ClampedArray]", uint16Tag = "[object Uint16Array]", uint32Tag = "[object Uint32Array]"; var reEmptyStringLeading = /\b__p \+= '';/g, reEmptyStringMiddle = /\b(__p \+=) '' \+/g, reEmptyStringTrailing = /(__e\(.*?\)|\b__t\)) \+\n'';/g; var reEscapedHtml = /&(?:amp|lt|gt|quot|#39);/g, reUnescapedHtml = /[&<>"']/g, reHasEscapedHtml = RegExp(reEscapedHtml.source), reHasUnescapedHtml = RegExp(reUnescapedHtml.source); var reEscape = /<%-([\s\S]+?)%>/g, reEvaluate = /<%([\s\S]+?)%>/g, reInterpolate = /<%=([\s\S]+?)%>/g; var reIsDeepProp = /\.|\[(?:[^[\]]*|(["'])(?:(?!\1)[^\\]|\\.)*?\1)\]/, reIsPlainProp = /^\w*$/, rePropName = /[^.[\]]+|\[(?:(-?\d+(?:\.\d+)?)|(["'])((?:(?!\2)[^\\]|\\.)*?)\2)\]|(?=(?:\.|\[\])(?:\.|\[\]|$))/g; var reRegExpChar = /[\\^$.*+?()[\]{}|]/g, reHasRegExpChar = RegExp(reRegExpChar.source); var reTrimStart = /^\s+/; var reWhitespace = /\s/; var reWrapComment = /\{(?:\n\/\* \[wrapped with .+\] \*\/)?\n?/, reWrapDetails = /\{\n\/\* \[wrapped with (.+)\] \*/, reSplitDetails = /,? & /; var reAsciiWord = /[^\x00-\x2f\x3a-\x40\x5b-\x60\x7b-\x7f]+/g; var reForbiddenIdentifierChars = /[()=,{}\[\]\/\s]/; var reEscapeChar = /\\(\\)?/g; var reEsTemplate = /\$\{([^\\}]*(?:\\.[^\\}]*)*)\}/g; var reFlags = /\w*$/; var reIsBadHex = /^[-+]0x[0-9a-f]+$/i; var reIsBinary = /^0b[01]+$/i; var reIsHostCtor = /^\[object .+?Constructor\]$/; var reIsOctal = /^0o[0-7]+$/i; var reIsUint = /^(?:0|[1-9]\d*)$/; var reLatin2 = /[\xc0-\xd6\xd8-\xf6\xf8-\xff\u0100-\u017f]/g; var reNoMatch = /($^)/; var reUnescapedString = /['\n\r\u2028\u2029\\]/g; var rsAstralRange = "\\ud800-\\udfff", rsComboMarksRange2 = "\\u0300-\\u036f", reComboHalfMarksRange = "\\ufe20-\\ufe2f", rsComboSymbolsRange2 = "\\u20d0-\\u20ff", rsComboRange = rsComboMarksRange2 + reComboHalfMarksRange + rsComboSymbolsRange2, rsDingbatRange = "\\u2700-\\u27bf", rsLowerRange = "a-z\\xdf-\\xf6\\xf8-\\xff", rsMathOpRange = "\\xac\\xb1\\xd7\\xf7", rsNonCharRange = "\\x00-\\x2f\\x3a-\\x40\\x5b-\\x60\\x7b-\\xbf", rsPunctuationRange = "\\u2000-\\u206f", rsSpaceRange = " \\t\\x0b\\f\\xa0\\ufeff\\n\\r\\u2028\\u2029\\u1680\\u180e\\u2000\\u2001\\u2002\\u2003\\u2004\\u2005\\u2006\\u2007\\u2008\\u2009\\u200a\\u202f\\u205f\\u3000", rsUpperRange = "A-Z\\xc0-\\xd6\\xd8-\\xde", rsVarRange = "\\ufe0e\\ufe0f", rsBreakRange = rsMathOpRange + rsNonCharRange + rsPunctuationRange + rsSpaceRange; var rsApos = "['’]", rsAstral = "[" + rsAstralRange + "]", rsBreak = "[" + rsBreakRange + "]", rsCombo2 = "[" + rsComboRange + "]", rsDigits = "\\d+", rsDingbat = "[" + rsDingbatRange + "]", rsLower = "[" + rsLowerRange + "]", rsMisc = "[^" + rsAstralRange + rsBreakRange + rsDigits + rsDingbatRange + rsLowerRange + rsUpperRange + "]", rsFitz = "\\ud83c[\\udffb-\\udfff]", rsModifier = "(?:" + rsCombo2 + "|" + rsFitz + ")", rsNonAstral = "[^" + rsAstralRange + "]", rsRegional = "(?:\\ud83c[\\udde6-\\uddff]){2}", rsSurrPair = "[\\ud800-\\udbff][\\udc00-\\udfff]", rsUpper = "[" + rsUpperRange + "]", rsZWJ = "\\u200d"; var rsMiscLower = "(?:" + rsLower + "|" + rsMisc + ")", rsMiscUpper = "(?:" + rsUpper + "|" + rsMisc + ")", rsOptContrLower = "(?:" + rsApos + "(?:d|ll|m|re|s|t|ve))?", rsOptContrUpper = "(?:" + rsApos + "(?:D|LL|M|RE|S|T|VE))?", reOptMod = rsModifier + "?", rsOptVar = "[" + rsVarRange + "]?", rsOptJoin = "(?:" + rsZWJ + "(?:" + [rsNonAstral, rsRegional, rsSurrPair].join("|") + ")" + rsOptVar + reOptMod + ")*", rsOrdLower = "\\d*(?:1st|2nd|3rd|(?![123])\\dth)(?=\\b|[A-Z_])", rsOrdUpper = "\\d*(?:1ST|2ND|3RD|(?![123])\\dTH)(?=\\b|[a-z_])", rsSeq = rsOptVar + reOptMod + rsOptJoin, rsEmoji = "(?:" + [rsDingbat, rsRegional, rsSurrPair].join("|") + ")" + rsSeq, rsSymbol = "(?:" + [rsNonAstral + rsCombo2 + "?", rsCombo2, rsRegional, rsSurrPair, rsAstral].join("|") + ")"; var reApos = RegExp(rsApos, "g"); var reComboMark2 = RegExp(rsCombo2, "g"); var reUnicode = RegExp(rsFitz + "(?=" + rsFitz + ")|" + rsSymbol + rsSeq, "g"); var reUnicodeWord = RegExp([ rsUpper + "?" + rsLower + "+" + rsOptContrLower + "(?=" + [rsBreak, rsUpper, "$"].join("|") + ")", rsMiscUpper + "+" + rsOptContrUpper + "(?=" + [rsBreak, rsUpper + rsMiscLower, "$"].join("|") + ")", rsUpper + "?" + rsMiscLower + "+" + rsOptContrLower, rsUpper + "+" + rsOptContrUpper, rsOrdUpper, rsOrdLower, rsDigits, rsEmoji ].join("|"), "g"); var reHasUnicode = RegExp("[" + rsZWJ + rsAstralRange + rsComboRange + rsVarRange + "]"); var reHasUnicodeWord = /[a-z][A-Z]|[A-Z]{2}[a-z]|[0-9][a-zA-Z]|[a-zA-Z][0-9]|[^a-zA-Z0-9 ]/; var contextProps = [ "Array", "Buffer", "DataView", "Date", "Error", "Float32Array", "Float64Array", "Function", "Int8Array", "Int16Array", "Int32Array", "Map", "Math", "Object", "Promise", "RegExp", "Set", "String", "Symbol", "TypeError", "Uint8Array", "Uint8ClampedArray", "Uint16Array", "Uint32Array", "WeakMap", "_", "clearTimeout", "isFinite", "parseInt", "setTimeout" ]; var templateCounter = -1; var typedArrayTags = {}; typedArrayTags[float32Tag] = typedArrayTags[float64Tag] = typedArrayTags[int8Tag] = typedArrayTags[int16Tag] = typedArrayTags[int32Tag] = typedArrayTags[uint8Tag] = typedArrayTags[uint8ClampedTag] = typedArrayTags[uint16Tag] = typedArrayTags[uint32Tag] = true; typedArrayTags[argsTag] = typedArrayTags[arrayTag] = typedArrayTags[arrayBufferTag] = typedArrayTags[boolTag] = typedArrayTags[dataViewTag] = typedArrayTags[dateTag] = typedArrayTags[errorTag] = typedArrayTags[funcTag] = typedArrayTags[mapTag] = typedArrayTags[numberTag] = typedArrayTags[objectTag] = typedArrayTags[regexpTag] = typedArrayTags[setTag] = typedArrayTags[stringTag] = typedArrayTags[weakMapTag] = false; var cloneableTags = {}; cloneableTags[argsTag] = cloneableTags[arrayTag] = cloneableTags[arrayBufferTag] = cloneableTags[dataViewTag] = cloneableTags[boolTag] = cloneableTags[dateTag] = cloneableTags[float32Tag] = cloneableTags[float64Tag] = cloneableTags[int8Tag] = cloneableTags[int16Tag] = cloneableTags[int32Tag] = cloneableTags[mapTag] = cloneableTags[numberTag] = cloneableTags[objectTag] = cloneableTags[regexpTag] = cloneableTags[setTag] = cloneableTags[stringTag] = cloneableTags[symbolTag2] = cloneableTags[uint8Tag] = cloneableTags[uint8ClampedTag] = cloneableTags[uint16Tag] = cloneableTags[uint32Tag] = true; cloneableTags[errorTag] = cloneableTags[funcTag] = cloneableTags[weakMapTag] = false; var deburredLetters2 = { // Latin-1 Supplement block. "À": "A", "Á": "A", "Â": "A", "Ã": "A", "Ä": "A", "Å": "A", "à": "a", "á": "a", "â": "a", "ã": "a", "ä": "a", "å": "a", "Ç": "C", "ç": "c", "Ð": "D", "ð": "d", "È": "E", "É": "E", "Ê": "E", "Ë": "E", "è": "e", "é": "e", "ê": "e", "ë": "e", "Ì": "I", "Í": "I", "Î": "I", "Ï": "I", "ì": "i", "í": "i", "î": "i", "ï": "i", "Ñ": "N", "ñ": "n", "Ò": "O", "Ó": "O", "Ô": "O", "Õ": "O", "Ö": "O", "Ø": "O", "ò": "o", "ó": "o", "ô": "o", "õ": "o", "ö": "o", "ø": "o", "Ù": "U", "Ú": "U", "Û": "U", "Ü": "U", "ù": "u", "ú": "u", "û": "u", "ü": "u", "Ý": "Y", "ý": "y", "ÿ": "y", "Æ": "Ae", "æ": "ae", "Þ": "Th", "þ": "th", "ß": "ss", // Latin Extended-A block. "Ā": "A", "Ă": "A", "Ą": "A", "ā": "a", "ă": "a", "ą": "a", "Ć": "C", "Ĉ": "C", "Ċ": "C", "Č": "C", "ć": "c", "ĉ": "c", "ċ": "c", "č": "c", "Ď": "D", "Đ": "D", "ď": "d", "đ": "d", "Ē": "E", "Ĕ": "E", "Ė": "E", "Ę": "E", "Ě": "E", "ē": "e", "ĕ": "e", "ė": "e", "ę": "e", "ě": "e", "Ĝ": "G", "Ğ": "G", "Ġ": "G", "Ģ": "G", "ĝ": "g", "ğ": "g", "ġ": "g", "ģ": "g", "Ĥ": "H", "Ħ": "H", "ĥ": "h", "ħ": "h", "Ĩ": "I", "Ī": "I", "Ĭ": "I", "Į": "I", "İ": "I", "ĩ": "i", "ī": "i", "ĭ": "i", "į": "i", "ı": "i", "Ĵ": "J", "ĵ": "j", "Ķ": "K", "ķ": "k", "ĸ": "k", "Ĺ": "L", "Ļ": "L", "Ľ": "L", "Ŀ": "L", "Ł": "L", "ĺ": "l", "ļ": "l", "ľ": "l", "ŀ": "l", "ł": "l", "Ń": "N", "Ņ": "N", "Ň": "N", "Ŋ": "N", "ń": "n", "ņ": "n", "ň": "n", "ŋ": "n", "Ō": "O", "Ŏ": "O", "Ő": "O", "ō": "o", "ŏ": "o", "ő": "o", "Ŕ": "R", "Ŗ": "R", "Ř": "R", "ŕ": "r", "ŗ": "r", "ř": "r", "Ś": "S", "Ŝ": "S", "Ş": "S", "Š": "S", "ś": "s", "ŝ": "s", "ş": "s", "š": "s", "Ţ": "T", "Ť": "T", "Ŧ": "T", "ţ": "t", "ť": "t", "ŧ": "t", "Ũ": "U", "Ū": "U", "Ŭ": "U", "Ů": "U", "Ű": "U", "Ų": "U", "ũ": "u", "ū": "u", "ŭ": "u", "ů": "u", "ű": "u", "ų": "u", "Ŵ": "W", "ŵ": "w", "Ŷ": "Y", "ŷ": "y", "Ÿ": "Y", "Ź": "Z", "Ż": "Z", "Ž": "Z", "ź": "z", "ż": "z", "ž": "z", "IJ": "IJ", "ij": "ij", "Œ": "Oe", "œ": "oe", "ʼn": "'n", "ſ": "s" }; var htmlEscapes = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }; var htmlUnescapes = { "&amp;": "&", "&lt;": "<", "&gt;": ">", "&quot;": '"', "&#39;": "'" }; var stringEscapes = { "\\": "\\", "'": "'", "\n": "n", "\r": "r", "\u2028": "u2028", "\u2029": "u2029" }; var freeParseFloat = parseFloat, freeParseInt = parseInt; var freeGlobal2 = typeof commonjsGlobal == "object" && commonjsGlobal && commonjsGlobal.Object === Object && commonjsGlobal; var freeSelf2 = typeof self == "object" && self && self.Object === Object && self; var root2 = freeGlobal2 || freeSelf2 || Function("return this")(); var freeExports = exports && !exports.nodeType && exports; var freeModule = freeExports && true && module && !module.nodeType && module; var moduleExports = freeModule && freeModule.exports === freeExports; var freeProcess = moduleExports && freeGlobal2.process; var nodeUtil = function() { try { var types = freeModule && freeModule.require && freeModule.require("util").types; if (types) { return types; } return freeProcess && freeProcess.binding && freeProcess.binding("util"); } catch (e) { } }(); var nodeIsArrayBuffer = nodeUtil && nodeUtil.isArrayBuffer, nodeIsDate = nodeUtil && nodeUtil.isDate, nodeIsMap = nodeUtil && nodeUtil.isMap, nodeIsRegExp = nodeUtil && nodeUtil.isRegExp, nodeIsSet = nodeUtil && nodeUtil.isSet, nodeIsTypedArray = nodeUtil && nodeUtil.isTypedArray; function apply(func, thisArg, args) { switch (args.length) { case 0: return func.call(thisArg); case 1: return func.call(thisArg, args[0]); case 2: return func.call(thisArg, args[0], args[1]); case 3: return func.call(thisArg, args[0], args[1], args[2]); } return func.apply(thisArg, args); } function arrayAggregator(array, setter, iteratee, accumulator) { var index2 = -1, length = array == null ? 0 : array.length; while (++index2 < length) { var value = array[index2]; setter(accumulator, value, iteratee(value), array); } return accumulator; } function arrayEach(array, iteratee) { var index2 = -1, length = array == null ? 0 : array.length; while (++index2 < length) { if (iteratee(array[index2], index2, array) === false) { break; } } return array; } function arrayEachRight(array, iteratee) { var length = array == null ? 0 : array.length; while (length--) { if (iteratee(array[length], length, array) === false) { break; } } return array; } function arrayEvery(array, predicate) { var index2 = -1, length = array == null ? 0 : array.length; while (++index2 < length) { if (!predicate(array[index2], index2, array)) { return false; } } return true; } function arrayFilter(array, predicate) { var index2 = -1, length = array == null ? 0 : array.length, resIndex = 0, result = []; while (++index2 < length) { var value = array[index2]; if (predicate(value, index2, array)) { result[resIndex++] = value; } } return result; } function arrayIncludes(array, value) { var length = array == null ? 0 : array.length; return !!length && baseIndexOf(array, value, 0) > -1; } function arrayIncludesWith(array, value, comparator) { var index2 = -1, length = array == null ? 0 : array.length; while (++index2 < length) { if (comparator(value, array[index2])) { return true; } } return false; } function arrayMap(array, iteratee) { var index2 = -1, length = array == null ? 0 : array.length, result = Array(length); while (++index2 < length) { result[index2] = iteratee(array[index2], index2, array); } return result; } function arrayPush(array, values) { var index2 = -1, length = values.length, offset = array.length; while (++index2 < length) { array[offset + index2] = values[index2]; } return array; } function arrayReduce(array, iteratee, accumulator, initAccum) { var index2 = -1, length = array == null ? 0 : array.length; if (initAccum && length) { accumulator = array[++index2]; } while (++index2 < length) { accumulator = iteratee(accumulator, array[index2], index2, array); } return accumulator; } function arrayReduceRight(array, iteratee, accumulator, initAccum) { var length = array == null ? 0 : array.length; if (initAccum && length) { accumulator = array[--length]; } while (length--) { accumulator = iteratee(accumulator, array[length], length, array); } return accumulator; } function arraySome(array, predicate) { var index2 = -1, length = array == null ? 0 : array.length; while (++index2 < length) { if (predicate(array[index2], index2, array)) { return true; } } return false; } var asciiSize = baseProperty("length"); function asciiToArray(string) { return string.split(""); } function asciiWords(string) { return string.match(reAsciiWord) || []; } function baseFindKey(collection, predicate, eachFunc) { var result; eachFunc(collection, function(value, key, collection2) { if (predicate(value, key, collection2)) { result = key; return false; } }); return result; } function baseFindIndex(array, predicate, fromIndex, fromRight) { var length = array.length, index2 = fromIndex + (fromRight ? 1 : -1); while (fromRight ? index2-- : ++index2 < length) { if (predicate(array[index2], index2, array)) { return index2; } } return -1; } function baseIndexOf(array, value, fromIndex) { return value === value ? strictIndexOf(array, value, fromIndex) : baseFindIndex(array, baseIsNaN, fromIndex); } function baseIndexOfWith(array, value, fromIndex, comparator) { var index2 = fromIndex - 1, length = array.length; while (++index2 < length) { if (comparator(array[index2], value)) { return index2; } } return -1; } function baseIsNaN(value) { return value !== value; } function baseMean(array, iteratee) { var length = array == null ? 0 : array.length; return length ? baseSum(array, iteratee) / length : NAN; } function baseProperty(key) { return function(object) { return object == null ? undefined$1 : object[key]; }; } function basePropertyOf2(object) { return function(key) { return object == null ? undefined$1 : object[key]; }; } function baseReduce(collection, iteratee, accumulator, initAccum, eachFunc) { eachFunc(collection, function(value, index2, collection2) { accumulator = initAccum ? (initAccum = false, value) : iteratee(accumulator, value, index2, collection2); }); return accumulator; } function baseSortBy(array, comparer) { var length = array.length; array.sort(comparer); while (length--) { array[length] = array[length].value; } return array; } function baseSum(array, iteratee) { var result, index2 = -1, length = array.length; while (++index2 < length) { var current = iteratee(array[index2]); if (current !== undefined$1) { result = result === undefined$1 ? current : result + current; } } return result; } function baseTimes(n, iteratee) { var index2 = -1, result = Array(n); while (++index2 < n) { result[index2] = iteratee(index2); } return result; } function baseToPairs(object, props) { return arrayMap(props, function(key) { return [key, object[key]]; }); } function baseTrim(string) { return string ? string.slice(0, trimmedEndIndex(string) + 1).replace(reTrimStart, "") : string; } function baseUnary(func) { return function(value) { return func(value); }; } function baseValues(object, props) { return arrayMap(props, function(key) { return object[key]; }); } function cacheHas(cache, key) { return cache.has(key); } function charsStartIndex(strSymbols, chrSymbols) { var index2 = -1, length = strSymbols.length; while (++index2 < length && baseIndexOf(chrSymbols, strSymbols[index2], 0) > -1) { } return index2; } function charsEndIndex(strSymbols, chrSymbols) { var index2 = strSymbols.length; while (index2-- && baseIndexOf(chrSymbols, strSymbols[index2], 0) > -1) { } return index2; } function countHolders(array, placeholder2) { var length = array.length, result = 0; while (length--) { if (array[length] === placeholder2) { ++result; } } return result; } var deburrLetter2 = basePropertyOf2(deburredLetters2); var escapeHtmlChar = basePropertyOf2(htmlEscapes); function escapeStringChar(chr) { return "\\" + stringEscapes[chr]; } function getValue(object, key) { return object == null ? undefined$1 : object[key]; } function hasUnicode(string) { return reHasUnicode.test(string); } function hasUnicodeWord(string) { return reHasUnicodeWord.test(string); } function iteratorToArray(iterator) { var data, result = []; while (!(data = iterator.next()).done) { result.push(data.value); } return result; } function mapToArray(map2) { var index2 = -1, result = Array(map2.size); map2.forEach(function(value, key) { result[++index2] = [key, value]; }); return result; } function overArg(func, transform2) { return function(arg) { return func(transform2(arg)); }; } function replaceHolders(array, placeholder2) { var index2 = -1, length = array.length, resIndex = 0, result = []; while (++index2 < length) { var value = array[index2]; if (value === placeholder2 || value === PLACEHOLDER) { array[index2] = PLACEHOLDER; result[resIndex++] = index2; } } return result; } function setToArray(set2) { var index2 = -1, result = Array(set2.size); set2.forEach(function(value) { result[++index2] = value; }); return result; } function setToPairs(set2) { var index2 = -1, result = Array(set2.size); set2.forEach(function(value) { result[++index2] = [value, value]; }); return result; } function strictIndexOf(array, value, fromIndex) { var index2 = fromIndex - 1, length = array.length; while (++index2 < length) { if (array[index2] === value) { return index2; } } return -1; } function strictLastIndexOf(array, value, fromIndex) { var index2 = fromIndex + 1; while (index2--) { if (array[index2] === value) { return index2; } } return index2; } function stringSize(string) { return hasUnicode(string) ? unicodeSize(string) : asciiSize(string); } function stringToArray(string) { return hasUnicode(string) ? unicodeToArray(string) : asciiToArray(string); } function trimmedEndIndex(string) { var index2 = string.length; while (index2-- && reWhitespace.test(string.charAt(index2))) { } return index2; } var unescapeHtmlChar = basePropertyOf2(htmlUnescapes); function unicodeSize(string) { var result = reUnicode.lastIndex = 0; while (reUnicode.test(string)) { ++result; } return result; } function unicodeToArray(string) { return string.match(reUnicode) || []; } function unicodeWords(string) { return string.match(reUnicodeWord) || []; } var runInContext = function runInContext2(context) { context = context == null ? root2 : _2.defaults(root2.Object(), context, _2.pick(root2, contextProps)); var Array2 = context.Array, Date2 = context.Date, Error2 = context.Error, Function2 = context.Function, Math2 = context.Math, Object2 = context.Object, RegExp2 = context.RegExp, String2 = context.String, TypeError2 = context.TypeError; var arrayProto = Array2.prototype, funcProto = Function2.prototype, objectProto2 = Object2.prototype; var coreJsData = context["__core-js_shared__"]; var funcToString = funcProto.toString; var hasOwnProperty = objectProto2.hasOwnProperty; var idCounter = 0; var maskSrcKey = function() { var uid = /[^.]+$/.exec(coreJsData && coreJsData.keys && coreJsData.keys.IE_PROTO || ""); return uid ? "Symbol(src)_1." + uid : ""; }(); var nativeObjectToString = objectProto2.toString; var objectCtorString = funcToString.call(Object2); var oldDash = root2._; var reIsNative = RegExp2( "^" + funcToString.call(hasOwnProperty).replace(reRegExpChar, "\\$&").replace(/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g, "$1.*?") + "$" ); var Buffer2 = moduleExports ? context.Buffer : undefined$1, Symbol2 = context.Symbol, Uint8Array = context.Uint8Array, allocUnsafe = Buffer2 ? Buffer2.allocUnsafe : undefined$1, getPrototype = overArg(Object2.getPrototypeOf, Object2), objectCreate = Object2.create, propertyIsEnumerable = objectProto2.propertyIsEnumerable, splice = arrayProto.splice, spreadableSymbol = Symbol2 ? Symbol2.isConcatSpreadable : undefined$1, symIterator = Symbol2 ? Symbol2.iterator : undefined$1, symToStringTag = Symbol2 ? Symbol2.toStringTag : undefined$1; var defineProperty = function() { try { var func = getNative(Object2, "defineProperty"); func({}, "", {}); return func; } catch (e) { } }(); var ctxClearTimeout = context.clearTimeout !== root2.clearTimeout && context.clearTimeout, ctxNow = Date2 && Date2.now !== root2.Date.now && Date2.now, ctxSetTimeout = context.setTimeout !== root2.setTimeout && context.setTimeout; var nativeCeil = Math2.ceil, nativeFloor = Math2.floor, nativeGetSymbols = Object2.getOwnPropertySymbols, nativeIsBuffer = Buffer2 ? Buffer2.isBuffer : undefined$1, nativeIsFinite = context.isFinite, nativeJoin = arrayProto.join, nativeKeys = overArg(Object2.keys, Object2), nativeMax = Math2.max, nativeMin = Math2.min, nativeNow = Date2.now, nativeParseInt = context.parseInt, nativeRandom = Math2.random, nativeReverse = arrayProto.reverse; var DataView = getNative(context, "DataView"), Map2 = getNative(context, "Map"), Promise2 = getNative(context, "Promise"), Set2 = getNative(context, "Set"), WeakMap2 = getNative(context, "WeakMap"), nativeCreate = getNative(Object2, "create"); var metaMap = WeakMap2 && new WeakMap2(); var realNames = {}; var dataViewCtorString = toSource(DataView), mapCtorString = toSource(Map2), promiseCtorString = toSource(Promise2), setCtorString = toSource(Set2), weakMapCtorString = toSource(WeakMap2); var symbolProto2 = Symbol2 ? Symbol2.prototype : undefined$1, symbolValueOf = symbolProto2 ? symbolProto2.valueOf : undefined$1, symbolToString2 = symbolProto2 ? symbolProto2.toString : undefined$1; function lodash2(value) { if (isObjectLike2(value) && !isArray(value) && !(value instanceof LazyWrapper)) { if (value instanceof LodashWrapper) { return value; } if (hasOwnProperty.call(value, "__wrapped__")) { return wrapperClone(value); } } return new LodashWrapper(value); } var baseCreate = /* @__PURE__ */ function() { function object() { } return function(proto) { if (!isObject2(proto)) { return {}; } if (objectCreate) { return objectCreate(proto); } object.prototype = proto; var result2 = new object(); object.prototype = undefined$1; return result2; }; }(); function baseLodash() { } function LodashWrapper(value, chainAll) { this.__wrapped__ = value; this.__actions__ = []; this.__chain__ = !!chainAll; this.__index__ = 0; this.__values__ = undefined$1; } lodash2.templateSettings = { /** * Used to detect `data` property values to be HTML-escaped. * * @memberOf _.templateSettings * @type {RegExp} */ "escape": reEscape, /** * Used to detect code to be evaluated. * * @memberOf _.templateSettings * @type {RegExp} */ "evaluate": reEvaluate, /** * Used to detect `data` property values to inject. * * @memberOf _.templateSettings * @type {RegExp} */ "interpolate": reInterpolate, /** * Used to reference the data object in the template text. * * @memberOf _.templateSettings * @type {string} */ "variable": "", /** * Used to import variables into the compiled template. * * @memberOf _.templateSettings * @type {Object} */ "imports": { /** * A reference to the `lodash` function. * * @memberOf _.templateSettings.imports * @type {Function} */ "_": lodash2 } }; lodash2.prototype = baseLodash.prototype; lodash2.prototype.constructor = lodash2; LodashWrapper.prototype = baseCreate(baseLodash.prototype); LodashWrapper.prototype.constructor = LodashWrapper; function LazyWrapper(value) { this.__wrapped__ = value; this.__actions__ = []; this.__dir__ = 1; this.__filtered__ = false; this.__iteratees__ = []; this.__takeCount__ = MAX_ARRAY_LENGTH; this.__views__ = []; } function lazyClone() { var result2 = new LazyWrapper(this.__wrapped__); result2.__actions__ = copyArray(this.__actions__); result2.__dir__ = this.__dir__; result2.__filtered__ = this.__filtered__; result2.__iteratees__ = copyArray(this.__iteratees__); result2.__takeCount__ = this.__takeCount__; result2.__views__ = copyArray(this.__views__); return result2; } function lazyReverse() { if (this.__filtered__) { var result2 = new LazyWrapper(this); result2.__dir__ = -1; result2.__filtered__ = true; } else { result2 = this.clone(); result2.__dir__ *= -1; } return result2; } function lazyValue() { var array = this.__wrapped__.value(), dir = this.__dir__, isArr = isArray(array), isRight = dir < 0, arrLength = isArr ? array.length : 0, view = getView(0, arrLength, this.__views__), start = view.start, end = view.end, length = end - start, index2 = isRight ? end : start - 1, iteratees = this.__iteratees__, iterLength = iteratees.length, resIndex = 0, takeCount = nativeMin(length, this.__takeCount__); if (!isArr || !isRight && arrLength == length && takeCount == length) { return baseWrapperValue(array, this.__actions__); } var result2 = []; outer: while (length-- && resIndex < takeCount) { index2 += dir; var iterIndex = -1, value = array[index2]; while (++iterIndex < iterLength) { var data = iteratees[iterIndex], iteratee2 = data.iteratee, type2 = data.type, computed = iteratee2(value); if (type2 == LAZY_MAP_FLAG) { value = computed; } else if (!computed) { if (type2 == LAZY_FILTER_FLAG) { continue outer; } else { break outer; } } } result2[resIndex++] = value; } return result2; } LazyWrapper.prototype = baseCreate(baseLodash.prototype); LazyWrapper.prototype.constructor = LazyWrapper; function Hash(entries) { var index2 = -1, length = entries == null ? 0 : entries.length; this.clear(); while (++index2 < length) { var entry = entries[index2]; this.set(entry[0], entry[1]); } } f