diff --git a/.github/dependabot.yml b/.github/dependabot.yml index bf976fbb00a..123ab7bd5a8 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -13,7 +13,14 @@ updates: labels: - dependencies versioning-strategy: increase-if-necessary - + ignore: + # sass >= 1.100.0 depends on ESM-only chokidar 5 and requires Node >= 20.19.0. + # The CI container (countly/countly-core:pipelines-*) ships Node 20.18.2, where + # require() of an ESM module throws ERR_REQUIRE_ESM during `countly task dist-all`. + # Keep sass on 1.99.x (chokidar ^4, dual CJS/ESM). Remove once CI Node >= 20.19.0. + - dependency-name: "sass" + versions: [">=1.100.0"] + - package-ecosystem: npm directory: "/ui-tests" schedule: @@ -26,6 +33,13 @@ updates: labels: - dependencies versioning-strategy: increase-if-necessary + ignore: + # @faker-js/faker >= 10 is ESM-only (no `require` export). The CJS fixture + # generator (require('@faker-js/faker')) throws ERR_REQUIRE_ESM on Node 20.18.2. + # Keep faker on 9.x (dual CJS/ESM). Remove once CI Node >= 20.19.0 or the + # generator scripts are converted to ESM / dynamic import(). + - dependency-name: "@faker-js/faker" + versions: [">=10.0.0"] # API utilities - package-ecosystem: npm diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index cdb083d65e1..66cbcc2e4a6 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,7 +37,7 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v7 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 8244f9587d1..43864c5672b 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -19,7 +19,7 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Enable command line shell: bash @@ -45,16 +45,16 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Log in to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with: push: true file: ./Dockerfile-core diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 882168cfcbe..fada7566d1e 100644 --- a/.github/workflows/docker-image.yml +++ b/.github/workflows/docker-image.yml @@ -12,7 +12,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set output id: vars @@ -26,13 +26,13 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with: context: . push: true @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set output id: vars @@ -57,13 +57,13 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with: push: true file: ./Dockerfile-api @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set output id: vars @@ -88,13 +88,13 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with: push: true file: ./Dockerfile-frontend @@ -105,7 +105,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v6 + uses: actions/checkout@v7 - name: Set output id: vars @@ -119,13 +119,13 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 + uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} - name: Build and push Docker image - uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f + uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf with: push: true file: ./Dockerfile-core diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 8fc9d551be8..0de409ddb6a 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -26,7 +26,7 @@ jobs: # Steps represent a sequence of tasks that will be executed as part of the job steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Copy code shell: bash @@ -96,7 +96,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Copy code shell: bash @@ -157,7 +157,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Prepare tests shell: bash @@ -211,7 +211,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Copy code shell: bash @@ -275,7 +275,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Install Chrome shell: bash diff --git a/.github/workflows/stable-je-deploy.yml b/.github/workflows/stable-je-deploy.yml index f2eaab8df53..c4841682603 100644 --- a/.github/workflows/stable-je-deploy.yml +++ b/.github/workflows/stable-je-deploy.yml @@ -19,7 +19,7 @@ jobs: steps: # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 - name: Deploy server shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index fc268bf6ba8..ea2dc53f2f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,42 @@ Dependencies: - Remove SQLite +## Version 25.03.XX +Fixes: +- [journey_engine] Added user merge handling: running journeys are remapped to the surviving user, keeping the furthest-progressed instance and stopping duplicates +- [views] Refresh drawer on app change + + +## Version 25.03.48 +Fixes: +- [core] Don't show the "no access" or initial-setup page to users who already have app access — redirect them to their default app instead + +Enterprise Fixes: +- [journey_engine] Avoid throwing on duplicate events and prevent overwriting existing event map entries during event creation +- [journey_engine] Fixed incoming-data journeys double-entering a user when a single request carried multiple events (e.g. a custom event together with begin_session's session event) + +Enterprise Features: +- [block] Allow using regex for event filter +- [journey_engine] Added an end-to-end API test suite covering every trigger type (event/session/crash incoming-data, profile-update, journey-exit, profile-group entry/exit) that delivers in-app content through the queue + +## Version 25.03.47 +Fixes: +- [content] Bugfixes for content showing +- [core] Improved validation for user passed queries. +- [journey_engine] resut tab made available for running journeys + +Enterprise Features: +-[data-manager] Improved user propertly value table to allow filtering all values. + +## Version 25.03.46 +Fixes: + - Overall security Fixes + - Ensuring Countly working from a network subdirectory + +Enterprise Features: + - [active_directory] Journey approver group added + - [ldap] Journey approver group added + ## Version 25.03.45 Fixes: - [core] Accept numeric color in saveNote schema so graph note create/edit no longer fails validation diff --git a/CLAUDE.md b/CLAUDE.md index 39c24fe97bd..48f16100c3e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -50,6 +50,27 @@ countly shellcheck # Validate shell scripts 5. **Never use v-html with user data** in Vue templates. +6. **Validate user-supplied Mongo queries — reject, never strip**. Any query/filter that comes from a request and reaches `find`/`aggregate`/`update`/`delete` must be checked with the `common` helpers at the endpoint where it is first parsed (NOT inside deep helpers or the `/drill/preprocess_query` hook). The query is run exactly as submitted or the request is rejected with `400` — it is never modified. + ```javascript + // raw query STRING from a request param → parse + validate in one step + var parsed = common.parseUserQuery(params.qstring.query); // accepts string OR object + if (parsed.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsed.error); + return common.returnMessage(params, 400, parsed.error); + } + var query = parsed.query; // safe to run as-is + + // ALREADY-parsed object — e.g. dbviewer parses with EJSON, or the query is + // nested in a larger saved payload. Validate that parsed object directly: + var parsedQuery = EJSON.parse(params.qstring.filter); // example: already parsed (EJSON / stored doc) + var badOp = common.findUnsafeMongoOperator(parsedQuery); + if (badOp) { + log.d("Rejected user query" + common.reqInfo(params) + ": Query contains disallowed operator: " + badOp); + return common.returnMessage(params, 400, "Query contains disallowed operator: " + badOp); + } + ``` + `$expr` is allowed; `$where`/`$function`/`$accumulator` are rejected at any depth (including nested inside `$expr`). Log the rejection at the call site using the file's `log` and `common.reqInfo(params)` (which adds the endpoint path/method, without the api_key). Do NOT pass `params` into `parseUserQuery` and do NOT log inside it. + ## File Locations | What | Where | diff --git a/SECURITY.md b/SECURITY.md index f5de9cb6879..be2163365cc 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -13,7 +13,28 @@ All software related security bugs with severity of medium and higher will be aw **Low** - no bounty rewards, does not directly lead to vulnerability, but provides a possibility (like exposing software version, which can be mapped to specific vulnerabilities), old dependencies, server misconfiguration -**Exclusion** +**Exclusions (out of scope — not eligible for bounty)** -Server specific configurations and deployment specific configurations due to on premise nature of our software. -All server configuration related issues will be reported to related departments/parties/companies, but we cannot guarantee any bounty rewards for them. +The following are out of scope. They may still be reported, and configuration issues will be forwarded to the relevant parties, but they do not qualify for a bounty reward: + +1. **Deployment & server configuration.** Server-specific and deployment-specific configuration issues, due to the on-premises nature of our software (TLS setup, reverse-proxy/CORS/header configuration, exposed ports, OS/database hardening, rate-limiting tuning, etc.). These are forwarded to the relevant departments/parties/companies but carry no bounty guarantee. + +2. **Privileged / admin-only endpoints behaving as designed.** Endpoints intended to be used only by authenticated global administrators or trusted server operators — for example `/mobile-login` and other operator/management endpoints — are not vulnerabilities when they require the privileges they are designed to require. "A global admin can do X across the system" is by design; global admin is a fully trusted role. + +3. **By-design cross-app access for global admins.** Plugins and features whose documented purpose is to aggregate or operate on data across multiple applications for global administrators are working as intended. Accessing cross-app data while holding global-admin rights is not a privilege escalation. + +4. **Findings that require code already fixed in the current codebase.** Reports reproduced only against an outdated or unpatched running server, demo, or hosted instance — where the issue is already fixed in the current source — are not eligible. Bounty assessment is made against the current code in this repository. + +5. **Excluded plugins.** Plugins that are not enabled by default — i.e. not listed in `plugins/plugins.default.json` — are out of scope, since they may be experimental, uncommonly used, or deprecated. In addition, the `consolidate` and `errorlogs` plugins are out of scope even though they are enabled by default. + +6. **Reliance on already-privileged access.** Issues that require the attacker to already hold rights equal to or greater than the access obtained (e.g. needing global admin to reach data a global admin already sees), or that depend on knowing a non-enumerable identifier of another tenant that is only ever exposed to authorized users. + +7. **Duplicates and already-known issues.** Reports duplicating an already-reported or already-fixed issue; only the first actionable report is eligible. + +8. **Theoretical, self-inflicted, or hardening-only issues.** Issues without a working proof of concept, self-inflicted issues (self-XSS, pasting attacker scripts into one's own console/session), and missing best-practice hardening that does not itself lead to an exploit (covered under "Low" above). + +9. **Hooks custom-code effects.** The Hooks plugin's custom-code effect runs operator-supplied JavaScript and is being migrated to a stronger isolation model (`isolated-vm`) in an upcoming release, which removes the existing execution surface entirely. Issues that depend on the behaviour of the current custom-code sandbox (for example escaping or abusing the bundled sandbox's built-in helpers) are out of scope. Note that the Hooks plugin already requires an authenticated account with the relevant per-app hooks permission, and the custom-code effect executes code the operator themselves configured. + +10. **Instances of a vulnerability class already under active remediation.** Findings that are additional instances of a vulnerability class we are already remediating — including work visible in an open or in-progress pull request, a public branch, or another not-yet-released fix — are considered part of that known, ongoing effort and are not separately eligible. Enumerating sibling occurrences of an issue from our published or in-progress remediation is not an independent discovery. Independently discovered issues remain welcome. + +11. **Cross-site scripting (XSS) without a working proof of concept.** XSS reports that do not demonstrate actual script execution in an authenticated dashboard session are out of scope. Pointing at a potential sink (for example a `v-html` binding or a DOM write) is not sufficient on its own, since the value reaching a sink may already be neutralized elsewhere in the request handling or rendering pipeline. XSS with a working end-to-end proof of concept — including DOM-based XSS that originates from the URL or other client-controlled input — is in scope and welcome. diff --git a/api/api.js b/api/api.js index 71422fa06a8..dd11a6c23f0 100644 --- a/api/api.js +++ b/api/api.js @@ -441,6 +441,17 @@ plugins.connectToAllDatabases().then(function() { key: fs.readFileSync(common.config.api.ssl.key), cert: fs.readFileSync(common.config.api.ssl.cert) }; + // Optional: let operators pin the negotiated TLS protocol range + // (e.g. minVersion "TLSv1.2"). Left unset by default so Node keeps + // its built-in defaults — deployments that still require older + // protocols, or that terminate TLS at nginx/their webserver, are + // unaffected. + if (common.config.api.ssl.minVersion) { + sslOptions.minVersion = common.config.api.ssl.minVersion; + } + if (common.config.api.ssl.maxVersion) { + sslOptions.maxVersion = common.config.api.ssl.maxVersion; + } if (common.config.api.ssl.ca) { sslOptions.ca = fs.readFileSync(common.config.api.ssl.ca); } diff --git a/api/config.sample.js b/api/config.sample.js index adf0987d0f8..d28580052ac 100644 --- a/api/config.sample.js +++ b/api/config.sample.js @@ -74,6 +74,8 @@ var countlyConfig = { key: "/path/to/ssl/private.key", cert: "/path/to/ssl/certificate.crt", // ca: "/path/to/ssl/ca_bundle.crt" // Optional: for client certificate verification, uncomment to activate + // minVersion: "TLSv1.2", // Optional: pin the lowest allowed TLS protocol (e.g. "TLSv1.2"). Unset = Node defaults + // maxVersion: "TLSv1.3", // Optional: pin the highest allowed TLS protocol. Unset = Node defaults } }, /** diff --git a/api/parts/data/events.js b/api/parts/data/events.js index e1dd8e1e6ce..284b26c518e 100644 --- a/api/parts/data/events.js +++ b/api/parts/data/events.js @@ -329,6 +329,10 @@ function processEvents(appEvents, appSegments, appSgValues, params, omitted_segm if (segKey === "") { continue; } + //skip keys that map to object prototype members when used as field names + if (segKey === "__proto__" || segKey === "constructor" || segKey === "prototype") { + continue; + } if (pluginsGetConfig.event_segmentation_limit && appSegments[currEvent.key] && diff --git a/api/parts/data/exports.js b/api/parts/data/exports.js index 49fc6c5df00..4470881c1a7 100644 --- a/api/parts/data/exports.js +++ b/api/parts/data/exports.js @@ -500,12 +500,71 @@ function isObjectId(id) { * @param {string} [options.filename] - name of the file to output, by default auto generated * @param {function} options.output - callback function where to pass data, by default outputs as file based on type */ + +/** +* Credential fields stripped from privileged collections on export, mirroring +* the DB Viewer read paths so the same data cannot be obtained through export. +*/ +var EXPORT_REDACTIONS = { + "members": ["password", "api_key", "two_factor_auth"], + "auth_tokens": ["_id"] +}; + +/** +* Whether documents from the given collection require credential redaction on export +* @param {string} collection - collection name +* @returns {boolean} true if the collection is redacted on export +*/ +exports.redactsExportCollection = function(collection) { + return Object.prototype.hasOwnProperty.call(EXPORT_REDACTIONS, collection); +}; + +/** +* Remove/mask credential fields from a document before it is written to an export +* @param {string} collection - collection the document belongs to +* @param {object} doc - document to redact (mutated and returned) +* @returns {object} the redacted document +*/ +exports.redactExportDoc = function(collection, doc) { + if (!doc || !exports.redactsExportCollection(collection)) { + return doc; + } + var fields = EXPORT_REDACTIONS[collection]; + for (var i = 0; i < fields.length; i++) { + if (fields[i] === "_id") { + // _id is normally present (fromDatabase keeps it unless explicitly + // excluded via projection), so mask its value rather than deleting it + if (typeof doc._id !== "undefined") { + doc._id = "***redacted***"; + } + } + else { + delete doc[fields[i]]; + } + } + return doc; +}; + exports.fromDatabase = function(options) { options.db = options.db || common.db; options.query = options.query || {}; options.projection = options.projection || {}; options.writeHeaders = true; + // Reject (never modify) export queries that contain disallowed Mongo + // operators ($where/$function/$accumulator). options.query is dispatched to + // /drill/preprocess_query and then run against the database; the dispatch + // hook no longer strips these operators, so validate the already-parsed + // query here at the boundary into the dispatch + DB find. + var badOp = common.findUnsafeMongoOperator(options.query); + if (badOp) { + log.d("Rejected user query" + common.reqInfo(options.params) + ": " + common.unsafeQueryError(badOp)); + if (options.params) { + common.returnMessage(options.params, 400, common.unsafeQueryError(badOp)); + } + return; + } + if (options.params && options.params.qstring && options.params.qstring.formatFields) { options.mapper = options.params.qstring.formatFields; } @@ -552,6 +611,15 @@ exports.fromDatabase = function(options) { options.query._id = common.db.ObjectID(options.query._id); } var cursor = options.db.collection(options.collection).find(options.query, {"projection": options.projection}); + // Strip credential material from privileged collections before it + // can reach an export file, mirroring the DB Viewer read paths. This + // runs at the cursor level so it applies regardless of output type + // (json/csv/xls) and regardless of the caller supplied projection. + if (exports.redactsExportCollection(options.collection)) { + cursor = cursor.map(function(doc) { + return exports.redactExportDoc(options.collection, doc); + }); + } if (options.sort) { cursor.sort(options.sort); } diff --git a/api/parts/data/fetch.js b/api/parts/data/fetch.js index 8934dd03419..c301b440160 100644 --- a/api/parts/data/fetch.js +++ b/api/parts/data/fetch.js @@ -8,6 +8,7 @@ const { getAdminApps, getUserApps } = require('../../utils/rights.js'); /** @lends module:api/parts/data/fetch */ var fetch = {}, common = require('./../../utils/common.js'), + log = common.log('core:api'), moment = require('moment-timezone'), async = require('async'), countlyModel = require('../../lib/countly.model.js'), @@ -519,12 +520,13 @@ fetch.fetchAllApps = function(params) { var filter = {}; if (params.qstring.filter) { - try { - filter = JSON.parse(params.qstring.filter); - } - catch (ex) { - filter = {}; + var parsed = common.parseUserQuery(params.qstring.filter); + if (parsed.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsed.error); + common.returnMessage(params, 400, parsed.error); + return; } + filter = parsed.query; } if (!params.member.global_admin) { @@ -2263,6 +2265,9 @@ fetch.alljobs = async function(metric, params) { fetch.jobDetails = async function(metric, params) { const columns = ["schedule", "next", "finished", "status", "data", "duration"]; let sort = {}; + if (typeof params.qstring.name !== "undefined" && params.qstring.name !== null) { + params.qstring.name = params.qstring.name + ""; + } const total = await common.db.collection('jobs').count({ name: params.qstring.name }); const cursor = common.db.collection('jobs').find({ name: params.qstring.name }); sort[columns[params.qstring.iSortCol_0 || 0]] = (params.qstring.sSortDir_0 === "asc") ? 1 : -1; diff --git a/api/parts/mgmt/app_users.js b/api/parts/mgmt/app_users.js index fcac2a6e601..d2f17355f53 100644 --- a/api/parts/mgmt/app_users.js +++ b/api/parts/mgmt/app_users.js @@ -49,9 +49,9 @@ usersApi.create = function(app_id, doc, params, callback) { callback("App does not exist"); return; } - var _id = common.crypto.createHash('sha1').update(app.key + doc.did + "").digest('hex'); + var _id = common.getAppUserId(app, doc.did); if (doc._id && doc._id !== _id) { - callback("Based on app key and device_id, provided _id property should be " + _id + ". Do not provide _id if you want api to use correct one"); + callback("Based on the app identity and device_id, provided _id property should be " + _id + ". Do not provide _id if you want api to use correct one"); return; } doc._id = _id; @@ -121,7 +121,6 @@ usersApi.update = function(app_id, query, update, params, callback) { callback = params; params = {}; } - common.stripUnsafeMongoOperators(query); plugins.dispatch("/drill/preprocess_query", { query: query }); @@ -178,7 +177,6 @@ usersApi.delete = function(app_id, query, params, callback) { query = {}; } } - common.stripUnsafeMongoOperators(query); plugins.dispatch("/drill/preprocess_query", { query: query }); @@ -303,7 +301,6 @@ usersApi.search = function(app_id, query, project, sort, limit, skip, callback) query = {}; } } - common.stripUnsafeMongoOperators(query); plugins.dispatch("/drill/preprocess_query", { query: query @@ -364,7 +361,6 @@ usersApi.count = function(app_id, query, callback) { query = {}; } } - common.stripUnsafeMongoOperators(query); plugins.dispatch("/drill/preprocess_query", { query: query @@ -1037,7 +1033,6 @@ usersApi.export = function(app_id, query, params, callback) { }); } - common.stripUnsafeMongoOperators(query); plugins.dispatch("/drill/preprocess_query", { query: query }); @@ -1293,22 +1288,17 @@ usersApi.loyalty = function(params) { const collectionName = 'app_users' + params.qstring.app_id; let query = params.qstring.query || {}; - if (typeof query === "string") { - try { - query = JSON.parse(query); - common.stripUnsafeMongoOperators(query); - plugins.dispatch("/drill/preprocess_query", { - query: query, - params - }); - } - catch (error) { - query = {}; - } - } - else { - common.stripUnsafeMongoOperators(query); + var parsedQuery = common.parseUserQuery(query); + if (parsedQuery.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedQuery.error); + common.returnMessage(params, 400, parsedQuery.error); + return; } + query = parsedQuery.query; + plugins.dispatch("/drill/preprocess_query", { + query: query, + params + }); if (cohorts) { var cohortQuery = cohorts.preprocessQuery(query); diff --git a/api/parts/mgmt/apps.js b/api/parts/mgmt/apps.js index 3f1b17db224..04a2be31992 100644 --- a/api/parts/mgmt/apps.js +++ b/api/parts/mgmt/apps.js @@ -287,12 +287,26 @@ appsApi.createApp = async function(params) { if (!newApp.key || newApp.key === "") { newApp.key = appKey; } - + //freeze the immutable identity key used to derive app user ids, so the app + //key can later be rotated without re-keying existing users + newApp.id_key = newApp.key; + //all currently accepted keys, including the current one. Old keys remain + //valid (and are tracked with last_data) until an admin removes them, so a + //key rotation does not break already-deployed SDK clients. + newApp.keys = [{key: newApp.key, added_at: Math.floor(Date.now() / 1000), last_data: 0}]; + + //run the uniqueness check against the actual key being stored, including + //an auto-generated one (checkUniqueKey only inspects args.key and would + //otherwise skip validation entirely when the key was generated) + params.qstring.args.key = newApp.key; checkUniqueKey(params, function() { common.db.collection('apps').insert(newApp, function(err, app) { if (!err && app && app.ops && app.ops[0] && app.ops[0]._id) { newApp._id = app.ops[0]._id; + //index for resolving incoming requests by any accepted key + common.db.collection('apps').ensureIndex({"keys.key": 1}, { background: true }, function() {}); + common.db.collection('app_users' + app.ops[0]._id).ensureIndex({ls: -1}, { background: true }, function() {}); common.db.collection('app_users' + app.ops[0]._id).ensureIndex({"uid": 1}, { background: true }, function() {}); common.db.collection('app_users' + app.ops[0]._id).ensureIndex({"sc": 1}, { background: true }, function() {}); @@ -416,7 +430,22 @@ appsApi.updateApp = function(params) { // argProps explicitly (or, for plugin-namespaced settings, routed through // /i/apps/update/plugins/). - if (Object.keys(updatedApp).length === 0) { + //optional list of old keys to retire. Read directly (not part of the + //schema) so users cannot set arbitrary key metadata; only remove by value. + var removeKeys = params.qstring.args.remove_keys; + if (typeof removeKeys === "string") { + try { + removeKeys = JSON.parse(removeKeys); + } + catch (e) { + removeKeys = [removeKeys]; + } + } + if (!Array.isArray(removeKeys)) { + removeKeys = []; + } + + if (Object.keys(updatedApp).length === 0 && removeKeys.length === 0) { common.returnMessage(params, 200, 'Nothing changed'); return true; } @@ -429,6 +458,34 @@ appsApi.updateApp = function(params) { common.returnMessage(params, 404, 'App not found'); } else { + //freeze the immutable identity key before any change is applied. If + //this app predates id_key, capture the current (old) key now so app + //user ids stay derived from it even if this update rotates the key. + if (typeof appBefore.id_key === "undefined" || appBefore.id_key === null) { + updatedApp.id_key = appBefore.key; + } + + //maintain the set of accepted keys. Lazily materialize it for apps + //that predate the keys array (no upgrade script needed), keep old + //keys valid after a rotation, and apply any requested removals + //(never the current key, which always stays accepted). + var nowSec = Math.floor(Date.now() / 1000); + var keysArr = (Array.isArray(appBefore.keys) && appBefore.keys.length) + ? appBefore.keys.slice() + : [{key: appBefore.key, added_at: appBefore.created_at || nowSec, last_data: appBefore.last_data || 0}]; + var currentKey = (typeof updatedApp.key !== "undefined" && updatedApp.key !== "") ? updatedApp.key : appBefore.key; + if (removeKeys.length) { + keysArr = keysArr.filter(function(k) { + return removeKeys.indexOf(k.key) === -1 || k.key === currentKey; + }); + } + if (!keysArr.some(function(k) { + return k.key === currentKey; + })) { + keysArr.push({key: currentKey, added_at: nowSec, last_data: 0}); + } + updatedApp.keys = keysArr; + checkUniqueKey(params, function() { if ((params.member && params.member.global_admin) || hasUpdateRight(FEATURE_NAME, params.qstring.args.app_id, params.member)) { common.db.collection('apps').update({'_id': common.db.ObjectID(params.qstring.args.app_id)}, {$set: updatedApp, "$unset": {"checksum_salt": ""}}, function() { @@ -1037,6 +1094,7 @@ function packApps(apps) { 'category': apps[i].category, 'country': apps[i].country, 'key': apps[i].key, + 'keys': apps[i].keys || [{key: apps[i].key, added_at: apps[i].created_at || 0, last_data: apps[i].last_data || 0}], 'name': apps[i].name, 'timezone': apps[i].timezone, 'salt': apps[i].salt || apps[i].checksum_salt || "", @@ -1148,7 +1206,10 @@ function checkUniqueKey(params, callback, update) { callback(); } else { - var query = {key: params.qstring.args.key}; + //a key must be globally unique across every app's current key AND every + //app's set of still-accepted (old) keys + var key = params.qstring.args.key + ""; + var query = {$or: [{key: key}, {"keys.key": key}]}; if (update) { query._id = {$ne: common.db.ObjectID(params.qstring.args.app_id + "")}; } diff --git a/api/parts/mgmt/cms.js b/api/parts/mgmt/cms.js index facb6470e38..96449c09a3f 100644 --- a/api/parts/mgmt/cms.js +++ b/api/parts/mgmt/cms.js @@ -198,11 +198,14 @@ cmsApi.getEntriesWithUpdate = function(params) { var query = { '_id': { '$regex': `^${params.qstring._id}` } }; - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - params.qstring.query = null; + if (params.qstring.query) { + var parsed = common.parseUserQuery(params.qstring.query); + if (parsed.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsed.error); + common.returnMessage(params, 400, parsed.error); + return false; + } + params.qstring.query = parsed.query; } if (params.qstring.query) { @@ -282,11 +285,14 @@ cmsApi.getEntries = function(params) { var query = { '_id': { '$regex': `^${params.qstring._id}` } }; - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - params.qstring.query = null; + if (params.qstring.query) { + var parsed = common.parseUserQuery(params.qstring.query); + if (parsed.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsed.error); + common.returnMessage(params, 400, parsed.error); + return false; + } + params.qstring.query = parsed.query; } if (params.qstring.query) { diff --git a/api/parts/mgmt/mail.js b/api/parts/mgmt/mail.js index 19e01d517fd..8f262213183 100644 --- a/api/parts/mgmt/mail.js +++ b/api/parts/mgmt/mail.js @@ -203,7 +203,7 @@ mail.sendToNewMember = function(member, memberPassword) { const password = mail.escapedHTMLString(memberPassword); mail.lookup(function(err, host) { localize.getProperties(member.lang, function(err2, properties) { - var message = localize.format(properties["mail.new-member"], mail.getUserFirstName(member), host, member.username, password); + var message = localize.format(properties["mail.new-member"], mail.escapedHTMLString(mail.getUserFirstName(member)), host, mail.escapedHTMLString(member.username), password); mail.sendMessage(member.email, properties["mail.new-member-subject"], message); }); }); diff --git a/api/parts/mgmt/users.js b/api/parts/mgmt/users.js index 9560efa9d0b..5510c534aa6 100644 --- a/api/parts/mgmt/users.js +++ b/api/parts/mgmt/users.js @@ -286,9 +286,15 @@ usersApi.createUser = async function(params) { mail.sendToNewMemberLink(member[0], prid); }); + // Broadcast a credential-free copy of the new member to + // event listeners (e.g. hooks): the member's password hash + // and api_key must never be forwarded into a hook payload. + var createdMemberEventData = Object.assign({}, member[0]); + delete createdMemberEventData.password; + delete createdMemberEventData.api_key; plugins.dispatch("/i/users/create", { params: params, - data: member[0] + data: createdMemberEventData }); delete member[0].password; @@ -558,10 +564,17 @@ usersApi.updateUser = async function(params) { common.db.collection('members').findOne({ '_id': common.db.ObjectID(params.qstring.args.user_id) }, function(err2, member) { if (member && !err2) { updatedMember._id = params.qstring.args.user_id; + // never forward credentials into event payloads (e.g. hooks) + var updatedMemberEventData = Object.assign({}, updatedMember); + delete updatedMemberEventData.password; + delete updatedMemberEventData.api_key; + var memberBeforeEventData = Object.assign({}, memberBefore); + delete memberBeforeEventData.password; + delete memberBeforeEventData.api_key; plugins.dispatch("/i/users/update", { params: params, - data: updatedMember, - member: memberBefore + data: updatedMemberEventData, + member: memberBeforeEventData }); if (params.qstring.args.send_notification && passwordNoHash) { mail.sendToUpdatedMember(member, passwordNoHash); @@ -616,10 +629,14 @@ usersApi.deleteUser = async function(params) { else { const user = await common.db.collection('members').findOne({ '_id': common.db.ObjectID(userIds[i]) }); const promisifiedDispatch = function(prms, data) { + // never forward credentials into event payloads (e.g. hooks) + var safeData = Object.assign({}, data); + delete safeData.password; + delete safeData.api_key; return new Promise((resolve, reject) => { plugins.dispatch("/i/users/delete", { params: prms, - data, + data: safeData, }, async(__, otherPluginResults) => { const rejectReasons = otherPluginResults.reduce((acc, result) => { if (result.status === "rejected") { @@ -813,6 +830,10 @@ usersApi.deleteOwnAccount = function(params) { }; if (member) { + // never forward credentials into event payloads (e.g. hooks) + var memberEventData = Object.assign({}, member); + delete memberEventData.password; + delete memberEventData.api_key; if (member.global_admin) { common.db.collection('members').count({'global_admin': true}, function(err2, count) { if (err2) { @@ -825,7 +846,7 @@ usersApi.deleteOwnAccount = function(params) { else { plugins.dispatch("/i/users/delete", { params: params, - data: member + data: memberEventData }, dispatchDeleteCallback); } }); @@ -833,7 +854,7 @@ usersApi.deleteOwnAccount = function(params) { else { plugins.dispatch("/i/users/delete", { params: params, - data: member + data: memberEventData }, dispatchDeleteCallback); } } diff --git a/api/utils/common.js b/api/utils/common.js index be62bfcd055..05119c59521 100644 --- a/api/utils/common.js +++ b/api/utils/common.js @@ -420,6 +420,95 @@ common.sha1Hash = function(str, addSalt) { return crypto.createHmac('sha1', salt + '').update(str + '').digest('hex'); }; +/** +* Derive the internal app user id (_id of the app_users document) from an app +* and a device id. The id is derived from the app's immutable identity key +* (app.id_key) rather than its current app key, so the app key can be rotated +* without re-keying existing users. For apps that predate id_key the value is +* absent and we fall back to the current key, which equals the value the id was +* originally derived from, keeping existing ids byte-identical (no migration). +* @param {object} app - the app document (must have key and optionally id_key) +* @param {string} deviceId - the device id from the SDK request +* @returns {string} sha1 hex app user id +*/ +common.getAppUserId = function(app, deviceId) { + var idKey = (app && (app.id_key || app.key)) || ""; + return crypto.createHash('sha1').update(idKey + deviceId + "").digest('hex'); +}; + +/** +* Record the last time data was received for the specific app key used on a +* request. Lets administrators see whether an old (rotated-away) key is still +* in use before retiring it. Throttled to at most one write per key per hour to +* keep it off the hot path; also updates the in-memory copy so a cached app +* document does not trigger repeated writes within the cache window. +* @param {object} app - the app document (with keys array) +* @param {string} usedKey - the app key value the request authenticated with +* @returns {void} +*/ +common.recordAppKeyUsage = function(app, usedKey) { + if (!app || !Array.isArray(app.keys) || !common.db) { + return; + } + var k = usedKey + ""; + var nowSec = Math.floor(Date.now() / 1000); + for (var i = 0; i < app.keys.length; i++) { + if (app.keys[i].key === k) { + if (!app.keys[i].last_data || app.keys[i].last_data < nowSec - 3600) { + app.keys[i].last_data = nowSec; + common.db.collection("apps").update({_id: app._id, "keys.key": k}, {$set: {"keys.$.last_data": nowSec}}, function() {}); + } + return; + } + } +}; + +/** +* Resolve an app by an incoming SDK app key for the ingestion/fetch hot path. +* Looks up the accepted-keys array first, then falls back to the current-key +* field. This is deliberately two single-field index lookups rather than one +* {$or:[{key},{keys.key}]} query: MongoDB cannot reliably serve such an $or via +* index union, so on this hot path it degrades to a full collection scan of the +* apps collection. Each lookup here is an indexed equality, and the readBatcher +* caches both (including misses), so the DB sees at most one query per shape per +* cache period. +* +* The keys.key lookup matches every app once its accepted-keys array exists +* (createApp/updateApp populate it, plus a one-time startup backfill), and also +* matches a rotated-away old key during its grace period. The key fallback keeps +* apps that predate the array — or that have not been backfilled yet — fully +* resolvable, so correctness never depends on the backfill having run. +* @param {string} appKey - incoming SDK app key +* @param {function} callback - callback(err, app) with the resolved app or null +* @returns {void} +*/ +common.resolveAppByKey = function(appKey, callback) { + var key = appKey + ""; + common.readBatcher.getOne("apps", {"keys.key": key}, function(err, app) { + if (app) { + return callback(err, app); + } + common.readBatcher.getOne("apps", {"key": key}, function(err2, app2) { + if (app2) { + return callback(err2, app2); + } + return callback(err2 || err, null); + }); + }); +}; + +/** +* Invalidate any cached apps entry that resolveAppByKey may have loaded for an +* incoming SDK key, covering both lookup shapes (keys.key and key). +* @param {string} appKey - incoming SDK app key +* @returns {void} +*/ +common.invalidateAppByKey = function(appKey) { + var key = appKey + ""; + common.readBatcher.invalidate("apps", {"keys.key": key}, {}, false); + common.readBatcher.invalidate("apps", {"key": key}, {}, false); +}; + common.sha512Hash = function(str, addSalt) { var salt = (addSalt) ? new Date().getTime() : ''; return crypto.createHmac('sha512', salt + '').update(str + '').digest('hex'); @@ -1338,7 +1427,10 @@ common.returnRaw = function(params, returnCode, body, heads) { else { console.error("Output already closed, can't write more"); console.trace(); - console.log(params); + // Don't dump the full params object — req.body/req.headers can + // contain credentials, session cookies, or other secrets. Log + // only the pathname (query string can carry api_key/auth_token). + console.log({pathname: params.urlParts && params.urlParts.pathname, apiPath: params.apiPath, qstringKeys: params.qstring && Object.keys(params.qstring)}); } } }; @@ -2331,55 +2423,171 @@ common.checkPromise = function(func, count, interval) { }; /** - * Recursively remove MongoDB operators that allow arbitrary JavaScript - * execution or comparison-bypass against trusted server state from a - * user-supplied query object. Strips: + * MongoDB query operators that allow arbitrary server-side JavaScript + * execution. These have no legitimate use in a user-supplied query and + * are rejected outright (never stripped) wherever a query enters the API. * * $where — evaluates a JS function on every doc; supports * `while(true){}` DoS and timing-based exfiltration. - * $expr — evaluates aggregation expressions against the doc; - * can be chained with $function/$accumulator below. * $function — Mongo 4.4+ server-side JS execution. * $accumulator — server-side JS aggregation execution. * - * Use this anywhere a request body / query string is passed straight - * into MongoDB find/update/delete/count/aggregate as the filter. It is - * a defence-in-depth helper, not a substitute for a strict allowlist - * of fields. + * $expr is intentionally NOT listed: it executes no JS by itself and has + * legitimate uses (e.g. cross-field comparisons). The walk below still + * descends into a $expr sub-expression, so a $function/$accumulator/$where + * nested inside $expr is still detected. + */ +common.UNSAFE_MONGO_OPERATORS = ["$where", "$function", "$accumulator"]; + +/** + * Sentinel returned by findUnsafeMongoOperator when a query is nested deeper + * than the recursion cap. Not a real operator; treated as a rejection. + */ +common.UNSAFE_QUERY_TOO_DEEP = "$__nestedTooDeep"; + +/** + * Turn a findUnsafeMongoOperator() result into a client-safe error message. + * Maps the too-deep sentinel to a clear message instead of leaking it as an + * "operator" name. Use this at every site that rejects on findUnsafeMongoOperator. + * + * @param {string} bad - the value returned by common.findUnsafeMongoOperator + * @returns {string} a client-safe error message + */ +common.unsafeQueryError = function(bad) { + if (bad === common.UNSAFE_QUERY_TOO_DEEP) { + return "Query is nested too deeply"; + } + return "Query contains disallowed operator: " + bad; +}; + +/** + * Recursively search an already-parsed query object for any operator in + * common.UNSAFE_MONGO_OPERATORS. Returns the name of the first offending + * operator found, or null if the query is clean. + * + * Only object KEYS are inspected — operators are always keys, never + * values — so a string value that merely contains "$where" is not a false + * positive. Detection runs on the decoded object, so it cannot be evaded + * by JSON unicode-escaping the operator key (e.g. "$where"). * - * @param {*} query - user-supplied query (any depth) - * @returns {*} the same query with the dangerous operators removed + * @param {*} query - parsed query (any depth) + * @returns {string|null} the offending operator name, or null */ -common.stripUnsafeMongoOperators = function(query) { - var BLOCKED = ["$where", "$expr", "$function", "$accumulator"]; +common.findUnsafeMongoOperator = function(query) { + var BLOCKED = common.UNSAFE_MONGO_OPERATORS; + // Cap recursion so a deeply nested payload cannot overflow the stack + // (a DoS in the very check meant to block DoS). Real queries are shallow; + // anything past this depth is rejected via the TOO_DEEP sentinel. + var MAX_DEPTH = 100; + var found = null; /** - * Recursive walk that strips blocked operators in-place. + * Recursive walk that records the first blocked operator key (or the + * too-deep sentinel). * @param {*} v - current node + * @param {number} depth - current recursion depth * @returns {void} */ - function walk(v) { - if (!v || typeof v !== "object") { + function walk(v, depth) { + if (found || !v || typeof v !== "object") { + return; + } + if (depth > MAX_DEPTH) { + found = common.UNSAFE_QUERY_TOO_DEEP; return; } if (Array.isArray(v)) { - for (var ai = 0; ai < v.length; ai++) { - walk(v[ai]); + for (var ai = 0; ai < v.length && !found; ai++) { + walk(v[ai], depth + 1); } return; } for (var bi = 0; bi < BLOCKED.length; bi++) { if (Object.prototype.hasOwnProperty.call(v, BLOCKED[bi])) { - delete v[BLOCKED[bi]]; + found = BLOCKED[bi]; + return; } } for (var k in v) { + if (found) { + break; + } if (Object.prototype.hasOwnProperty.call(v, k)) { - walk(v[k]); + walk(v[k], depth + 1); } } } - walk(query); - return query; + walk(query, 0); + return found; +}; + +/** + * Parse and validate a user-supplied MongoDB query received at an API + * endpoint. Accepts either a JSON string (as queries arrive on the wire) + * or an already-parsed object. The query is validated as a decoded object + * and is NEVER modified — it is either accepted exactly as submitted or + * rejected as a whole: + * + * - empty / null / undefined -> { query: {} } + * - invalid JSON string -> { error: "Invalid query JSON" } + * - not an object -> { error: "Query must be an object" } + * - contains a disallowed operator -> { error: "Query contains disallowed operator: $where" } + * - otherwise -> { query: } + * + * Callers run result.query exactly as submitted, or return result.error to + * the client. The query is never silently rewritten, so its meaning cannot + * change between what the caller sent and what runs. + * + * Note: this uses JSON.parse. Endpoints that accept extended JSON (EJSON, + * e.g. dbviewer with ObjectIds) should parse with EJSON themselves and + * then validate the parsed object via common.findUnsafeMongoOperator. + * + * @param {string|object} raw - the raw query parameter + * @returns {{query: object}|{error: string}} result + */ +common.parseUserQuery = function(raw) { + var query = raw; + if (typeof raw === "string") { + if (raw.length === 0) { + return { query: {} }; + } + try { + query = JSON.parse(raw); + } + catch (ex) { + return { error: "Invalid query JSON" }; + } + } + if (query === null || typeof query === "undefined") { + return { query: {} }; + } + if (typeof query !== "object" || Array.isArray(query)) { + return { error: "Query must be an object" }; + } + var bad = common.findUnsafeMongoOperator(query); + if (bad) { + return { error: common.unsafeQueryError(bad) }; + } + return { query: query }; +}; + +/** + * Build a short, log-safe label identifying the request's endpoint, for use in + * log messages (e.g. query-rejection logs). Returns the request path plus the + * `method` query param when present, e.g. " [/i/app_users/delete]" or + * " [/o method=logs]", or "" when no context is available. The query string is + * dropped (apart from method) so request api_key etc. are not written to logs. + * + * @param {object} [params] - request params object (may be null/undefined) + * @returns {string} bracketed endpoint label with a leading space, or "" + */ +common.reqInfo = function(params) { + var ctx = ""; + if (params) { + var reqPath = params.href ? ("" + params.href).split("?")[0] : ""; + var reqMethod = (params.qstring && params.qstring.method) ? " method=" + params.qstring.method : ""; + ctx = (reqPath + reqMethod).trim(); + } + return ctx ? " [" + ctx + "]" : ""; }; common.clearClashingQueryOperations = function(query) { diff --git a/api/utils/countly-request/package-lock.json b/api/utils/countly-request/package-lock.json index b3feb3b6f9c..4a6c42b6402 100644 --- a/api/utils/countly-request/package-lock.json +++ b/api/utils/countly-request/package-lock.json @@ -106,9 +106,9 @@ } }, "node_modules/ansi-regex": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.1.0.tgz", - "integrity": "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", "dev": true, "license": "MIT", "engines": { @@ -701,10 +701,20 @@ } }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -807,9 +817,9 @@ } }, "node_modules/mocha": { - "version": "11.7.5", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.5.tgz", - "integrity": "sha512-mTT6RgopEYABzXWFx+GcJ+ZQ32kp4fMf0xvpZIIfSq9Z8lC/++MtcCnQ9t5FP2veYEP95FIYSvW+U9fV4xrlig==", + "version": "11.7.6", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.6.tgz", + "integrity": "sha512-nS9xOGbw2I3cjCpxwZAEJ9xK9lmJ08vEkQvLtz4du9ZrF9UrjRpeJGiIgl2Z+Qs++pmB4ecDe48Fwsh+j+j7xA==", "dev": true, "license": "MIT", "dependencies": { @@ -976,16 +986,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/readdirp": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", @@ -1026,35 +1026,14 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/shebang-command": { @@ -1212,13 +1191,13 @@ } }, "node_modules/strip-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz", + "integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-regex": "^6.0.1" + "ansi-regex": "^6.2.2" }, "engines": { "node": ">=12" @@ -1384,9 +1363,9 @@ } }, "node_modules/wrap-ansi/node_modules/ansi-styles": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", "dev": true, "license": "MIT", "engines": { diff --git a/api/utils/countly-request/package.json b/api/utils/countly-request/package.json index df7ab76f5a2..831d9e51357 100644 --- a/api/utils/countly-request/package.json +++ b/api/utils/countly-request/package.json @@ -20,5 +20,8 @@ "devDependencies": { "mocha": "^11.1.0", "should": "^13.2.3" + }, + "overrides": { + "serialize-javascript": "^7.0.5" } } diff --git a/api/utils/requestProcessor.js b/api/utils/requestProcessor.js index dd9111a8617..3287fe1c7dc 100644 --- a/api/utils/requestProcessor.js +++ b/api/utils/requestProcessor.js @@ -12,7 +12,7 @@ const Promise = require('bluebird'); const url = require('url'); const common = require('./common.js'); const countlyCommon = require('../lib/countly.common.js'); -const { validateAppAdmin, validateUser, validateRead, validateUserForRead, validateUserForWrite, validateGlobalAdmin, dbUserHasAccessToCollection, validateUpdate, validateDelete, validateCreate, getBaseAppFilter } = require('./rights.js'); +const { validateAppAdmin, validateUser, validateRead, validateUserForRead, validateUserForWrite, validateGlobalAdmin, dbUserHasAccessToCollection, validateUpdate, validateDelete, validateCreate, getBaseAppFilter, getAdminApps, getUserAppsForFeaturePermission } = require('./rights.js'); const authorize = require('./authorizer.js'); const taskmanager = require('./taskmanager.js'); const plugins = require('../../plugins/pluginManager.js'); @@ -134,6 +134,14 @@ const processRequest = (params) => { } } + //make sure scalar identity parameters are strings, not query operators + var stringParams = ["app_key", "device_id", "old_device_id"]; + for (let s = 0; s < stringParams.length; s++) { + if (typeof params.qstring[stringParams[s]] !== "undefined" && params.qstring[stringParams[s]] !== null) { + params.qstring[stringParams[s]] = params.qstring[stringParams[s]] + ""; + } + } + if (params.qstring.app_id && params.qstring.app_id.length !== 24) { common.returnMessage(params, 400, 'Invalid parameter "app_id"'); return false; @@ -467,15 +475,14 @@ const processRequest = (params) => { common.returnMessage(params, 400, 'Missing parameter "query"'); return false; } - else if (typeof params.qstring.query === "string") { - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - console.log("Could not parse query", params.qstring.query); - common.returnMessage(params, 400, 'Could not parse parameter "query": ' + params.qstring.query); + else { + let parsedQuery = common.parseUserQuery(params.qstring.query); + if (parsedQuery.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedQuery.error); + common.returnMessage(params, 400, parsedQuery.error); return false; } + params.qstring.query = parsedQuery.query; } validateUserForWrite(params, function() { countlyApi.mgmt.appUsers.count(params.qstring.app_id, params.qstring.query, function(err, count) { @@ -508,15 +515,14 @@ const processRequest = (params) => { common.returnMessage(params, 400, 'Missing parameter "query"'); return false; } - else if (typeof params.qstring.query === "string") { - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - console.log("Could not parse query", params.qstring.query); - common.returnMessage(params, 400, 'Could not parse parameter "query": ' + params.qstring.query); + else { + let parsedQuery = common.parseUserQuery(params.qstring.query); + if (parsedQuery.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedQuery.error); + common.returnMessage(params, 400, parsedQuery.error); return false; } + params.qstring.query = parsedQuery.query; } if (!Object.keys(params.qstring.query).length) { common.returnMessage(params, 400, 'Parameter "query" cannot be empty, it would delete all users. Use clear app instead'); @@ -532,14 +538,27 @@ const processRequest = (params) => { common.returnMessage(params, 400, 'This query would delete more than one user'); return false; } - countlyApi.mgmt.appUsers.delete(params.qstring.app_id, params.qstring.query, params, function(err2) { - if (err2) { - common.returnMessage(params, 400, err2); - } - else { - common.returnMessage(params, 200, 'User deleted'); - } - }); + // Bulk (force) safety net: a force delete whose query matches + // (almost) all users in the app is treated as suspicious and + // requires an explicit confirm_delete_all=true. This catches a + // query that was meant to match a subset but actually matches + // everyone, without blocking legitimate subset deletions. + if (params.qstring.force && params.qstring.confirm_delete_all !== true && params.qstring.confirm_delete_all !== "true") { + common.db.collection("app_users" + params.qstring.app_id).estimatedDocumentCount(function(errTotal, total) { + if (!errTotal && total >= 100 && count >= total * 0.9) { + common.returnMessage(params, 400, 'This query matches ' + count + ' of ~' + total + ' app users (nearly all). If this is intended, retry with confirm_delete_all=true, or use clear app data instead.'); + return; + } + countlyApi.mgmt.appUsers.delete(params.qstring.app_id, params.qstring.query, params, function(err2) { + common.returnMessage(params, err2 ? 400 : 200, err2 || 'User deleted'); + }); + }); + } + else { + countlyApi.mgmt.appUsers.delete(params.qstring.app_id, params.qstring.query, params, function(err2) { + common.returnMessage(params, err2 ? 400 : 200, err2 || 'User deleted'); + }); + } }); }); break; @@ -617,15 +636,14 @@ const processRequest = (params) => { common.returnMessage(params, 400, 'Missing parameter "query"'); return false; } - else if (typeof params.qstring.query === "string") { - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - console.log("Could not parse query", params.qstring.query); - common.returnMessage(params, 400, 'Could not parse parameter "query": ' + params.qstring.query); + else { + let parsedQuery = common.parseUserQuery(params.qstring.query); + if (parsedQuery.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedQuery.error); + common.returnMessage(params, 400, parsedQuery.error); return false; } + params.qstring.query = parsedQuery.query; } var my_name = ""; @@ -1829,17 +1847,13 @@ const processRequest = (params) => { switch (paths[3]) { case 'all': validateRead(params, 'core', () => { - if (!params.qstring.query) { - params.qstring.query = {}; - } - if (typeof params.qstring.query === "string") { - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - params.qstring.query = {}; - } + var parsedQuery = common.parseUserQuery(params.qstring.query); + if (parsedQuery.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedQuery.error); + common.returnMessage(params, 400, parsedQuery.error); + return; } + params.qstring.query = parsedQuery.query; if (params.qstring.query.$or) { params.qstring.query.$and = [ {"$or": Object.assign([], params.qstring.query.$or) }, @@ -1853,8 +1867,25 @@ const processRequest = (params) => { params.qstring.query.subtask = {$exists: false}; params.qstring.query.app_id = params.qstring.app_id; if (params.qstring.app_ids && params.qstring.app_ids !== "") { - var ll = params.qstring.app_ids.split(","); + var ll = params.qstring.app_ids.split(",").map(function(id) { + return id.trim(); + }).filter(Boolean); if (ll.length > 1) { + // validateRead only checked the single app_id; every app + // in the multi-app list must also be one the member can read + if (!params.member.global_admin) { + var allowedTaskApps = (getAdminApps(params.member) || []) + .concat(getUserAppsForFeaturePermission(params.member, 'core', 'r') || []); + if (typeof params.member.permission === "undefined" && Array.isArray(params.member.user_of)) { + allowedTaskApps = allowedTaskApps.concat(params.member.user_of); + } + for (var taskAppIdx = 0; taskAppIdx < ll.length; taskAppIdx++) { + if (allowedTaskApps.indexOf(ll[taskAppIdx]) === -1) { + common.returnMessage(params, 401, 'User does not have access to one or more of the requested apps'); + return; + } + } + } params.qstring.query.app_id = {$in: ll}; } } @@ -1872,17 +1903,13 @@ const processRequest = (params) => { break; case 'count': validateRead(params, 'core', () => { - if (!params.qstring.query) { - params.qstring.query = {}; - } - if (typeof params.qstring.query === "string") { - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - params.qstring.query = {}; - } + var parsedQuery = common.parseUserQuery(params.qstring.query); + if (parsedQuery.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedQuery.error); + common.returnMessage(params, 400, parsedQuery.error); + return; } + params.qstring.query = parsedQuery.query; if (params.qstring.query.$or) { params.qstring.query.$and = [ {"$or": Object.assign([], params.qstring.query.$or) }, @@ -1907,17 +1934,13 @@ const processRequest = (params) => { break; case 'list': validateRead(params, 'core', () => { - if (!params.qstring.query) { - params.qstring.query = {}; - } - if (typeof params.qstring.query === "string") { - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - params.qstring.query = {}; - } + var parsedQuery = common.parseUserQuery(params.qstring.query); + if (parsedQuery.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedQuery.error); + common.returnMessage(params, 400, parsedQuery.error); + return; } + params.qstring.query = parsedQuery.query; params.qstring.query.$and = []; if (params.qstring.query.creator && params.qstring.query.creator === params.member._id) { params.qstring.query.$and.push({"creator": params.member._id + ""}); @@ -1972,17 +1995,27 @@ const processRequest = (params) => { common.returnMessage(params, 400, 'Missing parameter "task_id"'); return false; } - taskmanager.getResult({ - db: common.db, - id: params.qstring.task_id, - subtask_key: params.qstring.subtask_key - }, (err, res) => { - if (res) { - common.returnOutput(params, res); - } - else { + //long_tasks is a global collection keyed by task id; gate + //access so a caller can only read a task they own / are + //authorized for (or one explicitly marked global), not + //an arbitrary private task id from another app + taskmanager.loadIfReadable(common.db, params.qstring.task_id, params.member, (authErr) => { + if (authErr) { common.returnMessage(params, 400, 'Task does not exist'); + return; } + taskmanager.getResult({ + db: common.db, + id: params.qstring.task_id, + subtask_key: params.qstring.subtask_key + }, (err, res) => { + if (res) { + common.returnOutput(params, res); + } + else { + common.returnMessage(params, 400, 'Task does not exist'); + } + }); }); }); break; @@ -2006,7 +2039,8 @@ const processRequest = (params) => { taskmanager.checkResult({ db: common.db, - id: tasks + id: tasks, + member: params.member }, (err, res) => { if (isMulti && res) { common.returnMessage(params, 200, res); @@ -2075,6 +2109,15 @@ const processRequest = (params) => { common.returnMessage(params, 400, 'Missing parameter "collection"'); return false; } + // query params can be parsed into arrays/objects; force a + // plain string before any substring check or access lookup + params.qstring.collection = params.qstring.collection + ""; + // keep the db export surface aligned with DB Viewer: internal + // index metadata and the dashboard session store are not exportable + if (params.qstring.collection.indexOf("system.indexes") !== -1 || params.qstring.collection.indexOf("sessions_") !== -1) { + common.returnMessage(params, 401, 'User does not have access right for this collection'); + return false; + } if (typeof params.qstring.filter === "string") { try { params.qstring.query = JSON.parse(params.qstring.filter, common.reviver); @@ -2812,6 +2855,14 @@ const processRequest = (params) => { case 'geodata': { validateRead(params, 'core', function() { if (params.qstring.loadFor === "cities") { + if (typeof params.qstring.query !== "undefined") { + var pq = common.parseUserQuery(params.qstring.query); + if (pq.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + pq.error); + common.returnMessage(params, 400, pq.error); + return; + } + } countlyApi.data.geoData.loadCityCoordiantes({"query": params.qstring.query}, function(err, data) { common.returnOutput(params, data); }); @@ -3258,7 +3309,10 @@ const processBulkRequest = (i, requests, params) => { } if (!requests[i] || (!requests[i].app_key && !appKey)) { - return processBulkRequest(i + 1, requests, params); + //defer to the next tick so a long run of skipped entries does not + //grow the call stack synchronously (the valid path below is already + //async); otherwise a large array of empty entries overflows the stack + return setImmediate(processBulkRequest, i + 1, requests, params); } if (params.qstring.safe_api_response) { requests[i].safe_api_response = true; @@ -3285,7 +3339,7 @@ const processBulkRequest = (i, requests, params) => { tmpParams.qstring.app_key = (requests[i].app_key || appKey) + ""; if (!tmpParams.qstring.device_id) { - return processBulkRequest(i + 1, requests, params); + return setImmediate(processBulkRequest, i + 1, requests, params); } else { //make sure device_id is string @@ -3341,7 +3395,10 @@ const checksumSaltVerification = (params) => { payloads[i] = common.crypto.createHash('sha1').update(payloads[i] + params.app.checksum_salt).digest('hex').toUpperCase(); } if (payloads.indexOf((params.qstring.checksum + "").toUpperCase()) === -1) { - common.returnMessage(params, 200, 'Request does not match checksum'); + //return the same response as an unknown app so a valid app key + //with a wrong/absent checksum cannot be distinguished from an + //invalid app key (avoids an app key validity oracle) + common.returnMessage(params, 400, 'App does not exist'); console.log("Checksum did not match", params.href, params.req.body, payloads); params.cancelRequest = 'Request does not match checksum sha1'; plugins.dispatch("/sdk/cancel", {params: params}); @@ -3354,7 +3411,10 @@ const checksumSaltVerification = (params) => { payloads[i] = common.crypto.createHash('sha256').update(payloads[i] + params.app.checksum_salt).digest('hex').toUpperCase(); } if (payloads.indexOf((params.qstring.checksum256 + "").toUpperCase()) === -1) { - common.returnMessage(params, 200, 'Request does not match checksum'); + //return the same response as an unknown app so a valid app key + //with a wrong/absent checksum cannot be distinguished from an + //invalid app key (avoids an app key validity oracle) + common.returnMessage(params, 400, 'App does not exist'); console.log("Checksum did not match", params.href, params.req.body, payloads); params.cancelRequest = 'Request does not match checksum sha256'; plugins.dispatch("/sdk/cancel", {params: params}); @@ -3362,7 +3422,8 @@ const checksumSaltVerification = (params) => { } } else { - common.returnMessage(params, 200, 'Request does not have checksum'); + //same uniform response as above (no app key validity oracle) + common.returnMessage(params, 400, 'App does not exist'); console.log("Request does not have checksum", params.href, params.req.body); params.cancelRequest = "Request does not have checksum"; plugins.dispatch("/sdk/cancel", {params: params}); @@ -3482,7 +3543,7 @@ const validateAppForWriteAPI = (params, done, try_times) => { return done ? done() : false; } - common.readBatcher.getOne("apps", {'key': params.qstring.app_key + ""}, (err, app) => { + common.resolveAppByKey(params.qstring.app_key, (err, app) => { if (!app) { common.returnMessage(params, 400, 'App does not exist'); params.cancelRequest = "App not found or no Database connection"; @@ -3490,14 +3551,17 @@ const validateAppForWriteAPI = (params, done, try_times) => { } if (app.paused) { - common.returnMessage(params, 400, 'App is currently not accepting data'); + //return the same response as an unknown app so a valid app key for + //a paused app cannot be distinguished from an invalid one + common.returnMessage(params, 400, 'App does not exist'); params.cancelRequest = "App is currently not accepting data"; plugins.dispatch("/sdk/cancel", {params: params}); return done ? done() : false; } if ((params.populator || params.qstring.populator) && app.locked) { - common.returnMessage(params, 403, "App is locked"); + //same uniform response (no app key existence oracle) + common.returnMessage(params, 400, 'App does not exist'); params.cancelRequest = "App is locked"; plugins.dispatch("/sdk/cancel", {params: params}); return false; @@ -3510,6 +3574,15 @@ const validateAppForWriteAPI = (params, done, try_times) => { params.app = app; params.time = common.initTimeObj(params.appTimezone, params.qstring.timestamp); + //derive the app user id from the app's immutable identity key (falls + //back to the current key for apps without id_key), so app key rotation + //does not re-key existing users + if (params.qstring.device_id) { + params.app_user_id = common.getAppUserId(app, params.qstring.device_id); + } + + //track when this specific key last received data (for safe key retirement) + common.recordAppKeyUsage(app, params.qstring.app_key); var time = Date.now().valueOf(); time = Math.round((time || 0) / 1000); @@ -3519,7 +3592,10 @@ const validateAppForWriteAPI = (params, done, try_times) => { if (err1) { console.log("Failed to update apps collection " + err1); } - common.readBatcher.invalidate("apps", {"key": params.app.key}, {}, false); //because we load app by key on incoming requests. so invalidate also by key + //invalidate both cache shapes the request may have loaded the + //app with (accepted-keys lookup, then current-key fallback), so + //the cache entry for old-key requests is also cleared + common.invalidateAppByKey(params.qstring.app_key); }); } @@ -3627,7 +3703,7 @@ const validateAppForFetchAPI = (params, done, try_times) => { if (ignorePossibleDevices(params)) { return done ? done() : false; } - common.readBatcher.getOne("apps", {'key': params.qstring.app_key}, (err, app) => { + common.resolveAppByKey(params.qstring.app_key, (err, app) => { if (!app) { common.returnMessage(params, 400, 'App does not exist'); params.cancelRequest = "App not found or no Database connection"; @@ -3641,6 +3717,16 @@ const validateAppForFetchAPI = (params, done, try_times) => { params.app = app; params.time = common.initTimeObj(params.appTimezone, params.qstring.timestamp); + //derive the app user id from the app's immutable identity key (falls + //back to the current key for apps without id_key), so app key rotation + //does not re-key existing users + if (params.qstring.device_id) { + params.app_user_id = common.getAppUserId(app, params.qstring.device_id); + } + + //track when this specific key last received data (for safe key retirement) + common.recordAppKeyUsage(app, params.qstring.app_key); + if (!checksumSaltVerification(params)) { return done ? done() : false; } @@ -3786,9 +3872,7 @@ function processUser(params, initiator, done, try_times) { } //check if device id was changed else if (params && params.qstring && params.qstring.old_device_id && params.qstring.old_device_id !== params.qstring.device_id) { - const old_id = common.crypto.createHash('sha1') - .update(params.qstring.app_key + params.qstring.old_device_id + "") - .digest('hex'); + const old_id = common.getAppUserId(params.app, params.qstring.old_device_id); countlyApi.mgmt.appUsers.merge(params.app_id, params.app_user, params.app_user_id, old_id, params.qstring.device_id, params.qstring.old_device_id, function(err) { if (err) { diff --git a/api/utils/rights.js b/api/utils/rights.js index 70a68d19b2a..d1fe6e02896 100644 --- a/api/utils/rights.js +++ b/api/utils/rights.js @@ -1093,19 +1093,34 @@ function validateWrite(params, feature, accessType, callback, callbackParam) { exports.getBaseAppFilter = function(member, dbName, collectionName) { var base_filter = {}; var apps = exports.getUserApps(member); + // getUserApps() returns [] both for a global admin (meaning "all apps") and + // for a non-global member that has no app access at all. Those two cases + // must NOT be treated the same: a global admin should see everything, but a + // non-global member with an empty app list must be scoped to nothing. + // Without this guard the empty list produced an empty {} filter, which the + // callers treat as "no scope" and therefore returned every app's rows. + var hasApps = Array.isArray(apps) && apps.length > 0; if (dbName === "countly_drill" && collectionName === "drill_events") { - if (Array.isArray(apps) && apps.length > 0) { + if (hasApps) { base_filter.a = {"$in": apps}; } + else if (!member.global_admin) { + // non-global member without app access: match nothing + base_filter.a = {"$in": []}; + } } else if (dbName === "countly" && collectionName === "events_data") { - var in_array = []; - if (Array.isArray(apps) && apps.length > 0) { + if (hasApps) { + var in_array = []; for (var i = 0; i < apps.length; i++) { in_array.push(new RegExp("^" + apps[i] + "_.*")); } base_filter = {"_id": {"$in": in_array}}; } + else if (!member.global_admin) { + // non-global member without app access: match nothing + base_filter = {"_id": {"$in": []}}; + } } return base_filter; }; diff --git a/api/utils/ssrf-protection.js b/api/utils/ssrf-protection.js index 079873b187f..984c4554cd4 100644 --- a/api/utils/ssrf-protection.js +++ b/api/utils/ssrf-protection.js @@ -201,10 +201,68 @@ async function isUrlSafe(urlString) { return { safe: true, error: null }; } +/** + * Build a blocked-target error for the connect-time lookup guard. + * @param {string} hostname - hostname being resolved + * @param {string} address - resolved IP that was blocked + * @returns {Error} error with an identifiable code + */ +function blockedLookupError(hostname, address) { + const err = new Error(`Blocked SSRF target: "${hostname}" resolved to private/reserved IP "${address}"`); + err.code = 'ESSRFBLOCKED'; + return err; +} + +/** + * A dns.lookup-compatible function that resolves a hostname and then rejects + * the lookup if the resolved address is private/reserved/internal. Passing this + * as the `lookup` option of an HTTP request (got, node http/https) validates the + * IP AT CONNECT TIME, which closes the DNS-rebinding (TOCTOU) gap left by a + * parse-time-only check: even if a name resolves to a safe IP during isUrlSafe + * and to an internal IP a moment later, the socket only ever connects to an + * address that passed isBlockedIP here. + * + * @param {string} hostname - hostname to resolve + * @param {object|function} options - dns.lookup options, or the callback + * @param {function} [callback] - callback(err, address, family); when + * options.all is true it is called as callback(err, addresses) where + * addresses is an array of {address, family} objects + * @returns {void} + */ +function safeLookup(hostname, options, callback) { + if (typeof options === 'function') { + callback = options; + options = {}; + } + options = options || {}; + dns.lookup(hostname, options, function(err, address, family) { + if (err) { + return callback(err); + } + // options.all === true -> address is an array of {address, family} + if (options.all) { + const list = Array.isArray(address) ? address : [address]; + for (let i = 0; i < list.length; i++) { + if (isBlockedIP(list[i].address)) { + return callback(blockedLookupError(hostname, list[i].address)); + } + } + return callback(null, list); + } + if (isBlockedIP(address)) { + return callback(blockedLookupError(hostname, address)); + } + callback(null, address, family); + }); +} + /** * Build got-compatible request options with SSRF protection baked in. * - * Disables redirects to prevent redirect-based SSRF bypasses. + * Disables redirects to prevent redirect-based SSRF bypasses, and pins DNS + * resolution through safeLookup so the connected IP is validated at connect + * time (DNS-rebinding protection). Callers that issue requests via raw node + * http/https can pass `lookup: safeLookup` directly. * * @param {object} requestOptions - base request options (uri, timeout, headers, etc.) * @returns {object} the same options object with SSRF settings injected @@ -215,10 +273,14 @@ function getSsrfSafeOptions(requestOptions) { // Disable redirects entirely — prevents redirect-based SSRF bypasses options.followRedirect = false; + // Validate the resolved IP at connect time (DNS-rebinding protection) + options.lookup = safeLookup; + return options; } module.exports = { isUrlSafe, getSsrfSafeOptions, + safeLookup, }; diff --git a/api/utils/taskmanager.js b/api/utils/taskmanager.js index bb8f22e26a1..d4d5a39e06e 100644 --- a/api/utils/taskmanager.js +++ b/api/utils/taskmanager.js @@ -84,6 +84,51 @@ taskmanager.loadIfAuthorized = function(db, id, member, cb) { }); }; +/** + * Whether a member may READ a task's result/status. Read access is broader + * than write: in addition to creator / global-admin / app-admin + * (isAuthorizedFor), a task explicitly marked global (global !== false) is + * readable by any authenticated user - matching the /o/tasks/all listing + * filter ({global: {$ne: false}}). Write routes must keep using + * isAuthorizedFor, not this. + * @param {object} member - request member + * @param {object} task - long_tasks document + * @returns {boolean} true if the member may read the task + */ +taskmanager.isReadableBy = function(member, task) { + if (!task) { + return false; + } + if (task.global !== false) { + return true; + } + return taskmanager.isAuthorizedFor(member, task); +}; + +/** + * Load a task by id, then call cb(err, task) only if `member` may READ it + * (see isReadableBy). cb('forbidden') if not readable. + * @param {object} db - database connection + * @param {string} id - task id + * @param {object} member - params.member + * @param {function} cb - cb(err, task) + */ +taskmanager.loadIfReadable = function(db, id, member, cb) { + db = db || common.db; + db.collection("long_tasks").findOne({_id: id}, function(err, task) { + if (err) { + return cb(err); + } + if (!task) { + return cb('not_found'); + } + if (!taskmanager.isReadableBy(member, task)) { + return cb('forbidden'); + } + cb(null, task); + }); +}; + /** * Monitors DB query or some other potentially long task and switches to long task manager if it exceeds threshold * @param {object} options - options for the task @@ -637,6 +682,10 @@ taskmanager.editTask = function(options, callback) { */ taskmanager.checkResult = function(options, callback) { options.db = options.db || common.db; + //when a member is supplied, only report tasks they are authorized for + //(own / global admin / app admin); others are reported as "deleted" so a + //caller cannot probe another app's task status by id + var enforce = !!options.member; if (Array.isArray(options.id)) { options.db.collection("long_tasks").find({_id: {$in: options.id}}, { _id: 1, @@ -644,7 +693,9 @@ taskmanager.checkResult = function(options, callback) { report_name: 1, type: 1, manually_create: 1, - view: 1 + view: 1, + creator: 1, + app_id: 1 }).toArray(function(err, res) { if (err) { callback(err); @@ -655,6 +706,11 @@ taskmanager.checkResult = function(options, callback) { statuses[id] = {_id: id, status: "deleted"}; // if it is present in res, will be overwritten. }); res.forEach(function(item) { + if (enforce && !taskmanager.isReadableBy(options.member, item)) { + return; // leave as "deleted" for unauthorized tasks + } + delete item.creator; + delete item.app_id; statuses[item._id] = item; }); callback(null, Object.keys(statuses).map(function(_id) { @@ -669,8 +725,18 @@ taskmanager.checkResult = function(options, callback) { else { options.db.collection("long_tasks").findOne({_id: options.id}, { _id: 0, - status: 1 - }, callback); + status: 1, + creator: 1, + app_id: 1 + }, function(err, res) { + if (err || !res) { + return callback(err, res); + } + if (enforce && !taskmanager.isReadableBy(options.member, res)) { + return callback(null, null); + } + callback(null, {status: res.status}); + }); } }; diff --git a/bin/docker/k8s/countly-api.yaml b/bin/docker/k8s/countly-api.yaml index 810370a205c..774e8f8ad09 100644 --- a/bin/docker/k8s/countly-api.yaml +++ b/bin/docker/k8s/countly-api.yaml @@ -25,6 +25,9 @@ spec: labels: app: countly-api spec: + securityContext: + seccompProfile: + type: RuntimeDefault affinity: podAntiAffinity: requiredDuringSchedulingIgnoredDuringExecution: diff --git a/bin/docker/k8s/countly-frontend.yaml b/bin/docker/k8s/countly-frontend.yaml index a1b93cab6e7..977e93bce1e 100644 --- a/bin/docker/k8s/countly-frontend.yaml +++ b/bin/docker/k8s/countly-frontend.yaml @@ -25,6 +25,9 @@ spec: labels: app: countly-frontend spec: + securityContext: + seccompProfile: + type: RuntimeDefault affinity: podAffinity: requiredDuringSchedulingIgnoredDuringExecution: diff --git a/bin/docker/k8s/ingestion/countly-ingestion.yaml b/bin/docker/k8s/ingestion/countly-ingestion.yaml index fa889292d6e..ff48b9fe64c 100644 --- a/bin/docker/k8s/ingestion/countly-ingestion.yaml +++ b/bin/docker/k8s/ingestion/countly-ingestion.yaml @@ -25,6 +25,9 @@ spec: labels: app: countly-ingestion spec: + securityContext: + seccompProfile: + type: RuntimeDefault containers: - name: countly-ingestion image: countly/api:23.03 diff --git a/bin/docker/k8s/mongo/mongo-single.yaml b/bin/docker/k8s/mongo/mongo-single.yaml index a3c395f45a6..e9ebe248cae 100644 --- a/bin/docker/k8s/mongo/mongo-single.yaml +++ b/bin/docker/k8s/mongo/mongo-single.yaml @@ -37,6 +37,9 @@ spec: labels: app: mongo spec: + securityContext: + seccompProfile: + type: RuntimeDefault terminationGracePeriodSeconds: 10 containers: - name: mongo diff --git a/bin/scripts/fix-data/backfill_eventtimes_cd.js b/bin/scripts/fix-data/backfill_eventtimes_cd.js new file mode 100644 index 00000000000..7769e6f5348 --- /dev/null +++ b/bin/scripts/fix-data/backfill_eventtimes_cd.js @@ -0,0 +1,50 @@ +// backfill_eventtimes_cd.js +// Backfills the "cd" field on all eventTimes* collections so the TTL index can expire old documents. +// Usage: mongosh "mongodb://localhost" backfill_eventtimes_cd.js +// (any connection string works — the script selects the countly database itself) + +/* global db, print */ + +var countlyDb = db.getSiblingDB("countly"); +var totals = { matched: 0, modified: 0 }; + +countlyDb.getCollectionNames() + .filter(function(name) { + return name.indexOf("eventTimes") === 0; + }) + .forEach(function(name) { + print("processing " + name); + var result = countlyDb.getCollection(name).updateMany( + { cd: { $exists: false } }, + [{ + $set: { + cd: { + $switch: { + branches: [ + // same derivation the server code uses: h = "YYYY:M:D..." + { + case: { $eq: [{ $type: "$h" }, "string"] }, + then: { + $dateFromParts: { + year: { $toInt: { $arrayElemAt: [{ $split: ["$h", ":"] }, 0] } }, + month: { $toInt: { $arrayElemAt: [{ $split: ["$h", ":"] }, 1] } }, + day: { $toInt: { $arrayElemAt: [{ $split: ["$h", ":"] }, 2] } } + } + } + }, + // fallback: derive from the millisecond timestamp + { case: { $isNumber: "$ts" }, then: { $toDate: "$ts" } } + ], + // neither field present (shouldn't happen): stamp with now so TTL still picks it up eventually + default: "$$NOW" + } + } + } + }] + ); + print(" matched: " + result.matchedCount + ", modified: " + result.modifiedCount); + totals.matched += result.matchedCount; + totals.modified += result.modifiedCount; + }); + +print("done. total matched: " + totals.matched + ", total modified: " + totals.modified); diff --git a/bin/scripts/modify-data/delete/delete_user_properties.js b/bin/scripts/modify-data/delete/delete_user_properties.js index ac6ffa96318..d35e6c74fe5 100644 --- a/bin/scripts/modify-data/delete/delete_user_properties.js +++ b/bin/scripts/modify-data/delete/delete_user_properties.js @@ -6,9 +6,9 @@ */ //app id -var APP_ID = "5ab0c3ef92938d0e61cf77f4"; -//path to property -var PROPERTY = "custom.k2"; +var APP_ID = "69f208be3956eabf8824be88"; +//paths to properties +var PROPERTIES = ["custom.k2", "email"]; //delete historic properties too var HISTORIC = true; @@ -18,13 +18,25 @@ var asyncjs = require("async"); var crypto = require("crypto"); var internal_events = ["[CLY]_session", "[CLY]_crash", "[CLY]_view", "[CLY]_action", "[CLY]_push_action", "[CLY]_push_sent", "[CLY]_star_rating", "[CLY]_nps", "[CLY]_survey", "[CLY]_apm_network", "[CLY]_apm_device", "[CLY]_consent"]; -var unset = {}; -unset[PROPERTY] = ""; + +function buildUnset(properties, drillPrefix) { + var unset = {}; + for (var i = 0; i < properties.length; i++) { + var prop = properties[i]; + if (drillPrefix && !prop.startsWith("custom") && !prop.startsWith("cmp")) { + unset["up." + prop] = ""; + } + else { + unset[prop] = ""; + } + } + return unset; +} var Promise = require("bluebird"); -Promise.all([plugins.dbConnection("countly"), plugins.dbConnection("countly_drill")]).spread(function(db, dbDrill) { - console.log("Deleting property from app users"); - db.collection('app_users' + APP_ID).updateMany({}, {$unset: unset}, function(err,) { +Promise.all([plugins.dbConnection("countly"), plugins.dbConnection("countly_drill")]).then(async function([db, dbDrill]) { + console.log("Deleting properties from app users:", PROPERTIES); + db.collection('app_users' + APP_ID).updateMany({}, {$unset: buildUnset(PROPERTIES, false)}, function(err,) { if (err) { console.log("Error", err); } @@ -42,16 +54,7 @@ Promise.all([plugins.dbConnection("countly"), plugins.dbConnection("countly_dril done(); } }, function() { - //delete property from merged drill events collection - var unset = {}; - if (PROPERTY.startsWith("custom") || PROPERTY.startsWith("cmp")) { - unset[PROPERTY] = ""; - } - else { - unset["up." + PROPERTY] = ""; - } - - dbDrill.collection("drill_events").updateMany({"a": (APP_ID + "")}, {$unset: unset}, function(err) { + dbDrill.collection("drill_events").updateMany({"a": (APP_ID + "")}, {$unset: buildUnset(PROPERTIES, true)}, function(err) { if (err) { console.log("Error", err); } @@ -70,22 +73,14 @@ Promise.all([plugins.dbConnection("countly"), plugins.dbConnection("countly_dril } }); - //name, email, picture function deleteDrillUserProperties(event, done) { - console.log("Deleting historic property from", event); - var unset = {}; - if (PROPERTY.startsWith("custom") || PROPERTY.startsWith("cmp")) { - unset[PROPERTY] = ""; - } - else { - unset["up." + PROPERTY] = ""; - } + console.log("Deleting historic properties from", event); var collection = "drill_events" + crypto.createHash('sha1').update(event + APP_ID).digest('hex'); - dbDrill.collection(collection).updateMany({}, {$unset: unset}, function(err) { + dbDrill.collection(collection).updateMany({}, {$unset: buildUnset(PROPERTIES, true)}, function(err) { if (err) { console.log("Error", err); } done(); }); } -}); \ No newline at end of file +}); diff --git a/docs/APP_KEY_ROTATION.md b/docs/APP_KEY_ROTATION.md new file mode 100644 index 00000000000..bde85eb86cf --- /dev/null +++ b/docs/APP_KEY_ROTATION.md @@ -0,0 +1,100 @@ +# Rotating an application key + +This guide explains how to change ("rotate") an application's key in Countly, +what happens to your existing data and users, and how to retire an old key +safely once it is no longer in use. + +## What is the app key + +Every Countly application has an **app key**. Your SDKs send this key with every +request so Countly knows which application the incoming data belongs to. Because +the key is shipped inside your apps (mobile builds, web pages, server +integrations), you may occasionally need to change it, for example if it was +exposed or as part of a routine security practice. + +## What happens when you rotate a key + +When you set a new app key, Countly does **not** discard the old one. Instead: + +- The new key becomes the **current key**. New SDK setups should use it. +- The old key is **kept as an accepted key** and continues to work. Data sent + with it is still accepted and attributed to the same application. +- Your **users and historical data are unaffected**. Visitors are recognised as + the same users regardless of which key their app uses, so there are no + duplicate users and no reset of returning/new user counts. + +This means you can roll out a new key gradually. Clients that have already been +updated start using the new key, while older clients that you cannot update +immediately (for example mobile app versions still in use) keep working on the +old key until they are upgraded or naturally retire. + +## How to rotate a key + +1. Open **Management → Applications** and select the application. +2. Edit the application and set a new **App key**, then save. +3. Update your SDK configuration / integrations to use the new key wherever you + can. New installs and deployments should use the new key. + +The new key takes effect immediately, and the previous key remains accepted. + +A key must be unique across all applications, including keys that are still +accepted from previous rotations. If you choose a value already in use, the save +is rejected. + +> **Dashboard support.** The application management screen lists all accepted +> keys under "Accepted app keys", showing the current/primary key and each old +> key with its per-key "last received" time, and lets you retire an old key once +> it is no longer receiving data. The same workflows are also available through +> the apps management API described below. + +## Seeing which keys are still receiving data + +Each application stores its accepted keys in a `keys` array. Every entry records +the key value, when it was added, and the **last time data was received** using +that key (`last_data`). Reading the app object (for example via the apps +management API, `/o/apps`) returns this array, so you can tell whether an old key +is still being used by clients in the field. + +The per-key "last received" time is updated as data arrives, so a key that no +longer shows recent activity is a good candidate for removal. + +## Retiring an old key + +When you are confident an old key is no longer needed (for example its "last +received" time stopped advancing some time ago, after your client population has +upgraded), you can remove it by editing the application through the apps +management API and passing the key(s) to retire in the `remove_keys` field. + +Once removed, that key is no longer accepted: requests sent with it are rejected +as if the application did not exist. The **current key cannot be removed** — an +application always has at least one accepted key. + +There is no fixed deadline to remove an old key. You can keep it as long as you +need and retire it whenever you decide it is safe. + +## Recommended approach + +1. Set the new key and start using it for new deployments. +2. Roll the new key out to your clients/integrations over time. +3. Watch the per-key "last received" time (`last_data`) on the old key. +4. When the old key has been quiet long enough for your situation, retire it + with `remove_keys`. + +## Frequently asked questions + +**Will rotating the key lose any data or reset my users?** +No. Existing data is preserved and users continue to be recognised as the same +users across the change. + +**Do I have to update all my clients at the same time?** +No. The old key keeps working until you remove it, so you can migrate clients at +your own pace. + +**Can two applications share a key?** +No. Each key (current or previously accepted) must be unique across all +applications. + +**What happens to requests sent with a removed key?** +They are rejected. Make sure no clients you care about are still using a key +before you remove it — the per-key "last received" time is there to help you +decide. diff --git a/frontend/express/app.js b/frontend/express/app.js index f782a22df1f..8253bc7c16b 100644 --- a/frontend/express/app.js +++ b/frontend/express/app.js @@ -445,13 +445,13 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ app.set('view engine', 'html'); app.set('view options', {layout: false}); - app.use('/stylesheets/ionicons/fonts/', function(req, res, next) { + app.use(countlyConfig.path + '/stylesheets/ionicons/fonts/', function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); }); - app.use('/fonts/', function(req, res, next) { + app.use(countlyConfig.path + '/fonts/', function(req, res, next) { res.header("Access-Control-Allow-Origin", "*"); res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept"); next(); @@ -927,7 +927,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ } }); app.get(countlyConfig.path + '/dashboard', checkRequestForSession); - app.post('*', checkRequestForSession); + app.post(countlyConfig.path + '/*', checkRequestForSession); app.get(countlyConfig.path + '/logout', function(req, res) { if (req.query.message) { @@ -1763,22 +1763,61 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ }); }); + /** + * Safely delete a formidable upload temp file: only unlink it if it + * resolves inside the configured upload directory. The path originates from + * the request, so this guards it from being used to remove anything outside + * the upload dir. + * @param {string} filePath - path of the uploaded temp file to remove + * @returns {void} + */ + function removeUploadFile(filePath) { + if (!filePath) { + return; + } + //strip any directory component and re-root under the upload dir, so the + //request-derived value cannot reference anything outside it + var safePath = path.join(path.resolve(__dirname + '/uploads'), path.basename(filePath + "")); + fs.unlink(safePath, function() {}); + } + app.post(countlyConfig.path + '/member/icon', async function(req, res, next) { var params = paramsGenerator({req, res}); validateCreate(params, 'global_upload', async function() { if (!req.files.member_image || !req.body.member_image_id) { + //remove the already-uploaded temp file so a missing id cannot + //be used to leak files into the upload directory + removeUploadFile(req.files.member_image && req.files.member_image.path); res.end(); return true; } + //member_image_id is always a member _id; require a valid ObjectId + //so it cannot be used to build an arbitrary file path + if (!/^[a-f0-9]{24}$/i.test(req.body.member_image_id + "")) { + removeUploadFile(req.files.member_image.path); + res.status(400).send(false); + return true; + } + + //a member may only set their own image; managing another member's + //image (the user-management flow) is global-admin only. Without + //this an app-admin (who passes the global_upload check via their + //own app) could overwrite any member's avatar by id. + if (!(params.member && (params.member.global_admin || (req.body.member_image_id + "") === (params.member._id + "")))) { + removeUploadFile(req.files.member_image.path); + res.status(403).send(false); + return true; + } + req.body.member_image_id = common.sanitizeFilename(req.body.member_image_id); var tmp_path = req.files.member_image.path, target_path = __dirname + '/public/memberimages/' + req.body.member_image_id + ".png", type = req.files.member_image.type; if (type !== "image/png" && type !== "image/gif" && type !== "image/jpeg") { - fs.unlink(tmp_path, function() {}); + removeUploadFile(tmp_path); res.send(false); return true; } @@ -1788,14 +1827,14 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ const image = await jimp.Jimp.read(tmp_path); if (!image) { - fs.unlink(tmp_path, function() {}); + removeUploadFile(tmp_path); res.status(400).send(false); return true; } } catch (err) { console.log(err.stack); - fs.unlink(tmp_path, function() {}); + removeUploadFile(tmp_path); res.status(400).send(false); return true; } @@ -1813,7 +1852,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ console.log("Problem uploading member icon", e); res.status(400).send(false); } - fs.unlink(tmp_path, function() {}); + removeUploadFile(tmp_path); }); }); @@ -1972,6 +2011,38 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ }); countlyDb.collection('apps').createIndex({"key": 1}, { unique: true }, function() {}); + //index every accepted app key so incoming requests resolve by current OR + //rotated-but-still-accepted key without a collection scan. Ensured at + //startup (idempotent) so existing apps are covered after an upgrade with no + //migration script; createApp also ensures it for new apps. + countlyDb.collection('apps').createIndex({"keys.key": 1}, { background: true }, function() {}); + //backfill the accepted-keys array for apps that predate key rotation, so the + //SDK app lookup resolves them via the indexed keys.key field on its first + //query instead of falling back to the current-key field. Idempotent (only + //touches apps missing the array, and is guarded again per-app at write time) + //and self-disabling once filled, so it is safe to run on every startup; this + //process runs in low replica count and the apps collection is small, so no + //migration script or cross-process coordination is needed. App resolution + //stays correct before this completes via the current-key fallback in + //common.resolveAppByKey, so it does not need to gate startup. + countlyDb.collection('apps').find({ keys: { $exists: false } }, { projection: { key: 1, created_at: 1, last_data: 1 } }).toArray(function(ferr, legacyApps) { + if (ferr) { + console.log("Failed to read apps for accepted-keys backfill", ferr); + return; + } + var nowSec = Math.floor(Date.now() / 1000); + (legacyApps || []).forEach(function(legacyApp) { + countlyDb.collection('apps').updateOne( + { _id: legacyApp._id, keys: { $exists: false } }, + { $set: { keys: [{ key: legacyApp.key, added_at: legacyApp.created_at || nowSec, last_data: legacyApp.last_data || 0 }] } }, + function(uerr) { + if (uerr) { + console.log("Failed to backfill accepted-keys array for app", legacyApp._id, uerr); + } + } + ); + }); + }); countlyDb.collection('members').createIndex({"api_key": 1}, { unique: true }, function() {}); countlyDb.collection('members').createIndex({ email: 1 }, { unique: true }, function() {}); countlyDb.collection('jobs').createIndex({ finished: 1 }, function() {}); @@ -1988,6 +2059,17 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ key: fs.readFileSync(countlyConfig.web.ssl.key), cert: fs.readFileSync(countlyConfig.web.ssl.cert) }; + // Optional: let operators pin the negotiated TLS protocol range + // (e.g. minVersion "TLSv1.2"). Left unset by default so Node keeps + // its built-in defaults — deployments that still require older + // protocols, or that terminate TLS at nginx/their webserver, are + // unaffected. + if (countlyConfig.web.ssl.minVersion) { + sslOptions.minVersion = countlyConfig.web.ssl.minVersion; + } + if (countlyConfig.web.ssl.maxVersion) { + sslOptions.maxVersion = countlyConfig.web.ssl.maxVersion; + } if (countlyConfig.web.ssl.ca) { sslOptions.ca = fs.readFileSync(countlyConfig.web.ssl.ca); } diff --git a/frontend/express/config.sample.js b/frontend/express/config.sample.js index 9277c61a68f..308c5304c2e 100644 --- a/frontend/express/config.sample.js +++ b/frontend/express/config.sample.js @@ -83,6 +83,8 @@ var countlyConfig = { key: "/path/to/ssl/private.key", cert: "/path/to/ssl/certificate.crt", // ca: "/path/to/ssl/ca_bundle.crt" // Optional: for client certificate verification, uncomment to activate + // minVersion: "TLSv1.2", // Optional: pin the lowest allowed TLS protocol (e.g. "TLSv1.2"). Unset = Node defaults + // maxVersion: "TLSv1.3", // Optional: pin the highest allowed TLS protocol. Unset = Node defaults } }, /** diff --git a/frontend/express/libs/members.js b/frontend/express/libs/members.js index fbf9a377cf7..37cff1f3e4b 100644 --- a/frontend/express/libs/members.js +++ b/frontend/express/libs/members.js @@ -797,6 +797,15 @@ membersUtility.reset = function(req, callback) { callback(false, undefined); return; } + //enforce the same 10 minute expiry the reset page checks; the + //password change endpoint previously did not, so an expired + //(e.g. leaked/old) reset link stayed usable indefinitely + var nowTs = Math.round(new Date().getTime() / 1000); + if (nowTs > (passwordReset.timestamp + 600)) { + membersUtility.db.collection('password_reset').remove({ prid: req.body.prid }, function() { }); + callback(false, undefined); + return; + } membersUtility.db.collection('members').findAndModify({ _id: passwordReset.user_id }, {}, { '$set': { "password": password } }, function(err2, member) { member = member && member.ok ? member.value : null; killOtherSessionsForUser(passwordReset.user_id + "", null, null, membersUtility.db); @@ -1140,6 +1149,10 @@ membersUtility.createMember = async function(data, provider = '', deleteDuplicat user.approver = !!data.approver; user.approver_bypass = !!data.approver_bypass; + // journey approver permission + user.journey_approver = !!data.journey_approver; + user.journey_approver_bypass = !!data.journey_approver_bypass; + try { if (deleteDuplicate && (existingMembers.length >= 2 || (existingMembers.length === 1 && existingMembers[0].provider_id !== user.provider_id))) { await membersUtility.removeMembers(query); diff --git a/frontend/express/public/core/app-management/javascripts/countly.views.js b/frontend/express/public/core/app-management/javascripts/countly.views.js index 98523ef361a..61e9c2b4ef2 100755 --- a/frontend/express/public/core/app-management/javascripts/countly.views.js +++ b/frontend/express/public/core/app-management/javascripts/countly.views.js @@ -31,6 +31,26 @@ }, authToken: function() { return countlyGlobal.auth_token; + }, + acceptedKeys: function() { + if (this.newApp || !this.apps[this.selectedApp]) { + return []; + } + var current = this.apps[this.selectedApp].key; + var keys = this.apps[this.selectedApp].keys; + if (!Array.isArray(keys) || !keys.length) { + //legacy app whose keys array has not been materialized yet: + //surface at least the current key so the section is useful + return current ? [{key: current, last_data: this.apps[this.selectedApp].last_data || 0, isCurrent: true}] : []; + } + return keys.map(function(item) { + return { + key: item.key, + added_at: item.added_at, + last_data: item.last_data, + isCurrent: item.key === current + }; + }); } }, data: function() { @@ -460,7 +480,7 @@ error: function(xhr, status, error) { CountlyHelpers.notify({ title: jQuery.i18n.map["configs.not-saved"], - message: error || jQuery.i18n.map["configs.not-changed"], + message: (xhr && xhr.responseJSON && xhr.responseJSON.result) || error || jQuery.i18n.map["configs.not-changed"], type: "error" }); } @@ -518,14 +538,78 @@ }); }, error: function(xhr, status, error) { + // Revert the rejected/edited values so the form reflects the actual + // saved state instead of leaving the failed value on screen. + self.discardForm(); CountlyHelpers.notify({ title: jQuery.i18n.map["configs.not-saved"], - message: error || jQuery.i18n.map["configs.not-changed"], + message: (xhr && xhr.responseJSON && xhr.responseJSON.result) || error || jQuery.i18n.map["configs.not-changed"], type: "error" }); } }); }, + formatKeyTime: function(ts) { + ts = parseInt(ts, 10); + if (!ts) { + return CV.i18n("management-applications.accepted-keys.never"); + } + if (Math.round(ts).toString().length === 10) { + ts *= 1000; + } + return moment(new Date(ts)).format("ddd, D MMM YYYY, HH:mm"); + }, + removeKey: function(key) { + var self = this; + CountlyHelpers.confirm( + jQuery.i18n.map["management-applications.retire-key-confirm"], + "red", + function(result) { + if (!result) { + return true; + } + $.ajax({ + type: "GET", + url: countlyCommon.API_PARTS.apps.w + '/update', + data: { + args: JSON.stringify({app_id: self.selectedApp, remove_keys: [key]}), + app_id: self.selectedApp + }, + dataType: "json", + success: function(data) { + //optimistically drop the retired key in case the + //response does not echo the keys array back + var existing = (countlyGlobal.apps[self.selectedApp].keys || []).filter(function(k) { + return k.key !== key; + }); + countlyGlobal.apps[self.selectedApp].keys = existing; + if (countlyGlobal.admin_apps[self.selectedApp]) { + countlyGlobal.admin_apps[self.selectedApp].keys = existing; + } + for (var modAttr in data) { + countlyGlobal.apps[self.selectedApp][modAttr] = data[modAttr]; + if (countlyGlobal.admin_apps[self.selectedApp]) { + countlyGlobal.admin_apps[self.selectedApp][modAttr] = data[modAttr]; + } + } + CountlyHelpers.notify({ + title: jQuery.i18n.map["configs.changed"], + message: jQuery.i18n.map["management-applications.retire-key-success"] + }); + }, + error: function(xhr, status, error) { + CountlyHelpers.notify({ + title: jQuery.i18n.map["configs.not-saved"], + message: error || jQuery.i18n.map["configs.not-changed"], + type: "error" + }); + } + }); + }, + [jQuery.i18n.map["common.cancel"], jQuery.i18n.map["management-applications.retire-key-confirm-button"]], + {title: jQuery.i18n.map["management-applications.retire-key-title"]} + ); + }, loadDetails: function() { this.loadingDetails = true; var self = this; diff --git a/frontend/express/public/core/app-management/templates/app-management.html b/frontend/express/public/core/app-management/templates/app-management.html index b761ca28a41..e91581e74d0 100644 --- a/frontend/express/public/core/app-management/templates/app-management.html +++ b/frontend/express/public/core/app-management/templates/app-management.html @@ -223,7 +223,7 @@

{{apps[selectedApp] {{ i18n("common.diff-helper.keep-single") }} - + {{ i18n('dashboards.save-changes') }} @@ -261,6 +261,39 @@

{{apps[selectedApp] + +
+

{{i18n('management-applications.accepted-keys')}}

+

{{i18n('management-applications.accepted-keys.hint')}}

+ + + + + + + + + + + +
+
@@ -338,7 +371,7 @@

{{i18n('management-appli type="text" @click.stop="onChange(key, '')" class="bu-p-0 feedback-settings__button">{{i18n('feedback.delete-logo')}} - +
{{i18n('feedback.choose-file')}} diff --git a/frontend/express/public/core/home/javascripts/countly.models.js b/frontend/express/public/core/home/javascripts/countly.models.js index 594091dd287..3e288eb7d90 100644 --- a/frontend/express/public/core/home/javascripts/countly.models.js +++ b/frontend/express/public/core/home/javascripts/countly.models.js @@ -13,7 +13,7 @@ downloadScreen: function(context) { return CV.$.ajax({ type: "POST", - url: "/o/render?view=/dashboard&route=/" + countlyCommon.ACTIVE_APP_ID + "/", + url: countlyCommon.API_PARTS.data.r + "/render?view=/dashboard&route=/" + countlyCommon.ACTIVE_APP_ID + "/", data: { app_id: countlyCommon.ACTIVE_APP_ID, "id": "main_home_view", diff --git a/frontend/express/public/core/onboarding/javascripts/countly.views.js b/frontend/express/public/core/onboarding/javascripts/countly.views.js index add98bef73c..d891ffaf627 100644 --- a/frontend/express/public/core/onboarding/javascripts/countly.views.js +++ b/frontend/express/public/core/onboarding/javascripts/countly.views.js @@ -289,6 +289,12 @@ }); app.route('/initial-setup', 'initial-setup', function() { + if (!_.isEmpty(countlyGlobal.apps)) { + // Apps already exist; the initial-setup dead-end shouldn't be shown + // (e.g. reached via a stale restored route). Send to the default app. + app.navigate("/" + (countlyGlobal.defaultApp && countlyGlobal.defaultApp._id ? countlyGlobal.defaultApp._id : ""), true); + return; + } this.renderWhenReady(new CV.views.BackboneWrapper({ component: appSetupView, vuex: [{ clyModel: countlyOnboarding }], diff --git a/frontend/express/public/core/user-management/javascripts/countly.views.js b/frontend/express/public/core/user-management/javascripts/countly.views.js index 54fe5209d00..4c5a1a66f38 100644 --- a/frontend/express/public/core/user-management/javascripts/countly.views.js +++ b/frontend/express/public/core/user-management/javascripts/countly.views.js @@ -267,7 +267,7 @@ filteredFeatures: [], dropzoneOptions: { member: null, - url: "/member/icon", + url: (countlyGlobal.path || "") + "/member/icon", autoProcessQueue: false, acceptedFiles: 'image/*', maxFiles: 1, diff --git a/frontend/express/public/core/user-management/templates/drawer.html b/frontend/express/public/core/user-management/templates/drawer.html index e7999b5da31..f9a63622d72 100644 --- a/frontend/express/public/core/user-management/templates/drawer.html +++ b/frontend/express/public/core/user-management/templates/drawer.html @@ -72,8 +72,7 @@
{{ i18n('management-users.profile-picture') }}
- - + {{i18n('common.edit')}} @@ -91,7 +90,7 @@ :useCustomSlot=true :options="dropzoneOptions">
- +

{{ i18n('management-users.drag-and-drop-or') }} {{ i18n('management-users.browser') }} {{ i18n('management-users.files-to-add-picture') }}

diff --git a/frontend/express/public/javascripts/countly/countly.helpers.js b/frontend/express/public/javascripts/countly/countly.helpers.js index 0117d5cf129..ea6d802e575 100644 --- a/frontend/express/public/javascripts/countly/countly.helpers.js +++ b/frontend/express/public/javascripts/countly/countly.helpers.js @@ -48,7 +48,7 @@ CountlyHelpers.logout = function(path) { if (path) { - window.location = "logout"; + window.location = (countlyGlobal.path || "") + "/logout"; } else { window.location.reload();//this will log us out diff --git a/frontend/express/public/javascripts/countly/countly.views.js b/frontend/express/public/javascripts/countly/countly.views.js index dbbfe076ad8..a350d8f7232 100644 --- a/frontend/express/public/javascripts/countly/countly.views.js +++ b/frontend/express/public/javascripts/countly/countly.views.js @@ -1,4 +1,4 @@ -/* global countlyView, countlyCommon, app, CountlyHelpers, countlyGlobal, countlyTaskManager, countlyVersionHistoryManager, DownloadView, Backbone, jQuery, $*/ +/* global countlyView, countlyCommon, app, CountlyHelpers, countlyGlobal, countlyVersionHistoryManager, DownloadView, Backbone, jQuery, $*/ window.DashboardView = countlyView.extend({ renderCommon: function() { @@ -17,44 +17,51 @@ window.DownloadView = countlyView.extend({ renderCommon: function() { var self = this; if (!this.task_id) { - $(this.el).html('

' + jQuery.i18n.map["downloading-view.download-not-available-title"] + '

' + jQuery.i18n.map["downloading-view.download-not-available-text"] + '

'); + self.renderUnavailable(); return; } + // each route sets its own path; default protects direct/legacy entry this.path = this.path || "/app_users/download/"; - var myhtml; - if (this.path) { - myhtml = '

' + jQuery.i18n.map["downloading-view.download-title"] + '

'; - self.link = countlyCommon.API_PARTS.data.r + self.path + self.task_id + "?auth_token=" + countlyGlobal.auth_token + "&app_id=" + countlyCommon.ACTIVE_APP_ID; - window.location = self.link; - - if (self.link) { - myhtml += '

' + jQuery.i18n.map["downloading-view.if-not-start"] + '

'; - } - myhtml += "
"; - $(self.el).html(myhtml); - } - else { - this.path = "/app_users/download/"; - countlyTaskManager.fetchResult(this.task_id, function(res) { - myhtml = '

' + jQuery.i18n.map["downloading-view.download-title"] + '

'; - if (res && res.data) { - res.data = res.data.replace(new RegExp(""", 'g'), ""); - self.link = countlyCommon.API_PARTS.data.r + self.path + res.data + "?auth_token=" + countlyGlobal.auth_token + "&app_id=" + countlyCommon.ACTIVE_APP_ID; - window.location = self.link; - - - if (self.link) { - myhtml += '

' + jQuery.i18n.map["downloading-view.if-not-start"] + '

'; - } - myhtml += "
"; - } - else { - myhtml = '

' + jQuery.i18n.map["downloading-view.download-not-available-title"] + '

' + jQuery.i18n.map["downloading-view.download-not-available-text"] + '

'; - } - $(self.el).html(myhtml); - - }); + self.link = self.buildLink(self.task_id); + window.location = self.link; + self.renderDownloadLink(self.link); + }, + // Build the download URL. The id segment can originate from the URL hash + // (route parameter), so every segment is encoded before it is placed in + // the URL to keep it a single, well-formed path/query component. + buildLink: function(id) { + return countlyCommon.API_PARTS.data.r + this.path + encodeURIComponent(id) + + "?auth_token=" + encodeURIComponent(countlyGlobal.auth_token) + + "&app_id=" + encodeURIComponent(countlyCommon.ACTIVE_APP_ID); + }, + // Render via DOM APIs (textContent / href property) rather than building an + // HTML string, so the link value is never parsed as markup. + renderDownloadLink: function(link) { + var container = document.createElement("div"); + container.id = "no-app-type"; + var title = document.createElement("h1"); + title.textContent = jQuery.i18n.map["downloading-view.download-title"]; + container.appendChild(title); + if (link) { + var paragraph = document.createElement("p"); + var anchor = document.createElement("a"); + anchor.href = link; + anchor.textContent = jQuery.i18n.map["downloading-view.if-not-start"]; + paragraph.appendChild(anchor); + container.appendChild(paragraph); } + $(this.el).empty().append(container); + }, + renderUnavailable: function() { + var container = document.createElement("div"); + container.id = "no-app-type"; + var title = document.createElement("h1"); + title.textContent = jQuery.i18n.map["downloading-view.download-not-available-title"]; + var text = document.createElement("p"); + text.textContent = jQuery.i18n.map["downloading-view.download-not-available-text"]; + container.appendChild(title); + container.appendChild(text); + $(this.el).empty().append(container); } }); @@ -82,6 +89,7 @@ app.DownloadView = new DownloadView(); app.route('/exportedData/AppUserExport/:task_id', 'userExportTask', function(task_id) { this.DownloadView.task_id = task_id; + this.DownloadView.path = "/app_users/download/"; this.renderWhenReady(this.DownloadView); }); diff --git a/frontend/express/public/javascripts/countly/vue/components/vis.js b/frontend/express/public/javascripts/countly/vue/components/vis.js index 6d81e26211a..42ffe4274a1 100644 --- a/frontend/express/public/javascripts/countly/vue/components/vis.js +++ b/frontend/express/public/javascripts/countly/vue/components/vis.js @@ -2002,12 +2002,12 @@ '
\ \ \ - \ + \ \ \ - {{i18n("notes.add-note")}}\ - {{i18n("notes.manage-notes")}}\ - {{!areNotesHidden ? i18n("notes.hide-notes") : i18n("notes.show-notes")}}\ + {{i18n("notes.add-note")}}\ + {{i18n("notes.manage-notes")}}\ + {{!areNotesHidden ? i18n("notes.hide-notes") : i18n("notes.show-notes")}}\ \ \ \ diff --git a/frontend/express/public/javascripts/countly/vue/core.js b/frontend/express/public/javascripts/countly/vue/core.js index 1312776e551..697c2af068b 100644 --- a/frontend/express/public/javascripts/countly/vue/core.js +++ b/frontend/express/public/javascripts/countly/vue/core.js @@ -627,6 +627,18 @@ Vue.prototype.$route = new BackboneRouteAdapter(); + // countlyGlobal is a window-level global, but Vue 2's render proxy does not fall + // through to window for identifiers in template expressions, so a bare + // countlyGlobal.* in a template resolves to undefined and throws during render. + // Expose it on the prototype (via a getter so it always reflects the current + // window.countlyGlobal) so templates can use countlyGlobal.* safely. + Object.defineProperty(Vue.prototype, "countlyGlobal", { + configurable: true, + get: function() { + return window.countlyGlobal; + } + }); + var DummyCompAPI = VueCompositionAPI.defineComponent({ name: "DummyCompAPI", template: '
', diff --git a/frontend/express/public/javascripts/dom/pace/pace.js b/frontend/express/public/javascripts/dom/pace/pace.js index c7efc2fc229..2a727ff679f 100644 --- a/frontend/express/public/javascripts/dom/pace/pace.js +++ b/frontend/express/public/javascripts/dom/pace/pace.js @@ -497,7 +497,7 @@ for (_j = 0, _len1 = _ref2.length; _j < _len1; _j++) { pattern = _ref2[_j]; if (typeof pattern === 'string') { - if (url.indexOf(pattern) !== -1) { + if (url && url.indexOf(pattern) !== -1) { return true; } } else { diff --git a/frontend/express/public/localization/dashboard/dashboard.properties b/frontend/express/public/localization/dashboard/dashboard.properties index ab3b60d21f7..83d3e269872 100644 --- a/frontend/express/public/localization/dashboard/dashboard.properties +++ b/frontend/express/public/localization/dashboard/dashboard.properties @@ -815,10 +815,21 @@ management-applications.delete-admin = Only administrators of an application can management-applications.app-locked = Application is locked. management-applications.icon-error = Only JPG, PNG, and GIF image formats are allowed. management-applications.no-app-warning = In order to start collecting data you need to add an application to your account. -management-applications.app-key-change-warning-title = Changing the App key -management-applications.app-key-change-warning = Changing the app key will cause all users from this point on to be recorded as new users even if they used your application before. This action is only recommended if you are migrating an application from another server or changing the app key of a new application. +management-applications.app-key-change-warning-title = Change the App key +management-applications.app-key-change-warning = Changing the app key rotates it: the new key becomes the primary key, while the old key stays accepted so already-deployed SDK clients keep sending data without interruption. Existing users are preserved. You can retire the old key later from this screen, once it is no longer receiving data. management-applications.app-key-change-warning-confirm = Continue, change the app key -management-applications.app-key-change-warning-EE = Changing the app key will cause all users from this point on to be recorded as new users even if they used your application before. This action is only recommended if you are migrating an application from another server or changing the app key of a new application. If your intention was to change the app key to stop collecting data for this application, recommended way of doing so is using Filtering Rules plugin. +management-applications.app-key-change-warning-EE = Changing the app key rotates it: the new key becomes the primary key, while the old key stays accepted so already-deployed SDK clients keep sending data without interruption. Existing users are preserved. You can retire the old key later from this screen, once it is no longer receiving data. If your goal is to stop collecting data for this application, use the Filtering Rules plugin instead. +management-applications.accepted-keys = Accepted app keys +management-applications.accepted-keys.hint = Every key currently accepted for this app. The primary key is marked as current; older keys keep working until you retire them, so rotating the key never breaks already-deployed SDK clients. +management-applications.accepted-keys.key = App key +management-applications.accepted-keys.current = Current +management-applications.accepted-keys.last-data = Last received data +management-applications.accepted-keys.never = No data received yet +management-applications.accepted-keys.retire = Retire +management-applications.retire-key-title = Retire app key +management-applications.retire-key-confirm = Once retired, this app key will no longer be accepted and any SDK clients still using it will be unable to send data. Make sure it is no longer receiving data before continuing. Do you want to retire it? +management-applications.retire-key-confirm-button = Retire key +management-applications.retire-key-success = App key retired. management-applications.first-app-message2 = Great! You can now embed the Countly SDK into your application and start viewing your stats instantly. Don't forget to get your App Key from above. management-applications.types.mobile = Mobile management-applications.checksum-salt = Salt for checksum diff --git a/frontend/express/views/forgot.html b/frontend/express/views/forgot.html index 61847e2e2aa..8b63ff81de5 100644 --- a/frontend/express/views/forgot.html +++ b/frontend/express/views/forgot.html @@ -56,7 +56,7 @@
- +

diff --git a/frontend/express/views/setup.html b/frontend/express/views/setup.html index fac4c09c555..e211ed91e41 100644 --- a/frontend/express/views/setup.html +++ b/frontend/express/views/setup.html @@ -48,7 +48,7 @@
- +

diff --git a/package-lock.json b/package-lock.json index 842fae847c5..b5c112c947f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,7 +14,7 @@ "async": "3.2.6", "basic-auth": "2.0.1", "bluebird": "3.7.2", - "body-parser": "2.2.2", + "body-parser": "2.3.0", "bunyan": "1.8.15", "colors": "1.4.0", "connect-flash": "0.1.1", @@ -27,13 +27,13 @@ "csvtojson": "2.0.14", "ejs": "5.0.2", "errorhandler": "1.5.2", - "express": "4.22.1", + "express": "4.22.2", "express-rate-limit": "8.5.2", "express-session": "1.19.0", "form-data": "^4.0.0", "formidable": "2.1.3", "fs-extra": "11.3.5", - "geoip-lite": "2.0.2", + "geoip-lite": "2.0.3", "get-random-values": "^5.0.0", "grunt": "1.6.2", "grunt-cli": "1.5.0", @@ -58,14 +58,15 @@ "mongodb": "6.20.0", "mongodb-connection-string-url": "^7.0.1", "nginx-conf": "2.1.0", - "nodemailer": "8.0.7", + "nodemailer": "9.0.1", "object-hash": "3.0.0", "properties-parser": "0.6.0", "puppeteer": "^24.6.1", "rate-limiter-flexible": "^11.0.0", "sass": "1.99.0", "semver": "^7.7.1", - "sharp": "^0.34.2", + "sharp": "^0.35.0", + "sqlite3": "^5.1.7", "tslib": "^2.6.3", "uglify-js": "3.19.3", "underscore": "1.13.8", @@ -435,9 +436,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "license": "MIT", "optional": true, "dependencies": { @@ -575,6 +576,13 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, "node_modules/@humanwhocodes/config-array": { "version": "0.13.0", "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", @@ -647,9 +655,9 @@ } }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.5.tgz", - "integrity": "sha512-imtQ3WMJXbMY4fxb/Ndp6HBTNVtWCUI0WdobyheGf5+ad6xX8VIDO8u2xE4qc/fr08CKG/7dDseFtn6M6g/r3w==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.35.2.tgz", + "integrity": "sha512-eEieHsMksAW4IiO5NzauESRl2D2qz3J/kwUxUrSfV06A93eEaRfMpHXyUb1mAqrR7i8U9A0GRqE9pjn6u1Jjpg==", "cpu": [ "arm64" ], @@ -659,19 +667,19 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.4" + "@img/sharp-libvips-darwin-arm64": "1.3.1" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.5.tgz", - "integrity": "sha512-YNEFAF/4KQ/PeW0N+r+aVVsoIY0/qxxikF2SWdp+NRkmMB7y9LBZAVqQ4yhGCm/H3H270OSykqmQMKLBhBJDEw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.35.2.tgz", + "integrity": "sha512-BaktuGPCeHJMARpodR8jK4uKiZrPAy9WrfQW0sdI37clracq8Bp01AYS3SZgi5FS/y5twa9t4+LIuuxQjqRrWw==", "cpu": [ "x64" ], @@ -681,19 +689,38 @@ "darwin" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.4" + "@img/sharp-libvips-darwin-x64": "1.3.1" + } + }, + "node_modules/@img/sharp-freebsd-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-freebsd-wasm32/-/sharp-freebsd-wasm32-0.35.2.tgz", + "integrity": "sha512-YoAxdnd8hPUkvLHd3bWY+YA8nw3xM/RyRopYucNsWHVSan8NLVM3X2volsfoRDcXdUJPg6tXahSd7HXPK7lRnw==", + "license": "Apache-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "dependencies": { + "@img/sharp-wasm32": "0.35.2" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.4.tgz", - "integrity": "sha512-zqjjo7RatFfFoP0MkQ51jfuFZBnVE2pRiaydKJ1G/rHZvnsrHAOcQALIi9sA5co5xenQdTugCvtb1cuf78Vf4g==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.3.1.tgz", + "integrity": "sha512-4V/M3roRMTYjiwZY9IOVQOE8OyeCxFAkYmyZDrZl51uOKjibm3oeEJ4WAmLxutAfzFbC9jqUiPs2gbnGflH+7g==", "cpu": [ "arm64" ], @@ -707,9 +734,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.4.tgz", - "integrity": "sha512-1IOd5xfVhlGwX+zXv2N93k0yMONvUlANylbJw1eTah8K/Jtpi15KC+WSiaX/nBmbm2HxRM1gZ0nSdjSsrZbGKg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.3.1.tgz", + "integrity": "sha512-c0/DxItpJv2+dGhgycJBBgotdqruGYDvA79drdh0MD1dFpy7JzJ/PlXwi1H4rFf0eTy8tgbI91aHDnZIceY3jQ==", "cpu": [ "x64" ], @@ -723,9 +750,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.4.tgz", - "integrity": "sha512-bFI7xcKFELdiNCVov8e44Ia4u2byA+l3XtsAj+Q8tfCwO6BQ8iDojYdvoPMqsKDkuoOo+X6HZA0s0q11ANMQ8A==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.3.1.tgz", + "integrity": "sha512-aGGy9aWzXgHBG7HNyQPWorZthlp7+x6fDRoPAQbGO3ThcttuTyKIx3NuSHb6zb4gBNq6/yNn9f1cy9nFKS/Vmg==", "cpu": [ "arm" ], @@ -739,9 +766,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.4.tgz", - "integrity": "sha512-excjX8DfsIcJ10x1Kzr4RcWe1edC9PquDRRPx3YVCvQv+U5p7Yin2s32ftzikXojb1PIFc/9Mt28/y+iRklkrw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.3.1.tgz", + "integrity": "sha512-JznefmcK9j1JKPz8AkQDh89kjojubyfOasWBPKfzMIhPwsgDy9evpE/naJTXXXmghS1iFwR8u/kTwh/I2/+GCw==", "cpu": [ "arm64" ], @@ -755,9 +782,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.4.tgz", - "integrity": "sha512-FMuvGijLDYG6lW+b/UvyilUWu5Ayu+3r2d1S8notiGCIyYU/76eig1UfMmkZ7vwgOrzKzlQbFSuQfgm7GYUPpA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.3.1.tgz", + "integrity": "sha512-1EkwGNCZk6iWNCMWqrvdJ+r1j0PT1zIz60CNPhYnJlK/zyeWqlsPZIe+ocBVqPF8k/Ssee/NCk+tE9Ryrko6ng==", "cpu": [ "ppc64" ], @@ -771,9 +798,9 @@ } }, "node_modules/@img/sharp-libvips-linux-riscv64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.2.4.tgz", - "integrity": "sha512-oVDbcR4zUC0ce82teubSm+x6ETixtKZBh/qbREIOcI3cULzDyb18Sr/Wcyx7NRQeQzOiHTNbZFF1UwPS2scyGA==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-riscv64/-/sharp-libvips-linux-riscv64-1.3.1.tgz", + "integrity": "sha512-Ilays+w2bXdnxzxtQdmXR62u8o8GYa3eL4+Gr+1KiE4xperMZUslRaVPJwwPkzlHEjGfXAfRVAa/7CYCtSqsBw==", "cpu": [ "riscv64" ], @@ -787,9 +814,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.4.tgz", - "integrity": "sha512-qmp9VrzgPgMoGZyPvrQHqk02uyjA0/QrTO26Tqk6l4ZV0MPWIW6LTkqOIov+J1yEu7MbFQaDpwdwJKhbJvuRxQ==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.3.1.tgz", + "integrity": "sha512-VfBwVHQTbRoj4XlpA/KLZ7ltgMpz+4WSejFzQ+GnoImjo1PtEJ59QB2qR1xQEeRPYIkNrPIm2L4cICMvz4C2ew==", "cpu": [ "s390x" ], @@ -803,9 +830,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.4.tgz", - "integrity": "sha512-tJxiiLsmHc9Ax1bz3oaOYBURTXGIRDODBqhveVHonrHJ9/+k89qbLl0bcJns+e4t4rvaNBxaEZsFtSfAdquPrw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.3.1.tgz", + "integrity": "sha512-+c8ukgwU62DS54nCAjw7keOfHUkmr0B5QHEdcOqRnodF/MNXJbVI8Eopoj4B/0H8Asr65I+A4Amrn7a85/md6A==", "cpu": [ "x64" ], @@ -819,9 +846,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.4.tgz", - "integrity": "sha512-FVQHuwx1IIuNow9QAbYUzJ+En8KcVm9Lk5+uGUQJHaZmMECZmOlix9HnH7n1TRkXMS0pGxIJokIVB9SuqZGGXw==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.3.1.tgz", + "integrity": "sha512-qlKb/pwbkAi1WMsJrYHk7CuDrd12s27U2QnRhFYUoJNrRCmkosMTttuRFat/DDB3IlDm5qE1TJgZ4JDnHX8Ldw==", "cpu": [ "arm64" ], @@ -835,9 +862,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.4.tgz", - "integrity": "sha512-+LpyBk7L44ZIXwz/VYfglaX/okxezESc6UxDSoyo2Ks6Jxc4Y7sGjpgU9s4PMgqgjj1gZCylTieNamqA1MF7Dg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.3.1.tgz", + "integrity": "sha512-yO21HwoUVLN8Qa+/SBjQLMYwBWAVJjeGPNe+hc0OUeMeifEtJqu5a1c4HayE1nNpDih9y3/KkoltfkDodmKAlg==", "cpu": [ "x64" ], @@ -851,9 +878,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.5.tgz", - "integrity": "sha512-9dLqsvwtg1uuXBGZKsxem9595+ujv0sJ6Vi8wcTANSFpwV/GONat5eCkzQo/1O6zRIkh0m/8+5BjrRr7jDUSZw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.35.2.tgz", + "integrity": "sha512-SE4kzF2mepn6z+6E7L6lsV8FzuLL6IPQdyX8ZiwROAG/G8td+hP/m7FsFPwidtrF19gvajuC9l6TxAVcsA4S7A==", "cpu": [ "arm" ], @@ -863,19 +890,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.4" + "@img/sharp-libvips-linux-arm": "1.3.1" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.5.tgz", - "integrity": "sha512-bKQzaJRY/bkPOXyKx5EVup7qkaojECG6NLYswgktOZjaXecSAeCWiZwwiFf3/Y+O1HrauiE3FVsGxFg8c24rZg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.35.2.tgz", + "integrity": "sha512-af12Pnd0ZGu2HfP8NayB0kk6eC/lrfbQE6HlR4jD+34wdJ1Vw9TF6TMn6ZvffT+WgqVsl0hRbmNvz2u/23VmwA==", "cpu": [ "arm64" ], @@ -885,19 +912,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.4" + "@img/sharp-libvips-linux-arm64": "1.3.1" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.5.tgz", - "integrity": "sha512-7zznwNaqW6YtsfrGGDA6BRkISKAAE1Jo0QdpNYXNMHu2+0dTrPflTLNkpc8l7MUP5M16ZJcUvysVWWrMefZquA==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.35.2.tgz", + "integrity": "sha512-hYSBm7zcNtDCozCxQHYZJiu63b/bXsgRZuOxCIBZsStMM9Vap47iFHdbX4kCvQsblPB/k+clhELpdQJHQLSHvg==", "cpu": [ "ppc64" ], @@ -907,19 +934,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.4" + "@img/sharp-libvips-linux-ppc64": "1.3.1" } }, "node_modules/@img/sharp-linux-riscv64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.34.5.tgz", - "integrity": "sha512-51gJuLPTKa7piYPaVs8GmByo7/U7/7TZOq+cnXJIHZKavIRHAP77e3N2HEl3dgiqdD/w0yUfiJnII77PuDDFdw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-riscv64/-/sharp-linux-riscv64-0.35.2.tgz", + "integrity": "sha512-qQt0Kc13+Hoan/Awq/qMSQw3L+RI1NCRPgD5cUJ/1WSSmIoysLOc72jlRM3E0OHN9Yr313jgeQ2T+zW+F03QFA==", "cpu": [ "riscv64" ], @@ -929,19 +956,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-riscv64": "1.2.4" + "@img/sharp-libvips-linux-riscv64": "1.3.1" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.5.tgz", - "integrity": "sha512-nQtCk0PdKfho3eC5MrbQoigJ2gd1CgddUMkabUj+rBevs8tZ2cULOx46E7oyX+04WGfABgIwmMC0VqieTiR4jg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.35.2.tgz", + "integrity": "sha512-E4fLLfRPzDLlEeDaTzI98OFLcv++WL5ChLLMwPoVd0CIoZQqupBSNbOisPL5am9XsbQ9T84+iiMpUvbFtkunbA==", "cpu": [ "s390x" ], @@ -951,19 +978,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.4" + "@img/sharp-libvips-linux-s390x": "1.3.1" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.5.tgz", - "integrity": "sha512-MEzd8HPKxVxVenwAa+JRPwEC7QFjoPWuS5NZnBt6B3pu7EG2Ge0id1oLHZpPJdn3OQK+BQDiw9zStiHBTJQQQQ==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.35.2.tgz", + "integrity": "sha512-gi0zFJJRLswfCZmHtJdikXPOc5u7qamSOS3NHedLqLd4W8Q0NqjdBr6TTRIgsfFjqfTsHFgdfvJ9LwqSgcHiAA==", "cpu": [ "x64" ], @@ -973,19 +1000,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.4" + "@img/sharp-libvips-linux-x64": "1.3.1" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.5.tgz", - "integrity": "sha512-fprJR6GtRsMt6Kyfq44IsChVZeGN97gTD331weR1ex1c1rypDEABN6Tm2xa1wE6lYb5DdEnk03NZPqA7Id21yg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.35.2.tgz", + "integrity": "sha512-siWbOW1u6HFnFLrp0waKyW7VEf7jYvcDWdrXEFa8AkdAQgEvuu5Fz8/Y70w9EeqAdwDtfU012BhEHHaDqvQNzg==", "cpu": [ "arm64" ], @@ -995,19 +1022,19 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4" + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.5.tgz", - "integrity": "sha512-Jg8wNT1MUzIvhBFxViqrEhWDGzqymo3sV7z7ZsaWbZNDLXRJZoRGrjulp60YYtV4wfY8VIKcWidjojlLcWrd8Q==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.35.2.tgz", + "integrity": "sha512-YBqMMcjDi4QGYiSn4vNOYBhmlC4z5AXqkOUUqI2e0AFA4urNv4ESgOgwNl3K+4etQhha0twXlzeF20bbULm9Yg==", "cpu": [ "x64" ], @@ -1017,38 +1044,54 @@ "linux" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.4" + "@img/sharp-libvips-linuxmusl-x64": "1.3.1" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.5.tgz", - "integrity": "sha512-OdWTEiVkY2PHwqkbBI8frFxQQFekHaSSkUIJkwzclWZe64O1X4UlUjqqqLaPbUpMOQk6FBu/HtlGXNblIs0huw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.35.2.tgz", + "integrity": "sha512-Mrv4JQNYVQ94xH+jzZ9r+gowleN8mv2FTgKT+PI6bx5C0G8TdNYndu161pg2i7uoBwxy2ImPMHrJOM2LZef7Bw==", + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.11.1" + }, + "engines": { + "node": ">=20.9.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-webcontainers-wasm32": { + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-webcontainers-wasm32/-/sharp-webcontainers-wasm32-0.35.2.tgz", + "integrity": "sha512-QNV27pxs9wpApEiCfvHM1RDoP1w1+2KrUWWDPEhEwg+latvOrfuhWrHWZKwdSFwU6jh3myjw/yOCRsUIuOft3g==", "cpu": [ "wasm32" ], - "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "license": "Apache-2.0", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.7.0" + "@img/sharp-wasm32": "0.35.2" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.5.tgz", - "integrity": "sha512-WQ3AgWCWYSb2yt+IG8mnC6Jdk9Whs7O0gxphblsLvdhSpSTtmu69ZG1Gkb6NuvxsNACwiPV6cNSZNzt0KPsw7g==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.35.2.tgz", + "integrity": "sha512-BiVRYc/t6/Vl3e1hBx0hugG4oN9Pydf4fgMSpxTQJmwGUg/YoXTWHiFeRymHfCZzifxu4F4rpk/I67D0LQ20wQ==", "cpu": [ "arm64" ], @@ -1058,16 +1101,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.5.tgz", - "integrity": "sha512-FV9m/7NmeCmSHDD5j4+4pNI8Cp3aW+JvLoXcTUo0IqyjSfAZJ8dIUmijx1qaJsIiU+Hosw6xM5KijAWRJCSgNg==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.35.2.tgz", + "integrity": "sha512-YYEhx9PImCC7T0tI8JDMi4DB9LwLCXCU5OWNYEXAxh5Q1ShKkyC6byxzoBJ3gEFDnH2lQckWuDe70G7mB2XJog==", "cpu": [ "ia32" ], @@ -1077,16 +1120,16 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": "^20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.5.tgz", - "integrity": "sha512-+29YMsqY2/9eFEiW93eqWnuLcWcufowXewwSNIT6UwZdUUCrM3oFjMWH/Z6/TMmb4hlFenmfAVbpWeup2jryCw==", + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.35.2.tgz", + "integrity": "sha512-imoOyBcoM/iiUr4J6VPpCNjPnjvP/Gks95898yB8YqoGGYmHYbOyCuNv9FMhFgtaiHFGbHW8bxKqRV6VjtXThQ==", "cpu": [ "x64" ], @@ -1096,7 +1139,7 @@ "win32" ], "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" @@ -1807,6 +1850,45 @@ "node": ">= 8" } }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@npmcli/move-file/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@paralleldrive/cuid2": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/@paralleldrive/cuid2/-/cuid2-2.3.1.tgz", @@ -2227,6 +2309,16 @@ "integrity": "sha512-OvjF+z51L3ov0OyAU0duzsYuvO01PH7x4t6DJx+guahgTnBHkhJdG7soQeTSFLWN3efnHyibZ4Z8l2EuWwJN3A==", "license": "MIT" }, + "node_modules/@tootallnate/once": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.1.tgz", + "integrity": "sha512-HqmEUIGRJ5fSXchkVgR5F7qn48bDBzv0kWj/Kfu5e6uci4UlEeng4331LnBkWffb++Ei3FOVLxo8JJWMFBDMeQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@tootallnate/quickjs-emscripten": { "version": "0.23.0", "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", @@ -2570,11 +2662,24 @@ "node": ">= 14" } }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "clean-stack": "^2.0.0", @@ -2706,6 +2811,13 @@ "node": ">=8" } }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, "node_modules/archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", @@ -2835,6 +2947,21 @@ "dev": true, "license": "MIT" }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/argon2": { "version": "0.41.1", "resolved": "https://registry.npmjs.org/argon2/-/argon2-0.41.1.tgz", @@ -3097,6 +3224,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bl": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", @@ -3133,20 +3269,20 @@ } }, "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.3.0.tgz", + "integrity": "sha512-2cGmJupaNgg+QUwVLAucDuWuoMZ6EX9iHDRswZ5lsNYEmwPaRknMPCLZz07yTzVq/83p4o/wzbDZbBrTvGGTIw==", "license": "MIT", "dependencies": { "bytes": "^3.1.2", - "content-type": "^1.0.5", + "content-type": "^2.0.0", "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", + "http-errors": "^2.0.1", + "iconv-lite": "^0.7.2", "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" + "qs": "^6.15.2", + "raw-body": "^3.0.2", + "type-is": "^2.1.0" }, "engines": { "node": ">=18" @@ -3156,6 +3292,19 @@ "url": "https://opencollective.com/express" } }, + "node_modules/body-parser/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/body/node_modules/bytes": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/bytes/-/bytes-1.0.0.tgz", @@ -3328,6 +3477,137 @@ "node": ">= 0.8" } }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/cacache/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/cacache/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/cacache/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/cacache/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cacache/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "optional": true, + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/cacache/node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/cacheable-lookup": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", @@ -3483,6 +3763,15 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, "node_modules/chromium-bidi": { "version": "14.0.0", "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-14.0.0.tgz", @@ -3521,7 +3810,7 @@ "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=6" @@ -3696,6 +3985,16 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, "node_modules/colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", @@ -3772,6 +4071,13 @@ "node": ">= 0.4.0" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -4141,6 +4447,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -4196,6 +4511,13 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -4373,22 +4695,45 @@ "node": ">= 0.8" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", "license": "MIT", + "optional": true, "dependencies": { - "once": "^1.4.0" + "iconv-lite": "^0.6.2" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "node_modules/encoding/node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", "license": "MIT", - "engines": { - "node": ">=6" + "optional": true, + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "engines": { + "node": ">=6" } }, "node_modules/environment": { @@ -4404,6 +4749,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, "node_modules/error": { "version": "7.2.1", "resolved": "https://registry.npmjs.org/error/-/error-7.2.1.tgz", @@ -4852,6 +5204,15 @@ "node": ">= 0.8.0" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, "node_modules/expand-tilde": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/expand-tilde/-/expand-tilde-2.0.2.tgz", @@ -4865,14 +5226,14 @@ } }, "node_modules/express": { - "version": "4.22.1", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", - "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "version": "4.22.2", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", + "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "~1.20.3", + "body-parser": "~1.20.5", "content-disposition": "~0.5.4", "content-type": "~1.0.4", "cookie": "~0.7.1", @@ -4891,7 +5252,7 @@ "parseurl": "~1.3.3", "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "~6.14.0", + "qs": "~6.15.1", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", "send": "~0.19.0", @@ -4993,9 +5354,9 @@ "license": "MIT" }, "node_modules/express/node_modules/body-parser": { - "version": "1.20.4", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", - "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "version": "1.20.5", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", + "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", "license": "MIT", "dependencies": { "bytes": "~3.1.2", @@ -5006,7 +5367,7 @@ "http-errors": "~2.0.1", "iconv-lite": "~0.4.24", "on-finished": "~2.4.1", - "qs": "~6.14.0", + "qs": "~6.15.1", "raw-body": "~2.5.3", "type-is": "~1.6.18", "unpipe": "~1.0.0" @@ -5052,21 +5413,6 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, - "node_modules/express/node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/express/node_modules/raw-body": { "version": "2.5.3", "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", @@ -5296,6 +5642,12 @@ "url": "https://github.com/sindresorhus/file-type?sponsor=1" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5487,16 +5839,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -5577,6 +5929,30 @@ "node": ">=14.14" } }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs-minipass/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -5607,6 +5983,66 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/gauge/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/gauge/node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/gauge/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/gaze": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz", @@ -5631,16 +6067,16 @@ } }, "node_modules/geoip-lite": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/geoip-lite/-/geoip-lite-2.0.2.tgz", - "integrity": "sha512-C90u7hFgIrDTboiqJm+pc23buM5TykTJq11sCsRItKr6a4IHIHJvZPjq+pY96+Q15saWAMzs3ILzqyEVF0v2gA==", + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/geoip-lite/-/geoip-lite-2.0.3.tgz", + "integrity": "sha512-I0v/6sITK5FhEXHmatGSPBMply2nHN/2D743LIx4KkksttZt/E505lDaV6qUwt5RM08MbZW+/4E0KxusLVN39w==", "license": "Apache-2.0", "dependencies": { "chalk": "4.1 - 4.1.2", "iconv-lite": "0.4.13 - 0.6.3", "ip-address": "^10.2.0", "lazy": "1.0.11", - "yauzl": "^3.2.1" + "yauzl": "^3.3.1" }, "engines": { "node": ">=24.0.0" @@ -5786,6 +6222,12 @@ "omggif": "^1.0.10" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, "node_modules/glob": { "version": "10.5.0", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", @@ -6610,6 +7052,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, "node_modules/hasha": { "version": "5.2.2", "resolved": "https://registry.npmjs.org/hasha/-/hasha-5.2.2.tgz", @@ -6638,9 +7087,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" @@ -6767,6 +7216,16 @@ "node": ">= 14" } }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, "node_modules/husky": { "version": "9.1.7", "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", @@ -6879,7 +7338,7 @@ "version": "0.1.4", "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=0.8.19" @@ -6889,12 +7348,19 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true, + "devOptional": true, "license": "MIT", "engines": { "node": ">=8" } }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", @@ -7026,6 +7492,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, "node_modules/is-number": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", @@ -7737,16 +8210,16 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "17.0.5", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.5.tgz", - "integrity": "sha512-d12yC+/e8RhBjZtaxZn71FyrgU/P5e+uAPifhCLwdosQZP/zamSdKRWDC30ocVIbzDKiFG1McHc/LUgB92GIPw==", + "version": "17.0.8", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-17.0.8.tgz", + "integrity": "sha512-B2P/d+jVW0UXOQ0MVMLrB/9ydA1P+zz6jYfdrbbEd9ur3S2rcbduFWKiUCC02Sm5hbC8nrm7y24WuYMG54HfxA==", "dev": true, "license": "MIT", "dependencies": { "listr2": "^10.2.1", "picomatch": "^4.0.4", "string-argv": "^0.3.2", - "tinyexec": "^1.1.2" + "tinyexec": "^1.2.4" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -7758,7 +8231,7 @@ "url": "https://opencollective.com/lint-staged" }, "optionalDependencies": { - "yaml": "^2.8.4" + "yaml": "^2.9.0" } }, "node_modules/listr2": { @@ -8154,6 +8627,117 @@ "semver": "bin/semver.js" } }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/make-fetch-happen/node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/make-fetch-happen/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/make-fetch-happen/node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/make-iterator": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", @@ -8371,7 +8955,6 @@ "version": "1.2.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", - "devOptional": true, "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" @@ -8387,56 +8970,222 @@ "node": ">=16 || 14 >=14.17" } }, - "node_modules/mitt": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", - "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", - "license": "MIT" - }, - "node_modules/mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "devOptional": true, - "license": "MIT", + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, "dependencies": { - "minimist": "^1.2.6" + "minipass": "^3.0.0" }, - "bin": { - "mkdirp": "bin/cmd.js" + "engines": { + "node": ">= 8" } }, - "node_modules/mocha": { - "version": "10.8.2", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", - "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", - "dev": true, - "license": "MIT", + "node_modules/minipass-collect/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, "dependencies": { - "ansi-colors": "^4.1.3", - "browser-stdout": "^1.3.1", - "chokidar": "^3.5.3", - "debug": "^4.3.5", - "diff": "^5.2.0", - "escape-string-regexp": "^4.0.0", - "find-up": "^5.0.0", - "glob": "^8.1.0", - "he": "^1.2.0", - "js-yaml": "^4.1.0", - "log-symbols": "^4.1.0", - "minimatch": "^5.1.6", - "ms": "^2.1.3", - "serialize-javascript": "^6.0.2", - "strip-json-comments": "^3.1.1", - "supports-color": "^8.1.1", - "workerpool": "^6.5.1", - "yargs": "^16.2.0", - "yargs-parser": "^20.2.9", - "yargs-unparser": "^2.0.0" - }, - "bin": { - "_mocha": "bin/_mocha", - "mocha": "bin/mocha.js" + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-fetch/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.7.tgz", + "integrity": "sha512-TbqTz9cUwWyHS2Dy89P3ocAGUGxKjjLuR9z8w4WUTGAVgEj17/4nhgo2Du56i0Fm3Pm30g4iA8Lcqctc76jCzA==", + "license": "BlueOak-1.0.0", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-flush/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-pipeline/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minizlib/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "devOptional": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/mocha": { + "version": "10.8.2", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.8.2.tgz", + "integrity": "sha512-VZlYo/WE8t1tstuRmqgeyBgCbJc/lEdopaa+axcKzTBJ+UIdlAB9XnmvTCAH4pwR4ElNInaedhEBmZD8iCSVEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-colors": "^4.1.3", + "browser-stdout": "^1.3.1", + "chokidar": "^3.5.3", + "debug": "^4.3.5", + "diff": "^5.2.0", + "escape-string-regexp": "^4.0.0", + "find-up": "^5.0.0", + "glob": "^8.1.0", + "he": "^1.2.0", + "js-yaml": "^4.1.0", + "log-symbols": "^4.1.0", + "minimatch": "^5.1.6", + "ms": "^2.1.3", + "serialize-javascript": "^6.0.2", + "strip-json-comments": "^3.1.1", + "supports-color": "^8.1.1", + "workerpool": "^6.5.1", + "yargs": "^16.2.0", + "yargs-parser": "^20.2.9", + "yargs-unparser": "^2.0.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha.js" }, "engines": { "node": ">= 14.0.0" @@ -8831,6 +9580,12 @@ "license": "MIT", "optional": true }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -8878,6 +9633,18 @@ "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==", "license": "MIT" }, + "node_modules/node-abi": { + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/node-addon-api": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz", @@ -8887,6 +9654,31 @@ "node": "^18 || ^20 || >= 21" } }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, "node_modules/node-gyp-build": { "version": "4.8.4", "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", @@ -8898,6 +9690,52 @@ "node-gyp-build-test": "build-test.js" } }, + "node_modules/node-gyp/node_modules/brace-expansion": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.15.tgz", + "integrity": "sha512-EwOCDEex4quD37XhqM3omwtMoJjr//isUZz1JopUNWms+4Z2ViyM/k1YIRePpoVNnQhENnxtFjLaxNHrT7xIUg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/node-gyp/node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/node-gyp/node_modules/minimatch": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", + "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, "node_modules/node-preload": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", @@ -8919,9 +9757,9 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "8.0.7", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-8.0.7.tgz", - "integrity": "sha512-pkjE4mkBzQjdJT4/UmlKl3pX0rC9fZmjh7c6C9o7lv66Ac6w9WCnzPzhbPNxwZAzlF4mdq4CSWB5+FbK6FWCow==", + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-9.0.1.tgz", + "integrity": "sha512-Gwv8SQewT616ZM/URn0H54b8PWo/Wum7md3EW2aWy1lO27+WZCX+Xyak3J+NlmHUjDh5ME+uesJUDRbR3Ye8Bw==", "license": "MIT-0", "engines": { "node": ">=6.0.0" @@ -8963,6 +9801,23 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, "node_modules/nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -9894,6 +10749,67 @@ "node": ">=4" } }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/prebuild-install/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/prebuild-install/node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/prebuild-install/node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/prelude-ls": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", @@ -9944,6 +10860,27 @@ "node": ">=0.4.0" } }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/properties-parser": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/properties-parser/-/properties-parser-0.6.0.tgz", @@ -10065,9 +11002,9 @@ } }, "node_modules/qs": { - "version": "6.15.1", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", - "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", + "version": "6.15.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", + "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" @@ -10121,16 +11058,6 @@ "node": ">= 0.8" } }, - "node_modules/randombytes": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", - "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -10141,9 +11068,9 @@ } }, "node_modules/rate-limiter-flexible": { - "version": "11.1.0", - "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-11.1.0.tgz", - "integrity": "sha512-lyyC0SqKz+dE5JoHZ4JMqdrM3LSZKBxzuAFAyKCYAnmHnPz/Rb6iDquxoL4CMipDXoR0G+QRhOzYWL3JKihbNw==", + "version": "11.2.0", + "resolved": "https://registry.npmjs.org/rate-limiter-flexible/-/rate-limiter-flexible-11.2.0.tgz", + "integrity": "sha512-L0eIK+BmFMi6NcvmtEg7RSswOFKi9MMD5RhBIypFfOve+G6Jl1Xbb8qEHgK3uRzNtkOFYF0L9f7P4rSf2PnUVw==", "license": "ISC" }, "node_modules/raw-body": { @@ -10161,6 +11088,30 @@ "node": ">= 0.10" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/readable-stream": { "version": "3.6.2", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", @@ -10337,6 +11288,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, "node_modules/reusify": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", @@ -10360,7 +11321,7 @@ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "deprecated": "Rimraf versions prior to v4 are no longer supported", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "glob": "^7.1.3" @@ -10376,7 +11337,7 @@ "version": "1.1.14", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.14.tgz", "integrity": "sha512-MWPGfDxnyzKU7rNOW9SP/c50vi3xrmrua/+6hfPbCS2ABNWfx24vPidzvC7krjU/RTo235sV776ymlsMtGKj8g==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -10388,7 +11349,7 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -10409,7 +11370,7 @@ "version": "3.1.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", - "dev": true, + "devOptional": true, "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -10503,9 +11464,9 @@ } }, "node_modules/semver": { - "version": "7.8.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", - "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -10566,13 +11527,13 @@ } }, "node_modules/serialize-javascript": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.2.tgz", - "integrity": "sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g==", + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-7.0.5.tgz", + "integrity": "sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==", "dev": true, "license": "BSD-3-Clause", - "dependencies": { - "randombytes": "^2.1.0" + "engines": { + "node": ">=20.0.0" } }, "node_modules/serve-static": { @@ -10594,7 +11555,7 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "dev": true, + "devOptional": true, "license": "ISC" }, "node_modules/setprototypeof": { @@ -10604,47 +11565,47 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.34.5", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.5.tgz", - "integrity": "sha512-Ou9I5Ft9WNcCbXrU9cMgPBcCK8LiwLqcbywW3t4oDV37n1pzpuNLsYiAV8eODnjbtQlSDwZ2cUEeQz4E54Hltg==", - "hasInstallScript": true, + "version": "0.35.2", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.35.2.tgz", + "integrity": "sha512-FVtFjtBCMiJS6yb5CX7Sop45WFMpeGw6oRKuJnXYgf/f1ms/D7LE/ZUSNxnW7rZ/dbslQWYkoqFHGPaDBtaK4w==", "license": "Apache-2.0", "dependencies": { - "@img/colour": "^1.0.0", + "@img/colour": "^1.1.0", "detect-libc": "^2.1.2", - "semver": "^7.7.3" + "semver": "^7.8.4" }, "engines": { - "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + "node": ">=20.9.0" }, "funding": { "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.5", - "@img/sharp-darwin-x64": "0.34.5", - "@img/sharp-libvips-darwin-arm64": "1.2.4", - "@img/sharp-libvips-darwin-x64": "1.2.4", - "@img/sharp-libvips-linux-arm": "1.2.4", - "@img/sharp-libvips-linux-arm64": "1.2.4", - "@img/sharp-libvips-linux-ppc64": "1.2.4", - "@img/sharp-libvips-linux-riscv64": "1.2.4", - "@img/sharp-libvips-linux-s390x": "1.2.4", - "@img/sharp-libvips-linux-x64": "1.2.4", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.4", - "@img/sharp-libvips-linuxmusl-x64": "1.2.4", - "@img/sharp-linux-arm": "0.34.5", - "@img/sharp-linux-arm64": "0.34.5", - "@img/sharp-linux-ppc64": "0.34.5", - "@img/sharp-linux-riscv64": "0.34.5", - "@img/sharp-linux-s390x": "0.34.5", - "@img/sharp-linux-x64": "0.34.5", - "@img/sharp-linuxmusl-arm64": "0.34.5", - "@img/sharp-linuxmusl-x64": "0.34.5", - "@img/sharp-wasm32": "0.34.5", - "@img/sharp-win32-arm64": "0.34.5", - "@img/sharp-win32-ia32": "0.34.5", - "@img/sharp-win32-x64": "0.34.5" + "@img/sharp-darwin-arm64": "0.35.2", + "@img/sharp-darwin-x64": "0.35.2", + "@img/sharp-freebsd-wasm32": "0.35.2", + "@img/sharp-libvips-darwin-arm64": "1.3.1", + "@img/sharp-libvips-darwin-x64": "1.3.1", + "@img/sharp-libvips-linux-arm": "1.3.1", + "@img/sharp-libvips-linux-arm64": "1.3.1", + "@img/sharp-libvips-linux-ppc64": "1.3.1", + "@img/sharp-libvips-linux-riscv64": "1.3.1", + "@img/sharp-libvips-linux-s390x": "1.3.1", + "@img/sharp-libvips-linux-x64": "1.3.1", + "@img/sharp-libvips-linuxmusl-arm64": "1.3.1", + "@img/sharp-libvips-linuxmusl-x64": "1.3.1", + "@img/sharp-linux-arm": "0.35.2", + "@img/sharp-linux-arm64": "0.35.2", + "@img/sharp-linux-ppc64": "0.35.2", + "@img/sharp-linux-riscv64": "0.35.2", + "@img/sharp-linux-s390x": "0.35.2", + "@img/sharp-linux-x64": "0.35.2", + "@img/sharp-linuxmusl-arm64": "0.35.2", + "@img/sharp-linuxmusl-x64": "0.35.2", + "@img/sharp-webcontainers-wasm32": "0.35.2", + "@img/sharp-win32-arm64": "0.35.2", + "@img/sharp-win32-ia32": "0.35.2", + "@img/sharp-win32-x64": "0.35.2" } }, "node_modules/shebang-command": { @@ -10815,6 +11776,51 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/simple-xml-to-json": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.7.tgz", @@ -11069,6 +12075,62 @@ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", "license": "BSD-3-Clause" }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/sqlite3/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ssri/node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -11384,6 +12446,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "deprecated": "Old versions of tar are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/tar-fs": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.2.tgz", @@ -11410,6 +12490,27 @@ "streamx": "^2.15.0" } }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tar/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/teex": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/teex/-/teex-1.0.1.tgz", @@ -11566,9 +12667,9 @@ "license": "MIT" }, "node_modules/tinyexec": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.1.2.tgz", - "integrity": "sha512-dAqSqE/RabpBKI8+h26GfLq6Vb3JVXs30XYQjdMjaj/c2tS8IYYMbIzP599KtRj7c57/wYApb3QjgRgXmrCukA==", + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-1.2.4.tgz", + "integrity": "sha512-SHf/r48b7vOrjve9PxJo3MN5v5yuyjHvdUcrQffT3WXMUfnGmHDVbC4k3sHJaJTgZCwpUplIaAo5ANtMyp3YHg==", "dev": true, "license": "MIT", "engines": { @@ -11671,6 +12772,18 @@ "node": ">=0.6.x" } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -11698,17 +12811,34 @@ } }, "node_modules/type-is": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", - "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", + "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", "dependencies": { - "content-type": "^1.0.5", + "content-type": "^2.0.0", "media-typer": "^1.1.0", "mime-types": "^3.0.0" }, "engines": { - "node": ">= 0.6" + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/type-is/node_modules/content-type": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", + "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/type-is/node_modules/mime-db": { @@ -11836,6 +12966,26 @@ "integrity": "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==", "license": "MIT" }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, "node_modules/universalify": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", @@ -11929,13 +13079,17 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "version": "11.1.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", + "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", "dev": true, + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8flags": { @@ -12076,6 +13230,48 @@ "dev": true, "license": "ISC" }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wide-align/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/window-or-global": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/window-or-global/-/window-or-global-1.0.1.tgz", @@ -12237,9 +13433,9 @@ "license": "ISC" }, "node_modules/ws": { - "version": "8.20.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.0.tgz", - "integrity": "sha512-sAt8BhgNbzCtgGbt2OxmpuryO63ZoDk/sqaB/znQm94T4fCEsy/yV+7CdC1kJhOU9lboAEU7R3kquuycDoibVA==", + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", + "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", "license": "MIT", "engines": { "node": ">=10.0.0" @@ -12314,10 +13510,16 @@ "node": ">=10" } }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + }, "node_modules/yaml": { - "version": "2.8.4", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz", - "integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==", + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.9.0.tgz", + "integrity": "sha512-2AvhNX3mb8zd6Zy7INTtSpl1F15HW6Wnqj0srWlkKLcpYl/gMIMJiyuGq2KeI2YFxUPjdlB+3Lc10seMLtL4cA==", "dev": true, "license": "ISC", "optional": true, @@ -12430,12 +13632,11 @@ } }, "node_modules/yauzl": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.3.0.tgz", - "integrity": "sha512-PtGEvEP30p7sbIBJKUBjUnqgTVOyMURc4dLo9iNyAJnNIEz9pm88cCXF21w94Kg3k6RXkeZh5DHOGS0qEONvNQ==", + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-3.4.0.tgz", + "integrity": "sha512-jIH9yLR9wqr0wOS0TpBvo/g/2UgZH5qePVbjgRliiF0BYvOZyaBknKsF+x9Iht0O6sqgnB93rCICdOZFecJuDw==", "license": "MIT", "dependencies": { - "buffer-crc32": "~0.2.3", "pend": "~1.2.0" }, "engines": { diff --git a/package.json b/package.json index 6a27da39613..d942c6fcda6 100644 --- a/package.json +++ b/package.json @@ -46,7 +46,7 @@ "async": "3.2.6", "basic-auth": "2.0.1", "bluebird": "3.7.2", - "body-parser": "2.2.2", + "body-parser": "2.3.0", "bunyan": "1.8.15", "colors": "1.4.0", "connect-flash": "0.1.1", @@ -59,13 +59,13 @@ "csvtojson": "2.0.14", "ejs": "5.0.2", "errorhandler": "1.5.2", - "express": "4.22.1", + "express": "4.22.2", "express-rate-limit": "8.5.2", "express-session": "1.19.0", "form-data": "^4.0.0", "formidable": "2.1.3", "fs-extra": "11.3.5", - "geoip-lite": "2.0.2", + "geoip-lite": "2.0.3", "get-random-values": "^5.0.0", "grunt": "1.6.2", "grunt-cli": "1.5.0", @@ -90,14 +90,15 @@ "mongodb": "6.20.0", "mongodb-connection-string-url": "^7.0.1", "nginx-conf": "2.1.0", - "nodemailer": "8.0.7", + "nodemailer": "9.0.1", "object-hash": "3.0.0", "properties-parser": "0.6.0", "puppeteer": "^24.6.1", "rate-limiter-flexible": "^11.0.0", "sass": "1.99.0", "semver": "^7.7.1", - "sharp": "^0.34.2", + "sharp": "^0.35.0", + "sqlite3": "^5.1.7", "tslib": "^2.6.3", "uglify-js": "3.19.3", "underscore": "1.13.8", @@ -110,7 +111,10 @@ }, "csurf": { "cookie": "^0.7.1" - } + }, + "serialize-javascript": "^7.0.5", + "uuid": "^11.1.1", + "@tootallnate/once": "^2.0.1" }, "jshintConfig": { "esversion": 6, diff --git a/plugins/alerts/api/api.js b/plugins/alerts/api/api.js index d3349133494..3707b43a39d 100644 --- a/plugins/alerts/api/api.js +++ b/plugins/alerts/api/api.js @@ -5,7 +5,7 @@ var Promise = require("bluebird"); const JOB = require('../../../api/parts/jobs'); const utils = require('./parts/utils.js'); const _ = require('lodash'); -const { validateCreate, validateRead, validateUpdate, hasCreateRight } = require('../../../api/utils/rights.js'); +const { validateCreate, validateRead, validateUpdate, hasCreateRight, getAdminApps, getUserAppsForFeaturePermission } = require('../../../api/utils/rights.js'); const FEATURE_NAME = 'alerts'; const commonLib = require("./parts/common-lib.js"); const moment = require('moment-timezone'); @@ -461,7 +461,18 @@ function getScheduleTextExpression(period, offset) { let query = {}; let count_query = { _id: 'meta'}; if (params.member.global_admin !== true) { - query = { createdBy: params.member._id}; + //scope to the caller's own alerts AND only those targeting + //apps they can currently read alerts on, so alerts on apps + //the user no longer has access to are not disclosed + var allowedApps = (getAdminApps(params.member) || []) + .concat(getUserAppsForFeaturePermission(params.member, FEATURE_NAME, 'r') || []); + //legacy members (no permission object) are not covered by + //getUserAppsForFeaturePermission, so include their user_of + //apps too, otherwise they would see none of their own alerts + if (typeof params.member.permission === "undefined" && Array.isArray(params.member.user_of)) { + allowedApps = allowedApps.concat(params.member.user_of); + } + query = { createdBy: params.member._id, selectedApps: { $in: allowedApps } }; count_query = {_id: 'email:' + params.member.email}; } common.db.collection("alerts").find(query).toArray(function(err, alertsList) { diff --git a/plugins/alerts/api/parts/utils.js b/plugins/alerts/api/parts/utils.js index eabdf4c1c38..3d316f4e7fd 100644 --- a/plugins/alerts/api/parts/utils.js +++ b/plugins/alerts/api/parts/utils.js @@ -10,6 +10,7 @@ const fs = require('fs'); var Promise = require("bluebird"); const _ = require("lodash"); const log = require('../../../../api/utils/log.js')('alert:utils'); +const ssrfProtection = require('../../../../api/utils/ssrf-protection'); const utils = {_apps: {}}; utils.sendEmail = function(to, subject, message, callback) { @@ -18,10 +19,29 @@ utils.sendEmail = function(to, subject, message, callback) { }; utils.sendRequest = function(url, callback) { - return request(url, function(error, response, body) { - log.d('will send Alert request', error, response, body); + //validate the (potentially user/config-supplied) URL against SSRF before + //the server fetches it, and pin the connect-time IP via safeLookup so an + //internal/private/link-local target cannot be reached + //returns the promise chain; the callback always uses an (err, body) + //signature on every path for a consistent contract + return ssrfProtection.isUrlSafe(url).then(function(urlCheck) { + if (!urlCheck.safe) { + log.e('Alert request blocked by SSRF protection:', urlCheck.error); + if (callback) { + callback(new Error('SSRF protection: ' + urlCheck.error), null); + } + return; + } + request(ssrfProtection.getSsrfSafeOptions({ uri: url }), function(error, response, body) { + log.d('will send Alert request', error, response, body); + if (callback) { + callback(error, body); + } + }); + }).catch(function(e) { + log.e('Alert request SSRF validation error:', e); if (callback) { - callback(error, body); + callback(e, null); } }); }; diff --git a/plugins/compare/api/api.js b/plugins/compare/api/api.js index 314933192d2..cbb66cdb332 100644 --- a/plugins/compare/api/api.js +++ b/plugins/compare/api/api.js @@ -8,7 +8,7 @@ var exported = {}, crypto = require('crypto'), async = require('async'), log = common.log('compare:api'), - { validateRead, getUserApps } = require('../../../api/utils/rights.js'); + { validateRead, getAdminApps, getUserAppsForFeaturePermission } = require('../../../api/utils/rights.js'); const FEATURE_NAME = 'compare'; @@ -154,16 +154,19 @@ const FEATURE_NAME = 'compare'; params.qstring.app_id = appsToFetch[0]; validateRead(params, FEATURE_NAME, function() { - const userApps = getUserApps(params.member); if (!params.member.global_admin) { + // every requested app must be one the member can read with the + // compare feature, not merely an app they belong to; admin apps + // include the feature implicitly + var allowedApps = (getAdminApps(params.member) || []) + .concat(getUserAppsForFeaturePermission(params.member, FEATURE_NAME, 'r') || []); + // legacy members (no permission object) are not covered by + // getUserAppsForFeaturePermission, so fall back to their user_of + if (typeof params.member.permission === "undefined" && Array.isArray(params.member.user_of)) { + allowedApps = allowedApps.concat(params.member.user_of); + } for (var i = 0; i < appsToFetch.length; i++) { - if (params.member && userApps) { - if (userApps.indexOf(appsToFetch[i]) === -1) { - common.returnMessage(params, 401, 'User does not have view rights for one or more apps provided in apps parameter'); - return true; - } - } - else { + if (allowedApps.indexOf(appsToFetch[i]) === -1) { common.returnMessage(params, 401, 'User does not have view rights for one or more apps provided in apps parameter'); return true; } diff --git a/plugins/compare/tests.js b/plugins/compare/tests.js index 26f8e79698b..a59bb6ab0c8 100644 --- a/plugins/compare/tests.js +++ b/plugins/compare/tests.js @@ -237,4 +237,71 @@ describe('Testing Compare Plugin', function() { }); }); + + describe("Testing Compare Apps per-app permission", function() { + var API_KEY_ADMIN = ""; + var BASE_APP_ID = ""; + var victimAppId = ""; + var scopedApiKey = ""; + var scopedUserId = ""; + var uniq = Date.now(); + + before(function() { + API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); + BASE_APP_ID = testUtils.get("APP_ID"); + }); + + it('should create a victim app and a user with compare read on the base app only', function(done) { + request.get('/i/apps/create?api_key=' + API_KEY_ADMIN + '&args=' + encodeURIComponent(JSON.stringify({name: "CompareVictimApp", type: "mobile"}))) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + victimAppId = res.body._id; + var perm = { _: {a: [], u: [BASE_APP_ID]}, c: {}, r: {}, u: {}, d: {} }; + ["c", "r", "u", "d"].forEach(function(t) { + perm[t][BASE_APP_ID] = {all: false, allowed: {compare: true}}; + }); + var userParams = {full_name: "compareuser" + uniq, username: "compareuser" + uniq, password: "p4ssw0rD!", email: "compareuser" + uniq + "@mail.test", permission: perm}; + request.get('/i/users/create?api_key=' + API_KEY_ADMIN + '&args=' + encodeURIComponent(JSON.stringify(userParams))) + .expect(200) + .end(function(err2, res2) { + if (err2) { + return done(err2); + } + scopedApiKey = res2.body.api_key; + scopedUserId = res2.body._id; + should.exist(scopedApiKey); + done(); + }); + }); + }); + + it('should reject comparing apps when one app lacks compare permission', function(done) { + request.get('/o/compare/apps?period=30days&apps=' + encodeURIComponent(JSON.stringify([BASE_APP_ID, victimAppId])) + '&api_key=' + scopedApiKey) + .expect(401) + .end(function(err) { + return done(err); + }); + }); + + it('should allow comparing only apps the member can read with compare', function(done) { + request.get('/o/compare/apps?period=30days&apps=' + encodeURIComponent(JSON.stringify([BASE_APP_ID])) + '&api_key=' + scopedApiKey) + .expect(200) + .end(function(err) { + return done(err); + }); + }); + + after(function(done) { + request.get('/i/users/delete?api_key=' + API_KEY_ADMIN + '&args=' + encodeURIComponent(JSON.stringify({user_ids: [scopedUserId]}))) + .end(function() { + request.get('/i/apps/delete?api_key=' + API_KEY_ADMIN + '&args=' + encodeURIComponent(JSON.stringify({app_id: victimAppId}))) + .end(function() { + done(); + }); + }); + }); + }); }); diff --git a/plugins/compliance-hub/api/api.js b/plugins/compliance-hub/api/api.js index 4cada915655..d983c89c006 100644 --- a/plugins/compliance-hub/api/api.js +++ b/plugins/compliance-hub/api/api.js @@ -1,6 +1,7 @@ var plugin = {}, crypto = require('crypto'), common = require('../../../api/utils/common.js'), + log = common.log('compliance:api'), countlyCommon = require('../../../api/lib/countly.common.js'), appUsers = require('../../../api/parts/mgmt/app_users.js'), fetch = require('../../../api/parts/data/fetch.js'), @@ -139,16 +140,13 @@ const FEATURE_NAME = 'compliance_hub'; return true; } validateRead(params, FEATURE_NAME, function() { - var query = params.qstring.query || {}; - if (typeof query === "string" && query.length) { - try { - query = JSON.parse(query); - } - catch (ex) { - query = {}; - } + var parsed = common.parseUserQuery(params.qstring.query); + if (parsed.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsed.error); + common.returnMessage(params, 400, parsed.error); + return; } - common.stripUnsafeMongoOperators(query); + var query = parsed.query; // Force scope to the request's app_id even though this lookup // is in app_users; defense-in-depth in case a future // refactor changes the collection. @@ -165,16 +163,13 @@ const FEATURE_NAME = 'compliance_hub'; return true; } validateRead(params, FEATURE_NAME, function() { - var query = params.qstring.query || {}; - if (typeof query === "string" && query.length) { - try { - query = JSON.parse(query); - } - catch (ex) { - query = {}; - } + var parsed = common.parseUserQuery(params.qstring.query); + if (parsed.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsed.error); + common.returnMessage(params, 400, parsed.error); + return; } - common.stripUnsafeMongoOperators(query); + var query = parsed.query; query.app_id = params.app_id.toString(); common.db.collection("consent_history").countDocuments(query, function(err, total) { if (err) { @@ -184,16 +179,13 @@ const FEATURE_NAME = 'compliance_hub'; params.qstring.query = params.qstring.query || params.qstring.filter || {}; params.qstring.project = params.qstring.project || params.qstring.projection || {}; - params.qstring.query = params.qstring.query || {}; - if (typeof params.qstring.query === "string" && params.qstring.query.length) { - try { - params.qstring.query = JSON.parse(params.qstring.query); - } - catch (ex) { - params.qstring.query = {}; - } + var parsedSearch = common.parseUserQuery(params.qstring.query); + if (parsedSearch.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedSearch.error); + common.returnMessage(params, 400, parsedSearch.error); + return; } - common.stripUnsafeMongoOperators(params.qstring.query); + params.qstring.query = parsedSearch.query; if (params.qstring.sSearch && params.qstring.sSearch !== "") { try { @@ -295,6 +287,13 @@ const FEATURE_NAME = 'compliance_hub'; } else if (total > 0) { params.qstring.query = params.qstring.query || params.qstring.filter || {}; + var parsedC = common.parseUserQuery(params.qstring.query); + if (parsedC.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsedC.error); + common.returnMessage(params, 400, parsedC.error); + return; + } + params.qstring.query = parsedC.query; params.qstring.project = params.qstring.project || params.qstring.projection || {"did": 1, "d": 1, "av": 1, "consent": 1, "lac": 1, "uid": 1, "appUserExport": 1}; if (params.qstring.sSearch && params.qstring.sSearch !== "") { diff --git a/plugins/compliance-hub/tests.js b/plugins/compliance-hub/tests.js index f51874d3035..77f4ef95e87 100644 --- a/plugins/compliance-hub/tests.js +++ b/plugins/compliance-hub/tests.js @@ -585,6 +585,37 @@ describe('Testing Compliance Hub', function() { }); }); + describe('Check consent current', function() { + it('should return current consent', function(done) { + request + .get('/o/consent/current?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var ob = JSON.parse(res.text); + ob.should.be.an.Object; + setTimeout(done, 100); + }); + }); + }); + describe('Check app_users consents', function() { + it('should list app users with consent data', function(done) { + request + .get('/o/app_users/consents?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var ob = JSON.parse(res.text); + ob.should.be.an.Object; + setTimeout(done, 100); + }); + }); + }); + describe('Reset App', function() { it('should reset data', function(done) { var params = {app_id: APP_ID, period: "reset"}; diff --git a/plugins/consolidate/api/api.js b/plugins/consolidate/api/api.js index 9c56fc6ce88..da06b74cb0f 100644 --- a/plugins/consolidate/api/api.js +++ b/plugins/consolidate/api/api.js @@ -1,6 +1,7 @@ const plugins = require('../../pluginManager.js'); const common = require('../../../api/utils/common.js'); const { processRequest } = require('../../../api/utils/requestProcessor'); +const rights = require('../../../api/utils/rights.js'); //write api call plugins.register("/sdk/pre", function(ob) { @@ -88,7 +89,7 @@ plugins.register("/i", async function(ob) { }); -plugins.register('/i/apps/update/plugins/consolidate', async function({app, config}) { +plugins.register('/i/apps/update/plugins/consolidate', async function({params, app, config}) { try { console.log('Updating app %s config: %j', app._id, config); const addedSourceApps = config.selectedApps; @@ -98,6 +99,17 @@ plugins.register('/i/apps/update/plugins/consolidate', async function({app, conf return "Nothing changed"; } + // Apply the config only to source apps the member has access to (the same set + // the App Management UI offers). Global admins pass. + const member = params && params.member; + if (!member || !member.global_admin) { + const allowedApps = (member && rights.getUserApps(member)) || []; + const unauthorized = addedSourceApps.filter((id) => allowedApps.indexOf(id + "") === -1); + if (unauthorized.length) { + throw new Error('No access to source app(s): ' + unauthorized.join(', ')); + } + } + const removedSourceApps = addedSourceApps .filter(x => !initialSourceApps.includes(x)) .concat(initialSourceApps.filter(x => !addedSourceApps.includes(x))); diff --git a/plugins/crashes/api/api.js b/plugins/crashes/api/api.js index 48f2fc070e7..7490882ddb1 100644 --- a/plugins/crashes/api/api.js +++ b/plugins/crashes/api/api.js @@ -884,6 +884,7 @@ plugins.setConfigs("crashes", { else if (obParams.qstring.method === 'crashes') { validateRead(obParams, FEATURE_NAME, async function(params) { if (params.qstring.group) { + params.qstring.group = params.qstring.group + ""; if (params.qstring.userlist) { common.db.collection('app_crashusers' + params.app_id).find({group: params.qstring.group}, {uid: 1, _id: 0}).toArray(function(err, uids) { var res = []; @@ -1017,12 +1018,13 @@ plugins.setConfigs("crashes", { var columns = ["name", "os", "reports", "lastTs", "users", "latest_version"]; var filter = {}; if (params.qstring.query && params.qstring.query !== "") { - try { - filter = JSON.parse(params.qstring.query); - } - catch (ex) { - console.log("Cannot parse crashes query", params.qstring.query); + var parsed = common.parseUserQuery(params.qstring.query); + if (parsed.error) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + parsed.error); + common.returnMessage(params, 400, parsed.error); + return; } + filter = parsed.query; } if (params.qstring.sSearch && params.qstring.sSearch !== "") { var reg; @@ -1424,10 +1426,6 @@ plugins.setConfigs("crashes", { }); break; case 'view': - if (!obParams.qstring.args) { - common.returnMessage(obParams, 400, 'Please provide args parameter'); - return true; - } validateUpdate(obParams, FEATURE_NAME, function(params) { var crashes = params.qstring.args.crashes || [params.qstring.args.crash_id]; common.db.collection('app_crashgroups' + params.qstring.app_id).find({'_id': {$in: crashes}}).toArray(function(crashGroupsErr, groups) { @@ -1466,10 +1464,11 @@ plugins.setConfigs("crashes", { return true; } validateUpdate(obParams, FEATURE_NAME, function(params) { - var id = common.crypto.createHash('sha1').update(params.qstring.app_id + params.qstring.args.crash_id + "").digest('hex'); - common.db.collection('crash_share').insert({_id: id, app_id: params.qstring.app_id + "", crash_id: params.qstring.args.crash_id + ""}, function() { - common.db.collection('app_crashgroups' + params.qstring.app_id).update({'_id': params.qstring.args.crash_id }, {"$set": {is_public: true}}, function() {}); - plugins.dispatch("/systemlogs", {params: params, action: "crash_shared", data: {app_id: params.qstring.app_id, crash_id: params.qstring.args.crash_id}}); + var crashId = params.qstring.args.crash_id + ""; + var id = common.crypto.createHash('sha1').update(params.qstring.app_id + crashId).digest('hex'); + common.db.collection('crash_share').insert({_id: id, app_id: params.qstring.app_id + "", crash_id: crashId}, function() { + common.db.collection('app_crashgroups' + params.qstring.app_id).update({'_id': crashId }, {"$set": {is_public: true}}, function() {}); + plugins.dispatch("/systemlogs", {params: params, action: "crash_shared", data: {app_id: params.qstring.app_id, crash_id: crashId}}); common.returnMessage(params, 200, 'Success'); return true; }); @@ -1481,10 +1480,11 @@ plugins.setConfigs("crashes", { return true; } validateUpdate(obParams, FEATURE_NAME, function(params) { - var id = common.crypto.createHash('sha1').update(params.qstring.app_id + params.qstring.args.crash_id + "").digest('hex'); + var crashId = params.qstring.args.crash_id + ""; + var id = common.crypto.createHash('sha1').update(params.qstring.app_id + crashId).digest('hex'); common.db.collection('crash_share').remove({'_id': id }, function() { - common.db.collection('app_crashgroups' + params.qstring.app_id).update({'_id': params.qstring.args.crash_id }, {"$set": {is_public: false}}, function() {}); - plugins.dispatch("/systemlogs", {params: params, action: "crash_unshared", data: {app_id: params.qstring.app_id, crash_id: params.qstring.args.crash_id}}); + common.db.collection('app_crashgroups' + params.qstring.app_id).update({'_id': crashId }, {"$set": {is_public: false}}, function() {}); + plugins.dispatch("/systemlogs", {params: params, action: "crash_unshared", data: {app_id: params.qstring.app_id, crash_id: crashId}}); common.returnMessage(params, 200, 'Success'); return true; }); @@ -1497,8 +1497,9 @@ plugins.setConfigs("crashes", { } validateUpdate(obParams, FEATURE_NAME, function(params) { if (params.qstring.args.data) { - common.db.collection('app_crashgroups' + params.qstring.app_id).update({'_id': params.qstring.args.crash_id }, {"$set": {share: params.qstring.args.data}}, function() { - plugins.dispatch("/systemlogs", {params: params, action: "crash_modify_share", data: {app_id: params.qstring.app_id, crash_id: params.qstring.args.crash_id, data: params.qstring.args.data}}); + var crashId = params.qstring.args.crash_id + ""; + common.db.collection('app_crashgroups' + params.qstring.app_id).update({'_id': crashId }, {"$set": {share: params.qstring.args.data}}, function() { + plugins.dispatch("/systemlogs", {params: params, action: "crash_modify_share", data: {app_id: params.qstring.app_id, crash_id: crashId, data: params.qstring.args.data}}); common.returnMessage(params, 200, 'Success'); return true; }); diff --git a/plugins/crashes/tests.js b/plugins/crashes/tests.js index 8a58b8b8def..b8419a09ccb 100644 --- a/plugins/crashes/tests.js +++ b/plugins/crashes/tests.js @@ -1364,6 +1364,25 @@ describe('Testing Crashes', function() { }); }); + describe('Mark crash as viewed', function() { + it('should success', function(done) { + var args = { + crash_id: CRASHES[2] + }; + request + .get('/i/crashes/view?args=' + JSON.stringify(args) + '&app_id=' + APP_ID + '&api_key=' + API_KEY_ADMIN) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var ob = JSON.parse(res.text); + ob.should.have.property('result', 'Success'); + setTimeout(done, 10 * testUtils.testScalingFactor); + }); + }); + }); + /* describe('Check public crash', function() { it('should be found', function(done) { diff --git a/plugins/dashboards/api/api.js b/plugins/dashboards/api/api.js index 45bb743a36f..b1f3e5ec42f 100644 --- a/plugins/dashboards/api/api.js +++ b/plugins/dashboards/api/api.js @@ -549,21 +549,33 @@ plugins.setConfigs("dashboards", { plugins.register("/o/dashboards/widget-layout", function(ob) { var params = ob.params; - validateUser(params, function() { + var dashboardId = params.qstring.dashboard_id; - var dashboardId = params.qstring.dashboard_id; + if (typeof dashboardId === "undefined" || dashboardId.length !== 24) { + common.returnMessage(params, 401, 'Invalid parameter: dashboard_id'); + return true; + } - common.db.collection("dashboards").findOne({_id: common.db.ObjectID(dashboardId)}, {widgets: 1}, function(err, dashboard) { + validateUser(params, function() { + common.db.collection("dashboards").findOne({_id: common.db.ObjectID(dashboardId)}, function(err, dashboard) { + //return the same access-denied shape whether the dashboard is + //missing or not viewable, so dashboard ids cannot be enumerated + //via response differences (matches /o/dashboards) if (err || !dashboard) { - //Error + return common.returnOutput(params, {error: true, dashboard_access_denied: true}); } - else { - common.db.collection("widgets").find({_id: {$in: dashboard.widgets}}, {_id: 1, position: 1, size: 1}).toArray(function(er, widgets) { + //the caller must be allowed to view this dashboard; without this + //any authenticated user could read another dashboard's layout + hasViewAccessToDashboard(params.member, dashboard, function(er, status) { + if (er || !status) { + return common.returnOutput(params, {error: true, dashboard_access_denied: true}); + } + var widgetIds = Array.isArray(dashboard.widgets) ? dashboard.widgets : []; + common.db.collection("widgets").find({_id: {$in: widgetIds}}, {_id: 1, position: 1, size: 1}).toArray(function(e, widgets) { common.returnOutput(params, widgets || []); }); - } + }); }); - }); return true; diff --git a/plugins/dashboards/frontend/public/javascripts/countly.widgets.note.js b/plugins/dashboards/frontend/public/javascripts/countly.widgets.note.js index 1d339c6b83f..ab62330dcc3 100644 --- a/plugins/dashboards/frontend/public/javascripts/countly.widgets.note.js +++ b/plugins/dashboards/frontend/public/javascripts/countly.widgets.note.js @@ -1,6 +1,66 @@ -/*global countlyVue, CV, countlyCommon, ElementTiptap */ +/*global countlyVue, CV, countlyCommon, ElementTiptap, filterXSS */ (function() { + // Note content is rich text (TipTap editor) and is rendered with v-html + // after unescapeHtml(), which undoes the API's output escaping. Sanitize + // it with an allowlist covering only the formatting the editor produces, + // so a note cannot inject scripts/handlers/unsafe tags that would execute + // when another user views the dashboard. + var NOTE_STYLE_TAGS = ["p", "div", "span", "h1", "h2", "h3", "h4", "h5", "h6", + "strong", "b", "em", "i", "u", "s", "strike", "mark", "sub", "sup", + "ul", "ol", "li", "blockquote", "pre", "code", "hr"]; + var noteWhiteList = { + a: ["href", "target", "class", "style", "title"], + br: [], + input: ["type", "checked", "disabled"], + label: ["class"] + }; + NOTE_STYLE_TAGS.forEach(function(tag) { + noteWhiteList[tag] = ["class", "style"]; + }); + // Allow the CSS properties the editor emits. js-xss ships a property + // allowlist that already permits color, background-color (highlight), + // font-family, font-size and text-align, but disallows line-height by + // default, so merge it back in (kept safe by the CSS value filter). + var noteCSSWhiteList = {}; + if (typeof filterXSS === "function" && typeof filterXSS.getDefaultCSSWhiteList === "function") { + noteCSSWhiteList = filterXSS.getDefaultCSSWhiteList(); + noteCSSWhiteList["line-height"] = true; + } + var noteXSSOptions = { + whiteList: noteWhiteList, + css: { whiteList: noteCSSWhiteList }, + stripIgnoreTag: true, + stripIgnoreTagBody: ["script", "style"], + onIgnoreTagAttr: function(tag, name, value) { + // preserve data-* attributes (used by the editor for e.g. todo + // lists); they are not script execution vectors + if (name.substr(0, 5) === "data-") { + return name + '="' + filterXSS.escapeAttrValue(value) + '"'; + } + }, + onTagAttr: function(tag, name, value) { + // href/src are left to the library's safeAttrValue, which permits + // http(s)/mailto/tel/relative/anchor links and blocks javascript: + // and other dangerous schemes - so links in notes keep working. + if (tag === "input" && name === "type" && value !== "checkbox") { + // todo lists only need checkboxes + return "type='checkbox'"; + } + } + }; + /** + * Sanitize note HTML content for safe rendering with v-html + * @param {string} html - raw (unescaped) note html + * @returns {string} sanitized html + */ + function sanitizeNoteHTML(html) { + if (typeof filterXSS === "function") { + return filterXSS(html, noteXSSOptions); + } + // if the sanitizer is unavailable, fail closed by escaping + return countlyCommon.encodeHtml(html); + } var WidgetComponent = countlyVue.views.create({ template: CV.T('/dashboards/templates/widgets/note/widget.html'), mixins: [countlyVue.mixins.customDashboards.global], @@ -19,7 +79,7 @@ widgetHTML: function() { var unescapedHTML = countlyCommon.unescapeHtml(this.data.contenthtml); unescapedHTML = unescapedHTML.replace(/]*><\/p>/g, '
'); - return unescapedHTML; + return sanitizeNoteHTML(unescapedHTML); } }, }); diff --git a/plugins/dashboards/tests.js b/plugins/dashboards/tests.js index c37265f71cf..0076b02d062 100644 --- a/plugins/dashboards/tests.js +++ b/plugins/dashboards/tests.js @@ -1,1153 +1,93 @@ var request = require('supertest'); var should = require('should'); -var testUtils = require("../../test/testUtils"); -var Promise = require("bluebird"); -request = request(testUtils.url); - -// Sample dashboard and widget configurations based on OpenAPI schema -const baseDashboard = { - "name": "Test Dashboard", - "share_with": "none", // Use enum value from OpenAPI spec - "widgets": [] -}; - -const sampleWidget = { - "widget_type": "analytics", - "data_type": "session", - "apps": [], - "configuration": { - "metrics": ["t", "n", "u"], - "period": "30days" - }, - "dimensions": { - "width": 6, - "height": 4, - "position": 1 - } -}; - -// Store created resources for later use and cleanup -const createdResources = { - dashboards: [], - widgets: [] -}; - -function getRequestURL(path) { - const API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); - return path + `?api_key=${API_KEY_ADMIN}`; -} - -// Helper function to log API response details for debugging -function logApiResponse(method, endpoint, requestBody, response) { - console.log(`\n🔍 API ${method} ${endpoint} RESPONSE DETAILS:`); - console.log(`📤 Request body: ${JSON.stringify(requestBody || {})}`); - console.log(`📥 Response status: ${response.status}`); - console.log(`📥 Response body: ${JSON.stringify(response.body || {})}`); - console.log(`📥 Response headers: ${JSON.stringify(response.headers || {})}`); - console.log('\n'); -} - -// Schema validation functions based on OpenAPI spec -function validateDashboardObject(dashboard) { - dashboard.should.have.property('_id').which.is.a.String(); - dashboard.should.have.property('name').which.is.a.String(); - dashboard.should.have.property('widgets').which.is.an.Array(); - - // The actual API uses owner_id instead of owner, and owner can be object or string - if (dashboard.owner !== undefined) { - // Owner can be either string ID or object with user details - if (typeof dashboard.owner === 'string') { - dashboard.owner.should.be.a.String(); - } - else { - dashboard.owner.should.be.an.Object(); - dashboard.owner.should.have.property('_id'); - } - } - if (dashboard.owner_id !== undefined) { - dashboard.owner_id.should.be.a.String(); - } - - if (dashboard.created_at !== undefined) { - dashboard.created_at.should.be.a.Number(); - } - - if (dashboard.last_modified !== undefined) { - dashboard.last_modified.should.be.a.Number(); - } -} - -function validateWidgetObject(widget) { - widget.should.have.property('_id').which.is.a.String(); - - if (widget.widget_type !== undefined) { - widget.widget_type.should.be.a.String(); - } - - if (widget.data_type !== undefined) { - widget.data_type.should.be.a.String(); - } - - if (widget.apps !== undefined) { - widget.apps.should.be.an.Array(); - } - - if (widget.configuration !== undefined) { - widget.configuration.should.be.an.Object(); - } - - if (widget.dimensions !== undefined) { - widget.dimensions.should.be.an.Object(); - } -} - -describe('Testing Dashboard API against OpenAPI Specification', function() { - describe('0. Setup - Create test resources', function() { - it('should create initial test dashboard for other tests', function(done) { - const APP_ID = testUtils.get("APP_ID"); - const setupDashboard = { - name: "Setup Test Dashboard", - share_with: "none" - }; - - request - .get(getRequestURL('/i/dashboards/create') + - `&name=${encodeURIComponent(setupDashboard.name)}&share_with=${setupDashboard.share_with}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/create', setupDashboard, res); - return done(err); - } - - should.exist(res.body); - createdResources.dashboards.push(res.body); - console.log(`✅ Setup dashboard created with ID: ${res.body}`); - done(); - }); - }); - }); - - describe('1. /o/dashboards - Get specific dashboard', function() { - it('should retrieve a specific dashboard with widgets and app info', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboard created for test")); - } - - const dashboardId = createdResources.dashboards[0]; - request - .get(getRequestURL('/o/dashboards') + `&dashboard_id=${dashboardId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards', null, res); - return done(err); - } - - // Validate response schema according to OpenAPI spec - res.body.should.have.property('widgets').which.is.an.Array(); - res.body.should.have.property('apps').which.is.an.Array(); - - if (res.body.is_owner !== undefined) { - res.body.is_owner.should.be.a.Boolean(); - } - if (res.body.is_editable !== undefined) { - res.body.is_editable.should.be.a.Boolean(); - } - if (res.body.owner !== undefined) { - res.body.owner.should.be.an.Object(); - } - - console.log(`✅ Dashboard retrieved successfully with ${res.body.widgets.length} widgets`); - done(); - }); - }); - - it('should fail with invalid dashboard_id', function(done) { - request - .get(getRequestURL('/o/dashboards') + '&dashboard_id=invalid') - .expect(401) - .end(function(err, res) { - if (err && err.status !== 401) { - logApiResponse('GET', '/o/dashboards', null, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/invalid.*dashboard_id/i); - done(); - }); - }); - - it('should support period and action parameters', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboard created for test")); - } - - const dashboardId = createdResources.dashboards[0]; - request - .get(getRequestURL('/o/dashboards') + - `&dashboard_id=${dashboardId}&period=30days&action=refresh`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards', null, res); - return done(err); - } - - res.body.should.have.property('widgets').which.is.an.Array(); - res.body.should.have.property('apps').which.is.an.Array(); - done(); - }); - }); - }); - - describe('2. /o/dashboards/widget - Get widget info', function() { - let testWidgetId; - - before(function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboard available for widget test")); - } - - // Create a test widget first - const APP_ID = testUtils.get("APP_ID"); - const dashboardId = createdResources.dashboards[0]; - const widgetData = JSON.stringify({ - widget_type: "analytics", - apps: [APP_ID], - data_type: "sessions" +var testUtils = require('../../test/testUtils'); +request = request.agent(testUtils.url); + +// Regression test: /o/dashboards/widget-layout must enforce dashboard view +// access, so an authenticated user cannot read the layout of a private +// dashboard they have no access to. + +describe('Testing dashboards widget-layout access control', function() { + var API_KEY_ADMIN = ""; + var dashboardId = ""; + var otherApiKey = ""; + var otherUserId = ""; + var uniq = Date.now(); + + it('should create a private dashboard as admin', function(done) { + API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); + request + .get('/i/dashboards/create?api_key=' + API_KEY_ADMIN + '&name=PrivateDash&share_with=none') + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + dashboardId = JSON.parse(res.text); + should.exist(dashboardId); + done(); }); - - request - .get(getRequestURL('/i/dashboards/add-widget') + - `&dashboard_id=${dashboardId}&widget=${encodeURIComponent(widgetData)}`) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - testWidgetId = res.body; - createdResources.widgets.push({dashboardId, widgetId: testWidgetId}); - done(); - }); - }); - - it('should retrieve widget data for valid dashboard and widget combination', function(done) { - if (!testWidgetId || createdResources.dashboards.length === 0) { - return done(new Error("No widget or dashboard available for test")); - } - - const dashboardId = createdResources.dashboards[0]; - request - .get(getRequestURL('/o/dashboards/widget') + - `&dashboard_id=${dashboardId}&widget_id=${testWidgetId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards/widget', null, res); - return done(err); - } - - res.body.should.be.an.Object(); - console.log(`✅ Widget data retrieved successfully`); - done(); - }); - }); - - it('should fail with invalid dashboard_id', function(done) { - if (!testWidgetId) { - return done(new Error("No widget available for test")); - } - - request - .get(getRequestURL('/o/dashboards/widget') + - `&dashboard_id=invalid&widget_id=${testWidgetId}`) - .expect(401) - .end(function(err, res) { - if (err && err.status !== 401) { - logApiResponse('GET', '/o/dashboards/widget', null, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/invalid.*dashboard_id/i); - done(); - }); - }); - - it('should fail with invalid widget_id', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboard available for test")); - } - - const dashboardId = createdResources.dashboards[0]; - request - .get(getRequestURL('/o/dashboards/widget') + - `&dashboard_id=${dashboardId}&widget_id=invalid`) - .expect(401) - .end(function(err, res) { - if (err && err.status !== 401) { - logApiResponse('GET', '/o/dashboards/widget', null, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/invalid.*widget_id/i); - done(); - }); - }); }); - describe('3. /o/dashboards/test - Test widgets', function() { - it('should test widget configurations and return data', function(done) { - const APP_ID = testUtils.get("APP_ID"); - const testWidgets = JSON.stringify([ - { - widget_type: "analytics", - apps: [APP_ID], - data_type: "sessions" + it('should create a separate non-admin user', function(done) { + var userParams = { + full_name: "dashoutsider" + uniq, + username: "dashoutsider" + uniq, + password: "p4ssw0rD!", + email: "dashoutsider" + uniq + "@mail.test", + permission: { _: { a: [], u: [] }, c: {}, r: {}, u: {}, d: {} } + }; + request + .get('/i/users/create?api_key=' + API_KEY_ADMIN + '&args=' + encodeURIComponent(JSON.stringify(userParams))) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); } - ]); - - request - .get(getRequestURL('/o/dashboards/test') + - `&widgets=${encodeURIComponent(testWidgets)}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards/test', null, res); - return done(err); - } - - res.body.should.be.an.Object(); - console.log(`✅ Widget test completed successfully`); - done(); - }); - }); - - it('should handle empty widgets parameter', function(done) { - request - .get(getRequestURL('/o/dashboards/test') + '&widgets=[]') - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards/test', null, res); - return done(err); - } - - res.body.should.be.an.Object(); - done(); - }); - }); - }); - - describe('4. /o/dashboards/widget-layout - Get widget layout', function() { - it('should retrieve widget layout information', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboard available for test")); - } - - const dashboardId = createdResources.dashboards[0]; - request - .get(getRequestURL('/o/dashboards/widget-layout') + - `&dashboard_id=${dashboardId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards/widget-layout', null, res); - return done(err); - } - - res.body.should.be.an.Array(); - - // Validate each widget layout object - res.body.forEach(widget => { - widget.should.have.property('_id').which.is.a.String(); - if (widget.position !== undefined) { - widget.position.should.be.an.Array(); - } - if (widget.size !== undefined) { - widget.size.should.be.an.Array(); - } - }); - - console.log(`✅ Widget layout retrieved with ${res.body.length} widgets`); - done(); - }); - }); - }); - - describe('5. /o/dashboard/data - Get dashboard data', function() { - it('should retrieve data for a specific widget in dashboard', function(done) { - if (createdResources.dashboards.length === 0 || createdResources.widgets.length === 0) { - return done(new Error("No dashboard or widget available for test")); - } - - const dashboardId = createdResources.dashboards[0]; - const widgetId = createdResources.widgets[0].widgetId; - - request - .get(getRequestURL('/o/dashboard/data') + - `&dashboard_id=${dashboardId}&widget_id=${widgetId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboard/data', null, res); - return done(err); - } - - res.body.should.be.an.Object(); - console.log(`✅ Dashboard data retrieved successfully`); - done(); - }); - }); - - it('should fail with missing dashboard_id', function(done) { - if (createdResources.widgets.length === 0) { - return done(new Error("No widget available for test")); - } - - const widgetId = createdResources.widgets[0].widgetId; - request - .get(getRequestURL('/o/dashboard/data') + `&widget_id=${widgetId}`) - .expect(401) - .end(function(err, res) { - if (err && err.status !== 401) { - logApiResponse('GET', '/o/dashboard/data', null, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/invalid.*dashboard_id/i); - done(); - }); - }); - - it('should fail with missing widget_id', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboard available for test")); - } - - const dashboardId = createdResources.dashboards[0]; - request - .get(getRequestURL('/o/dashboard/data') + `&dashboard_id=${dashboardId}`) - .expect(401) - .end(function(err, res) { - if (err && err.status !== 401) { - logApiResponse('GET', '/o/dashboard/data', null, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/invalid.*widget_id/i); - done(); - }); - }); - }); - - describe('6. /o/dashboards/all - Get all dashboards', function() { - it('should retrieve all dashboards with correct schema', function(done) { - request - .get(getRequestURL('/o/dashboards/all')) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards/all', null, res); - return done(err); - } - - res.body.should.be.an.Array(); - - // Verify each dashboard has required properties - if (res.body.length > 0) { - res.body.forEach(validateDashboardObject); - console.log(`✅ Retrieved ${res.body.length} dashboards`); - } - - done(); - }); - }); - - it('should support just_schema parameter', function(done) { - request - .get(getRequestURL('/o/dashboards/all') + '&just_schema=true') - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards/all', null, res); - return done(err); - } - - res.body.should.be.an.Array(); - - // With just_schema, should only return basic info - if (res.body.length > 0) { - res.body.forEach(dashboard => { - dashboard.should.have.property('_id').which.is.a.String(); - dashboard.should.have.property('name').which.is.a.String(); - }); - } - - done(); - }); - }); - - it('should fail without authentication', function(done) { - request - .get('/o/dashboards/all') - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - logApiResponse('GET', '/o/dashboards/all', null, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/missing.*api_key.*auth_token/i); - done(); - }); - }); - }); - describe('7. /i/dashboards/create - Create Dashboard', function() { - it('should create a new dashboard with all required parameters using GET', function(done) { - const dashboardName = "Create Test Dashboard"; - const shareWith = "none"; - - request - .get(getRequestURL('/i/dashboards/create') + - `&name=${encodeURIComponent(dashboardName)}&share_with=${shareWith}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/create', {name: dashboardName, share_with: shareWith}, res); - return done(err); - } - - // According to OpenAPI spec, should return dashboard ID as string - should.exist(res.body); - res.body.should.be.a.String(); - - // Store the created dashboard ID for later tests - createdResources.dashboards.push(res.body); - - console.log(`✅ Dashboard created successfully with ID: ${res.body}`); - done(); - }); - }); - - it('should create dashboard with sharing parameters', function(done) { - const dashboardName = "Shared Dashboard Test"; - const shareWith = "selected-users"; - const sharedEmailEdit = JSON.stringify(["test@example.com"]); - const sharedEmailView = JSON.stringify(["viewer@example.com"]); - - request - .get(getRequestURL('/i/dashboards/create') + - `&name=${encodeURIComponent(dashboardName)}&share_with=${shareWith}` + - `&shared_email_edit=${encodeURIComponent(sharedEmailEdit)}` + - `&shared_email_view=${encodeURIComponent(sharedEmailView)}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/create', { - name: dashboardName, - share_with: shareWith, - shared_email_edit: sharedEmailEdit, - shared_email_view: sharedEmailView - }, res); - return done(err); - } - - should.exist(res.body); - res.body.should.be.a.String(); - createdResources.dashboards.push(res.body); - console.log(`✅ Shared dashboard created successfully with ID: ${res.body}`); - done(); - }); - }); - - it('should create dashboard with refresh rate', function(done) { - const dashboardName = "Dashboard with Refresh"; - const shareWith = "none"; - const useRefreshRate = "true"; - const refreshRate = 10; // minutes - - request - .get(getRequestURL('/i/dashboards/create') + - `&name=${encodeURIComponent(dashboardName)}&share_with=${shareWith}` + - `&use_refresh_rate=${useRefreshRate}&refreshRate=${refreshRate}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/create', { - name: dashboardName, - share_with: shareWith, - use_refresh_rate: useRefreshRate, - refreshRate: refreshRate - }, res); - return done(err); - } - - should.exist(res.body); - res.body.should.be.a.String(); - createdResources.dashboards.push(res.body); - console.log(`✅ Dashboard with refresh rate created successfully with ID: ${res.body}`); - done(); - }); - }); - - it('should fail when missing required name parameter', function(done) { - request - .get(getRequestURL('/i/dashboards/create') + '&share_with=none') - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - logApiResponse('GET', '/i/dashboards/create', {share_with: 'none'}, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/missing.*name/i); - done(); - }); - }); - - it('should fail when missing required share_with parameter', function(done) { - request - .get(getRequestURL('/i/dashboards/create') + '&name=Test Dashboard') - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - logApiResponse('GET', '/i/dashboards/create', {name: 'Test Dashboard'}, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/missing.*share_with/i); - done(); - }); - }); - }); - - describe('8. /i/dashboards/update - Update Dashboard', function() { - it('should update an existing dashboard', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboards created for update test")); - } - - const dashboardId = createdResources.dashboards[0]; - const newName = "Updated Dashboard Name"; - const shareWith = "all-users"; - - request - .get(getRequestURL('/i/dashboards/update') + - `&dashboard_id=${dashboardId}&name=${encodeURIComponent(newName)}&share_with=${shareWith}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/update', { - dashboard_id: dashboardId, - name: newName, - share_with: shareWith - }, res); - return done(err); - } - - res.body.should.be.an.Object(); - // Dashboard update returns MongoDB result object - if (res.body.result && res.body.result === 'Success') { - res.body.should.have.property('result', 'Success'); - } - else if (res.body.acknowledged !== undefined) { - // MongoDB update result format - res.body.should.have.property('acknowledged', true); - res.body.should.have.property('modifiedCount'); - } - console.log(`✅ Dashboard updated successfully`); - done(); - }); - }); - - it('should fail with invalid dashboard_id', function(done) { - const invalidId = "507f1f77bcf86cd799439011"; - const newName = "Should Not Update"; - - request - .get(getRequestURL('/i/dashboards/update') + - `&dashboard_id=${invalidId}&name=${encodeURIComponent(newName)}&share_with=none`) - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - logApiResponse('GET', '/i/dashboards/update', { - dashboard_id: invalidId, - name: newName, - share_with: 'none' - }, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/dashboard.*doesn.*exist|invalid.*dashboard/i); - done(); - }); - }); - }); - - describe('9. /i/dashboards/add-widget - Add Widget', function() { - it('should add a widget to an existing dashboard', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboards created for widget test")); - } - - const APP_ID = testUtils.get("APP_ID"); - const dashboardId = createdResources.dashboards[0]; - const widgetData = JSON.stringify({ - widget_type: "analytics", - apps: [APP_ID], - data_type: "sessions", - position: [0, 0], - size: [4, 3] + otherApiKey = res.body.api_key; + otherUserId = res.body._id; + should.exist(otherApiKey); + done(); }); - - request - .get(getRequestURL('/i/dashboards/add-widget') + - `&dashboard_id=${dashboardId}&widget=${encodeURIComponent(widgetData)}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/add-widget', { - dashboard_id: dashboardId, - widget: widgetData - }, res); - return done(err); - } - - // Should return widget ID - should.exist(res.body); - res.body.should.be.a.String(); - - createdResources.widgets.push({ - dashboardId: dashboardId, - widgetId: res.body - }); - - console.log(`✅ Widget added successfully with ID: ${res.body}`); - done(); - }); - }); - - it('should fail with invalid dashboard_id', function(done) { - const invalidId = "507f1f77bcf86cd799439011"; - const widgetData = JSON.stringify({widget_type: "analytics"}); - - request - .get(getRequestURL('/i/dashboards/add-widget') + - `&dashboard_id=${invalidId}&widget=${encodeURIComponent(widgetData)}`) - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - logApiResponse('GET', '/i/dashboards/add-widget', { - dashboard_id: invalidId, - widget: widgetData - }, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/invalid.*parameter.*widget|invalid.*dashboard/i); - done(); - }); - }); }); - describe('10. /i/dashboards/update-widget - Update Widget', function() { - it('should update an existing widget', function(done) { - if (createdResources.widgets.length === 0) { - return done(new Error("No widgets created for update test")); - } - - const widget = createdResources.widgets[0]; - const updatedWidgetData = JSON.stringify({ - widget_type: "analytics", - data_type: "users", - title: "Updated Widget Title" + it('should deny widget-layout for a user without dashboard access', function(done) { + request + .get('/o/dashboards/widget-layout?api_key=' + otherApiKey + '&dashboard_id=' + dashboardId) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var ob = JSON.parse(res.text); + ob.should.have.property('dashboard_access_denied', true); + done(); }); - - request - .get(getRequestURL('/i/dashboards/update-widget') + - `&dashboard_id=${widget.dashboardId}&widget_id=${widget.widgetId}` + - `&widget=${encodeURIComponent(updatedWidgetData)}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/update-widget', { - dashboard_id: widget.dashboardId, - widget_id: widget.widgetId, - widget: updatedWidgetData - }, res); - return done(err); - } - - res.body.should.have.property('result', 'Success'); - console.log(`✅ Widget updated successfully`); - done(); - }); - }); - - it('should fail with invalid widget_id', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboards available for test")); - } - - const dashboardId = createdResources.dashboards[0]; - const invalidWidgetId = "507f1f77bcf86cd799439011"; - const widgetData = JSON.stringify({widget_type: "analytics"}); - - request - .get(getRequestURL('/i/dashboards/update-widget') + - `&dashboard_id=${dashboardId}&widget_id=${invalidWidgetId}` + - `&widget=${encodeURIComponent(widgetData)}`) - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - logApiResponse('GET', '/i/dashboards/update-widget', { - dashboard_id: dashboardId, - widget_id: invalidWidgetId, - widget: widgetData - }, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/dashboard.*widget.*combination.*not.*exist/i); - done(); - }); - }); }); - describe('11. /i/dashboards/remove-widget - Remove Widget', function() { - it('should remove an existing widget', function(done) { - if (createdResources.widgets.length === 0) { - return done(new Error("No widgets created for removal test")); - } - - const widget = createdResources.widgets[0]; - - request - .get(getRequestURL('/i/dashboards/remove-widget') + - `&dashboard_id=${widget.dashboardId}&widget_id=${widget.widgetId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/remove-widget', { - dashboard_id: widget.dashboardId, - widget_id: widget.widgetId - }, res); - return done(err); - } - - res.body.should.have.property('result', 'Success'); - - // Remove from our tracking array - createdResources.widgets.splice(0, 1); - - console.log(`✅ Widget removed successfully`); - done(); - }); - }); - - it('should fail with invalid widget_id', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboards available for test")); - } - - const dashboardId = createdResources.dashboards[0]; - const invalidWidgetId = "507f1f77bcf86cd799439011"; - - request - .get(getRequestURL('/i/dashboards/remove-widget') + - `&dashboard_id=${dashboardId}&widget_id=${invalidWidgetId}`) - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - logApiResponse('GET', '/i/dashboards/remove-widget', { - dashboard_id: dashboardId, - widget_id: invalidWidgetId - }, res); - return done(err); - } - - res.body.should.have.property('result'); - res.body.result.should.match(/dashboard.*widget.*combination.*not.*exist/i); - done(); - }); - }); - }); - - describe('12. /i/dashboards/delete - Delete Dashboard', function() { - it('should delete an existing dashboard', function(done) { - if (createdResources.dashboards.length === 0) { - return done(new Error("No dashboards created for deletion test")); - } - - // Delete the last dashboard to keep others for remaining tests - const dashboardIdToDelete = createdResources.dashboards[createdResources.dashboards.length - 1]; - - request - .get(getRequestURL('/i/dashboards/delete') + `&dashboard_id=${dashboardIdToDelete}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/delete', {dashboard_id: dashboardIdToDelete}, res); - return done(err); - } - - res.body.should.be.an.Object(); - // Dashboard delete returns MongoDB result object - if (res.body.result && res.body.result === 'Success') { - res.body.should.have.property('result', 'Success'); - } - else if (res.body.acknowledged !== undefined) { - // MongoDB delete result format - res.body.should.have.property('acknowledged', true); - res.body.should.have.property('deletedCount'); - } - - // Remove from our tracking array - createdResources.dashboards.splice( - createdResources.dashboards.indexOf(dashboardIdToDelete), - 1 - ); - - console.log(`✅ Dashboard deleted successfully`); - done(); - }); - }); - - it('should handle non-existent dashboard ID', function(done) { - const nonExistentId = "507f1f77bcf86cd799439011"; - - request - .get(getRequestURL('/i/dashboards/delete') + `&dashboard_id=${nonExistentId}`) - .expect(400) - .end(function(err, res) { - if (err && err.status !== 400) { - // Some APIs might return 200 for idempotent delete - if (res.status === 200) { - return done(); - } - logApiResponse('GET', '/i/dashboards/delete', {dashboard_id: nonExistentId}, res); - return done(err); - } - - if (res.body && res.body.result) { - res.body.result.should.match(/dashboard.*doesn.*exist|not.*found/i); - } - done(); - }); - }); - }); - - describe('13. End-to-End Workflow Test', function() { - let workflowDashboardId; - let workflowWidgetId; - - it('should successfully execute complete dashboard lifecycle', function(done) { - const APP_ID = testUtils.get("APP_ID"); - - // Step 1: Create a new dashboard - const dashboardName = "Workflow Test Dashboard"; - request - .get(getRequestURL('/i/dashboards/create') + - `&name=${encodeURIComponent(dashboardName)}&share_with=none`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/create', {name: dashboardName, share_with: 'none'}, res); - return done(err); - } - - should.exist(res.body); - workflowDashboardId = res.body; - createdResources.dashboards.push(workflowDashboardId); - - // Step 2: Add a widget to the dashboard - const widgetData = JSON.stringify({ - widget_type: "analytics", - apps: [APP_ID], - data_type: "sessions" - }); - - request - .get(getRequestURL('/i/dashboards/add-widget') + - `&dashboard_id=${workflowDashboardId}&widget=${encodeURIComponent(widgetData)}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/add-widget', { - dashboard_id: workflowDashboardId, - widget: widgetData - }, res); - return done(err); - } - - workflowWidgetId = res.body; - createdResources.widgets.push({ - dashboardId: workflowDashboardId, - widgetId: workflowWidgetId - }); - - // Step 3: Get the dashboard data - request - .get(getRequestURL('/o/dashboard/data') + - `&dashboard_id=${workflowDashboardId}&widget_id=${workflowWidgetId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboard/data', null, res); - return done(err); - } - - res.body.should.be.an.Object(); - - // Step 4: Update the dashboard - const updatedName = "Updated Workflow Dashboard"; - request - .get(getRequestURL('/i/dashboards/update') + - `&dashboard_id=${workflowDashboardId}&name=${encodeURIComponent(updatedName)}&share_with=none`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/update', { - dashboard_id: workflowDashboardId, - name: updatedName, - share_with: 'none' - }, res); - return done(err); - } - - res.body.should.be.an.Object(); - // Dashboard update returns MongoDB result object - if (res.body.result && res.body.result === 'Success') { - res.body.should.have.property('result', 'Success'); - } - else if (res.body.acknowledged !== undefined) { - // MongoDB update result format - res.body.should.have.property('acknowledged', true); - res.body.should.have.property('modifiedCount'); - } - - // Step 5: Verify the update by getting all dashboards - request - .get(getRequestURL('/o/dashboards/all')) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/o/dashboards/all', null, res); - return done(err); - } - - const updatedDashboard = res.body.find(d => d._id === workflowDashboardId); - should.exist(updatedDashboard); - updatedDashboard.should.have.property('name', updatedName); - - // Step 6: Remove the widget - request - .get(getRequestURL('/i/dashboards/remove-widget') + - `&dashboard_id=${workflowDashboardId}&widget_id=${workflowWidgetId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/remove-widget', { - dashboard_id: workflowDashboardId, - widget_id: workflowWidgetId - }, res); - return done(err); - } - - res.body.should.have.property('result', 'Success'); - - // Step 7: Delete the dashboard - request - .get(getRequestURL('/i/dashboards/delete') + - `&dashboard_id=${workflowDashboardId}`) - .expect(200) - .end(function(err, res) { - if (err) { - logApiResponse('GET', '/i/dashboards/delete', { - dashboard_id: workflowDashboardId - }, res); - return done(err); - } - - res.body.should.be.an.Object(); - // Dashboard delete returns MongoDB result object - if (res.body.result && res.body.result === 'Success') { - res.body.should.have.property('result', 'Success'); - } - else if (res.body.acknowledged !== undefined) { - // MongoDB delete result format - res.body.should.have.property('acknowledged', true); - res.body.should.have.property('deletedCount'); - } - - // Remove from tracking arrays - const dashboardIndex = createdResources.dashboards.indexOf(workflowDashboardId); - if (dashboardIndex >= 0) { - createdResources.dashboards.splice(dashboardIndex, 1); - } - - const widgetIndex = createdResources.widgets.findIndex(w => - w.widgetId === workflowWidgetId); - if (widgetIndex >= 0) { - createdResources.widgets.splice(widgetIndex, 1); - } - - console.log(`✅ Complete workflow test passed successfully`); - done(); - }); - }); - }); - }); - }); - }); - }); - }); + it('should allow widget-layout for the dashboard owner', function(done) { + request + .get('/o/dashboards/widget-layout?api_key=' + API_KEY_ADMIN + '&dashboard_id=' + dashboardId) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var ob = JSON.parse(res.text); + Array.isArray(ob).should.eql(true); + done(); + }); }); - // Clean up all created resources after all tests after(function(done) { - if (createdResources.dashboards.length === 0) { - return done(); - } - - console.log(`\n🧹 Cleaning up ${createdResources.dashboards.length} test dashboards...`); - - // Delete all dashboards created during testing - const deletePromises = createdResources.dashboards.map(dashboardId => { - return new Promise((resolve) => { - console.log(` - Deleting dashboard ${dashboardId}`); + request + .get('/i/dashboards/delete?api_key=' + API_KEY_ADMIN + '&dashboard_id=' + dashboardId) + .end(function() { request - .get(getRequestURL('/i/dashboards/delete') + `&dashboard_id=${dashboardId}`) - .end(function(err, res) { - if (err) { - console.log(`❌ Error deleting dashboard ${dashboardId}: ${err.message}`); - console.log(` Response details: ${JSON.stringify(res.body || {})}`); - } - else { - console.log(` ✓ Dashboard ${dashboardId} deleted`); - } - resolve(); + .get('/i/users/delete?api_key=' + API_KEY_ADMIN + '&args=' + encodeURIComponent(JSON.stringify({user_ids: [otherUserId]}))) + .end(function() { + done(); }); }); - }); - - Promise.all(deletePromises) - .then(() => { - console.log(`✅ Cleanup completed`); - done(); - }) - .catch(done); }); -}); \ No newline at end of file +}); diff --git a/plugins/data-manager/frontend/public/images/upload-icon.svg b/plugins/data-manager/frontend/public/images/data-manager/upload-icon.svg similarity index 100% rename from plugins/data-manager/frontend/public/images/upload-icon.svg rename to plugins/data-manager/frontend/public/images/data-manager/upload-icon.svg diff --git a/plugins/data-manager/frontend/public/localization/data-manager.properties b/plugins/data-manager/frontend/public/localization/data-manager.properties index c8fc93ed2ea..4146c830488 100644 --- a/plugins/data-manager/frontend/public/localization/data-manager.properties +++ b/plugins/data-manager/frontend/public/localization/data-manager.properties @@ -278,6 +278,9 @@ data-manager.category-name = Category name data-manager.add-category = Add Category data-manager.back-to-manage-properties = Back to Manage Properties data-manager.property-details = Property Details +data-manager.property-values-limit-before = Up to +data-manager.property-values-limit-after = values are shown by default. Adjust the limit in Settings to view more. +data-manager.property-values-total-label = total data-manager.edit-property = Edit Property data-manager.delete-property-permanent = Delete property permanently? data-manager.select-data-type = Select Data Type diff --git a/plugins/data-manager/frontend/public/stylesheets/main.scss b/plugins/data-manager/frontend/public/stylesheets/main.scss index 060ef8e6661..8c5801e428c 100644 --- a/plugins/data-manager/frontend/public/stylesheets/main.scss +++ b/plugins/data-manager/frontend/public/stylesheets/main.scss @@ -217,7 +217,7 @@ margin-bottom: 10px; } .dz-filename::before{ - content: url('/data-manager/images/upload-icon.svg'); + content: url("../images/data-manager/upload-icon.svg"); vertical-align: middle; display: inline-block; margin-right: 8px; diff --git a/plugins/data_migration/.gitignore b/plugins/data_migration/.gitignore deleted file mode 100644 index c05fc40a256..00000000000 --- a/plugins/data_migration/.gitignore +++ /dev/null @@ -1,2 +0,0 @@ -export -import \ No newline at end of file diff --git a/plugins/data_migration/api/api.js b/plugins/data_migration/api/api.js deleted file mode 100644 index fc20d1b6b49..00000000000 --- a/plugins/data_migration/api/api.js +++ /dev/null @@ -1,1023 +0,0 @@ -var pluginOb = {}, - common = require('../../../api/utils/common.js'), - log = common.log('datamigration:api'), - plugins = require('../../pluginManager.js'), - migration_helper = require("./data_migration_helper.js"); -const fs = require('fs'); -const fse = require('fs-extra'); -var path = require('path'); -var cp = require('child_process'); //call process -const { validateCreate, validateRead, validateUpdate, validateDelete } = require('../../../api/utils/rights.js'); -var NginxConfFile = ""; -try { - NginxConfFile = require('nginx-conf').NginxConfFile; -} -catch (e) { - log.e("nginx-conf not installed"); -} - -var Promise = require("bluebird"); - -var authorize = require('../../../api/utils/authorizer.js'); //for token - -const request = require('countly-request')(plugins.getConfig("security")); -const FEATURE_NAME = 'data_migration'; - -/** - * Validate a data migration id before using it as a path segment. - * @param {string} exportid - export or import id - * @returns {string|null} safe id or null - */ -function safeDataMigrationId(exportid) { - exportid = exportid + ""; - if (exportid && common.sanitizeFilename(exportid) === exportid) { - return exportid; - } - return null; -} - -/** -*Function to delete all exported files in export folder -* @returns {Promise} Promise -*/ -function delete_all_exports() { - return new Promise(function(resolve, reject) { - if (fs.existsSync(__dirname + '/../export')) { - fse.remove(__dirname + '/../export', err => { - if (err) { - reject(Error('Unable to remove directory')); - } - else { - resolve(); - } - }); - } - else { - resolve(); - } - }); -} -/** -*Function to update progress on export. Used when receiving data about import on other server. -* @param {string} my_exportid - export id -* @param {string} step - export step -* @param {string} status - export status -* @param {integer} dif - dif from previous(used to track export progress) -* @param {string} reason - if failed - error message -* @param {boolean} reset_progress - states if need to reset progress variable -* @param {object} more_fields - more info to save -* @param {object} myparams - request parameters -*/ -function update_progress(my_exportid, step, status, dif, reason, reset_progress, more_fields, myparams) { - var data_migrator = new migration_helper(common.db); - data_migrator.update_progress(my_exportid, step, status, dif, reason, reset_progress, more_fields, myparams); -} - -/** -*Function applies redirect to apps -* @param {array} apps - array of app id -* @param {object} params - request params -* @param {string} my_redirect_url - url to redirect to -* @param {string} userid - user id -* @param {string} email - user email -* @returns {Promise} Promise -*/ -function apply_redirect_to_apps(apps, params, my_redirect_url, userid, email) { - return new Promise(function(resolve) { - if (!my_redirect_url || my_redirect_url === "") { - resolve(); - } - else { - var object_array = []; - for (var i = 0; i < apps.length; i++) { - object_array.push(common.db.ObjectID(apps[i])); - } - - common.db.collection("apps").update({_id: {$in: object_array}}, {$set: {redirect_url: my_redirect_url}}, {upsert: true, multi: true}, function(err) { - if (err) { - resolve(err); - } - else { - plugins.dispatch("/systemlogs", {params: {req: JSON.parse(params)}, user: {_id: userid, email: email}, action: "app_redirected", data: {app_id: apps, redirect_url: my_redirect_url}}); - } - resolve(); - }); - } - }); -} -/** -*Function fixes given address - trims slashes -* @param {string} address - address to fix -* @returns {string} fixed address -*/ -function trim_ending_slashes(address) { - while (address.length > 0 && address[address.length - 1] === '/') { - address = address.substring(0, address.length - 1); - } - return address; -} - - -//update_progress -//apply_redirect_to_apps -(function() { - - plugins.register("/permissions/features", function(ob) { - ob.features.push(FEATURE_NAME); - }); - - //report import status from remote server - plugins.register("/i/datamigration/report_import", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - - if (params.qstring) { - if (!params.qstring.exportid) { - common.returnMessage(params, 404, 'data-migration.exportid_not_provided'); - return; - } - if (!params.qstring.token) { - common.returnMessage(params, 404, 'data-migration.token_missing'); - return; - } - - common.db.collection("data_migrations").findOne({_id: params.qstring.exportid, server_token: params.qstring.token}, function(err, res) { - if (err) { - common.returnMessage(params, 404, err); - } - else { - if (res) { - if (params.qstring.status && params.qstring.status !== "") { - if (params.qstring.status === 'finished') { - update_progress(params.qstring.exportid, "importing", "finished", 0, "", true, {}, params); - if (res.server_address && res.server_address.length > 0 && res.redirect_traffic && res.redirect_traffic === true) { - //remove trailing slash - while (res.server_address.length > 0 && res.server_address[res.server_address.length - 1] === '/') { - res.server_address = res.server_address.substring(0, res.server_address.length - 1); - } - apply_redirect_to_apps(res.apps, res.myreq, res.server_address, res.userid, res.email).then( - function() {}, - function(err1) { - log.e(err1.message); - } - ); - } - common.returnMessage(ob.params, 200, "ok"); - } - else { - update_progress(params.qstring.exportid, "importing", params.qstring.status, 0, params.qstring.message, true, {}, params); - } - } - else { - common.returnMessage(ob.params, 404, "data-migration.status-missing"); - } - } - else { - common.returnMessage(ob.params, 404, "data-migration.export_not_found"); - } - } - }); - } - return true; - }); - - //Import data - //to import existing import, which is coppied on server in folder data_migration/import/ - //You have to pass export_id as existing_file - plugins.register("/i/datamigration/import", function(ob) { - //exportid - //my hash key - var params = ob.params; - //if we have import key or validated as user - - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - - validateCreate(params, FEATURE_NAME, function() { - if (params.qstring.test_con) { - common.returnMessage(params, 200, "valid"); - return; - } - var foldername = ""; - var data_migrator = ""; - var logpath = ""; - if (params.files && params.files.import_file) { - foldername = params.files.import_file.name.split('.'); - if (params.qstring.exportid && params.qstring.exportid === '') { - foldername = params.qstring.exportid; - } - else { - foldername = foldername[0]; - } - - if (fs.existsSync(__dirname + "/../import/" + foldername + ".tar.gz") || fs.existsSync(__dirname + "/../import/" + foldername)) { - common.returnMessage(params, 404, 'data-migration.import-process-exist'); - return; - } - logpath = path.resolve(__dirname, '../../../log/dm-import_' + foldername + '.log'); - common.returnMessage(params, 200, "data-migration.import-started"); - - data_migrator = new migration_helper(); - - data_migrator.import_data(params.files.import_file, params, logpath, log, foldername); - } - else if (params.qstring.existing_file) { - var importBasePath = path.resolve(__dirname, './../import'); - var existingFileInput = (params.qstring.existing_file + "").trim(); - var resolvedExistingFilePath = null; - - if (safeDataMigrationId(existingFileInput)) { - resolvedExistingFilePath = common.resolvePathInBase(importBasePath, existingFileInput + '.tar.gz'); - } - else if (existingFileInput.endsWith('.tar.gz') && common.sanitizeFilename(existingFileInput) === existingFileInput) { - resolvedExistingFilePath = common.resolvePathInBase(importBasePath, existingFileInput); - } - - if (resolvedExistingFilePath && fs.existsSync(resolvedExistingFilePath)) { - var fname = path.basename(resolvedExistingFilePath);//path to file - fname = fname.split("."); - foldername = fname[0]; - - if (foldername.length === 0) { - common.returnMessage(params, 404, "data-migration.could-not-find-file"); - } - else { - logpath = path.resolve(__dirname, '../../../log/dm-import_' + foldername + '.log'); - common.returnMessage(params, 200, "data-migration.import-started"); - data_migrator = new migration_helper(); - data_migrator.importExistingData(resolvedExistingFilePath, params, logpath, log, foldername); - } - } - else { - common.returnMessage(params, 404, "data-migration.could-not-find-file"); - } - - } - else { - common.returnMessage(params, 404, "data-migration.import-file-missing"); - } - }); - return true; - }); - - plugins.register("/i/datamigration/delete_all", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateDelete(params, FEATURE_NAME, function() { - delete_all_exports() - .then(function() { - if (fs.existsSync(path.resolve(__dirname, './../import'))) { - fse.remove(path.resolve(__dirname, './../import'), function() { - common.returnMessage(ob.params, 200, "ok"); - }); - } - else { - common.returnMessage(ob.params, 200, "ok"); - } - }, - function(err) { - common.returnMessage(ob.params, 404, err.message); - }); - }); - return true; - }); - - - plugins.register("/i/datamigration/delete_export", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateDelete(params, FEATURE_NAME, function() { - if (params.qstring.exportid) { - var exportid = safeDataMigrationId(params.qstring.exportid); - if (!exportid) { - common.returnMessage(ob.params, 400, "data-migration.invalid-exportid"); - return; - } - common.db.collection("data_migrations").findOne({_id: exportid}, function(err, res) { - if (err) { - common.returnMessage(params, 404, err); - } - else { - if (res) { - var data_migrator = new migration_helper(common.db); - - data_migrator.clean_up_data('export', exportid, true).then(function() { - var logPath = null; - if (res.log && common.sanitizeFilename(res.log) === (res.log + "")) { - logPath = common.resolvePathInBase(path.resolve(__dirname, './../../../log'), res.log); - } - if (res.log && !logPath) { - common.returnMessage(ob.params, 401, "data-migration.unable-to-delete-log-file"); - return; - } - if (logPath && fs.existsSync(logPath)) { - try { - fs.unlinkSync(logPath); - } - catch (err1) { - log.e(err1); - common.returnMessage(ob.params, 401, "data-migration.unable-to-delete-log-file"); return; - } - } - common.db.collection("data_migrations").remove({_id: exportid}, function(err1) { - if (err1) { - common.returnMessage(params, 404, err1); - } - else { - common.returnMessage(ob.params, 200, "ok"); - } - }); - - }, - function(err2) { - common.returnMessage(ob.params, 404, err2.message); - }); - } - else { - common.returnMessage(ob.params, 404, "data-migration.invalid-exportid"); - } - } - }); - } - else { - common.returnMessage(ob.params, 404, 'data-migration.exportid_not_provided'); - } - }); - return true; - }); - - - plugins.register("/i/datamigration/delete_import", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateDelete(params, FEATURE_NAME, function() { - if (params.qstring.exportid && params.qstring.exportid !== '') { - var exportid = safeDataMigrationId(params.qstring.exportid); - if (!exportid) { - common.returnMessage(ob.params, 400, 'data-migration.invalid-exportid'); - return; - } - var data_migrator = new migration_helper(common.db); - data_migrator.clean_up_data('import', exportid, true).then(function() { - //delete log file - var importLogPath = common.resolvePathInBase(path.resolve(__dirname, './../../../log'), 'dm-import_' + exportid + '.log'); - if (importLogPath && fs.existsSync(importLogPath)) { - try { - fs.unlinkSync(importLogPath); - } - catch (err) { - log.e(err); - } - } - //delete info file - try { - var importInfoPath = common.resolvePathInBase(path.resolve(__dirname, './../import'), exportid + '.json'); - if (importInfoPath && fs.existsSync(importInfoPath)) { - fs.unlinkSync(importInfoPath); - } - } - catch (err) { - log.e(err); - } - - common.returnMessage(ob.params, 200, "ok"); - }, - function(err) { - common.returnMessage(ob.params, 404, err.message); - }); - } - else { - common.returnMessage(ob.params, 404, 'data-migration.exportid-missing'); - } - }); - return true; - }); - - plugins.register("/i/datamigration/stop_export", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateUpdate(params, FEATURE_NAME, function() { - if (params.qstring.exportid) { - common.db.collection("data_migrations").findOne({_id: params.qstring.exportid}, function(err, res) { - if (err) { - common.returnMessage(params, 404, err); - } - else { - if (res) { - if (res.status === 'finished') { - common.returnMessage(ob.params, 404, 'data-migration.export-already-finished'); - } - else if (res.status === 'failed') { - common.returnMessage(ob.params, 404, 'data-migration.export-already-failed'); - } - else { - common.db.collection("data_migrations").update({_id: params.qstring.exportid}, {$set: {stopped: true}}, {upsert: true}, function(err1) { - if (err1) { - log.e("Unable to update export status in db"); - } - }); - - if (res.step === 'packing' || res.step === 'exporting') { - common.returnMessage(ob.params, 200, "data-migration.export-already-stopped"); - } - else { - common.returnMessage(ob.params, 404, "data-migration.export-already-sent"); - } - } - return true; - - } - else { - common.returnMessage(ob.params, 404, "data-migration.data-migration.exportid_not_provided"); - } - } - }); - } - else { - common.returnMessage(ob.params, 404, 'data-migration.data-migration.exportid_not_provided'); - } - }); - return true; - }); - - //gets list of exports - plugins.register("/o/datamigration/getmyexports", function(ob) { - var params = ob.params; - //var validate = ob.validateUserForGlobalAdmin; - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('/o/datamigration/getmyexports Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateRead(params, FEATURE_NAME, function() { - common.db.collection("data_migrations").find().sort({ts: -1}).toArray(function(err, res) { - if (err) { - common.returnMessage(ob.params, 404, err.message); - } - else { - if (res) { - if (res.length === 0) { - common.returnMessage(params, 200, "data-migration.no-exports"); - return true; - } - for (var i = 0; i < res.length; i++) { - var dir = path.resolve(__dirname, './../export/' + res[i]._id + '.tar.gz'); - if (res[i].export_path && res[i].export_path !== '') { - dir = res[i].export_path; - } - - if (fs.existsSync(dir)) { - res[i].can_download = true; - } - else { - res[i].can_download = false; - } - - if (fs.existsSync(path.resolve(__dirname, './../export/' + res[i]._id))) { - res[i].have_folder = true; - } - else { - res[i].have_folder = false; - } - - if (!fs.existsSync(path.resolve(__dirname, './../../../log/' + res[i].log))) { - res[i].log = ""; - } - } - common.returnMessage(ob.params, 200, res); - } - else { - common.returnMessage(params, 200, "data-migration.no-exports"); - } - } - }); - }); - return true; - }); - - - plugins.register("/o/datamigration/getmyimports", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('/o/datamigration/getmyimports Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateRead(params, FEATURE_NAME, function() { - var ret_arr = {}; - var have_any = false; - var myfiles = ""; - var filename = ""; - if (fs.existsSync(path.resolve(__dirname, "./../import"))) { - myfiles = fs.readdirSync(path.resolve(__dirname, "./../import")); - for (var i = 0; i < myfiles.length; i++) { - filename = myfiles[i].split('.'); - if (!ret_arr[filename[0]]) { - ret_arr[filename[0]] = {type: '', log: '', last_update: ""}; - have_any = true; - } - - if (filename.length > 0 && filename[1] === 'tar') { - ret_arr[filename[0]].type = 'archive'; - } - else if (filename.length > 0 && filename[1] === 'json') { - try { - var data = fs.readFileSync(path.resolve(__dirname, "./../import/" + myfiles[i])); - var mydata = JSON.parse(data); - if (mydata && mydata.app_names) { - ret_arr[filename[0]].app_list = mydata.app_names; - } - } - catch (SyntaxError) { - log.e("Parse error"); - } - } - else { - ret_arr[filename[0]].type = 'folder'; - } - } - } - - if (fs.existsSync(path.resolve(__dirname, "../../../log"))) { - myfiles = fs.readdirSync(path.resolve(__dirname, "../../../log")); - for (var j = 0; j < myfiles.length; j++) { - filename = myfiles[j].split('_'); - if (filename[0] === 'dm-import' && filename.length > 0) { - var myid = myfiles[j].substr(10).split('.'); - if (myid[0] && typeof ret_arr[myid[0]] !== 'undefined') { - ret_arr[myid[0]].log = myfiles[j]; - } - else { - ret_arr[myid[0]] = {type: '', log: myfiles[j], last_update: ""}; - have_any = true; - } - - try { - var stats = fs.statSync(path.resolve(__dirname, "../../../log/" + myfiles[j])); - ret_arr[myid[0]].last_update = stats.atime; - } - catch (error) { - log.e('Error getting stat of log file'); - } - } - } - } - if (have_any) { - common.returnMessage(ob.params, 200, ret_arr); - } - else { - common.returnMessage(ob.params, 200, "data-migration.no-imports"); - } - }); - return true; - }); - - //create import token - //@params.ttl = time to live in minutes - plugins.register("/o/datamigration/createimporttoken", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - - validateCreate(params, FEATURE_NAME, function() { - var ttl, multi; - //passed in minutes - if (params.qstring.ttl) { - ttl = parseInt(params.qstring.ttl) * 60; - } - else { - ttl = 86400; - }//1 day - if (params.qstring.multi === false) { - multi = false; - } - else { - multi = true; - } - - authorize.save({ - endpoint: ['/i/datamigration/import'], - db: common.db, - ttl: ttl, - multi: multi, - owner: params.member._id + "", - app: "", - callback: function(err, token) { - if (err) { - log.e(err); - common.returnMessage(params, 404, 'data-migration.unable-to-create-token'); - } - else { - common.returnMessage(params, 200, token); - } - } - }); - }); - return true; - }); - - - //Get status of export - //@params.exportid - Export ID - plugins.register("/o/datamigration/getstatus", function(ob) { - var params = ob.params; - - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('/o/datamigration/getstatus Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateRead(params, FEATURE_NAME, function() { - if (typeof params.qstring.exportid !== "undefined") { - common.db.collection("data_migrations").findOne({_id: params.qstring.exportid}, function(err, res) { - if (err) { - common.returnOutput(ob.params, err.message); - } - else { - if (res) { - common.returnMessage(params, 200, res); - } - else { - common.returnMessage(params, 404, 'data-migration.invalid-exportid'); - } - } - }); - } - else { - common.returnOutput(ob.params, 'data-migration.exportid-missing'); - } - }); - - return true; - }); - - - //Get configuration. Default export path for. - plugins.register("/o/datamigration/get_config", function(ob) { - var params = ob.params; - if (params.qstring && params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('/o/datamigration/getstatus Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateRead(params, FEATURE_NAME, function() { - var fileSizeLimit = 0; - - cp.exec("nginx -t", (error, stdout, stderr) => { - if (error) { - console.log(error); - common.returnMessage(params, 200, {def_path: path.resolve(__dirname, './../export'), fileSizeLimit: fileSizeLimit}); - } - else { - var dd = stdout; - if (stdout === "") { - dd = stderr; - } - var pos1 = dd.indexOf("the configuration file"); - var pos2 = dd.indexOf(" ", pos1 + 23 + 2); - var conffile = ""; - if (typeof dd === "string") { - conffile = dd.substring(pos1 + 23, pos2).trim(); - } - else { - conffile = dd.toString("utf-8", pos1 + 23, pos2).trim(); - } - - if (NginxConfFile && NginxConfFile !== "" && conffile !== "" && fs.existsSync(conffile)) { - NginxConfFile.create(conffile, function(err, conf) { - if (err) { - console.log(err); - return; - } - fileSizeLimit = conf.nginx.http.client_max_body_size._value || 0; - if (fileSizeLimit[fileSizeLimit.length - 1] === 'k' || fileSizeLimit[fileSizeLimit.length - 1] === 'K') { - fileSizeLimit = parseInt(fileSizeLimit.substr(0, fileSizeLimit.length - 1)); - } - else if (fileSizeLimit[fileSizeLimit.length - 1] === 'm' || fileSizeLimit[fileSizeLimit.length - 1] === 'M') { - fileSizeLimit = parseInt(fileSizeLimit.substr(0, fileSizeLimit.length - 1)) * 1024; - } - else if (fileSizeLimit[fileSizeLimit.length - 1] === 'g' || fileSizeLimit[fileSizeLimit.length - 1] === 'G') { - fileSizeLimit = parseInt(fileSizeLimit.substr(0, fileSizeLimit.length - 1)) * 1024 * 1024; - } - else { - fileSizeLimit = parseInt(fileSizeLimit) / 1024; - } - common.returnMessage(params, 200, {def_path: path.resolve(__dirname, './../export'), fileSizeLimit: fileSizeLimit}); - }); - } - else { - common.returnMessage(params, 200, {def_path: path.resolve(__dirname, './../export'), fileSizeLimit: fileSizeLimit}); - } - } - }); - - }); - - return true; - }); - - //Export data - //@only_export - 1(only export data), 0 - export and send to remote server - //@apps - app id's separated with ',' - //@server_address - remote server address - //@server_token - token generated on remote server - plugins.register("/i/datamigration/export", function(ob) { - var params = ob.params; - - if (params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateCreate(params, FEATURE_NAME, function() { - var dir = __dirname + '/../export'; - if (!fs.existsSync(dir)) { - try { - fs.mkdirSync(dir, 484); - } - catch (err) { - log.e(err.message); - } - } - - var apps = []; - if (typeof params.qstring.apps !== 'undefined' && params.qstring.apps !== "") { - apps = params.qstring.apps.split(','); - } - else { - common.returnMessage(params, 404, 'data-migration.no_app_ids'); - return true; - } - - if (!params.qstring.only_export || (parseInt(params.qstring.only_export) !== 1 && parseInt(params.qstring.only_export) !== 2)) { - params.qstring.only_export = false; - if (!params.qstring.server_token || params.qstring.server_token === '') { - common.returnMessage(params, 404, 'data-migration.token_missing'); - return true; - } - - if (!params.qstring.server_address || params.qstring.server_address === '') { - common.returnMessage(params, 404, 'data-migration.address_missing'); - return true; - } - else { - params.qstring.server_address = trim_ending_slashes(params.qstring.server_address); - } - } - else { - if (params.qstring.only_export && parseInt(params.qstring.only_export, 10) === 2) { - params.qstring.only_commands = true; - } - params.qstring.only_export = true; - params.qstring.server_address = ""; - params.qstring.server_token = ""; - } - - if (params.qstring.aditional_files && parseInt(params.qstring.aditional_files) === 1) { - params.qstring.aditional_files = true; - } - else { - params.qstring.aditional_files = false; - } - - if (params.qstring.redirect_traffic && parseInt(params.qstring.redirect_traffic) === 1) { - params.qstring.redirect_traffic = true; - } - else { - params.qstring.redirect_traffic = false; - } - - - - var data_migrator = new migration_helper(); - if (params.qstring.only_commands) { - data_migrator.create_export_commands(apps, params, common.db, log).then( - function(result) { - //convert string to buffer - if (typeof result === "string") { - result = Buffer.from(result, 'utf8'); - } - common.returnRaw(params, 200, result, {'Content-Type': 'text/plain; charset=utf-8', 'Content-disposition': 'attachment; filename=countly-export-commands.log'}); - }, - function(error) { - common.returnMessage(params, 404, error.message); - } - ); - } - else { - data_migrator.export_data(apps, params, common.db, log).then( - function(result) { - common.returnMessage(params, 200, result); - }, - function(error) { - common.returnMessage(params, 404, error.message); - } - ); - } - - }); - return true; - }); - - //Validates if given token and address can be used for data import - //@server_address - remote server address - //@server_token - token generated on remote server - plugins.register("/o/datamigration/validateconnection", function(ob) { - var params = ob.params; - - if (params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - - validateRead(params, FEATURE_NAME, function() { - if (!params.qstring.server_token || params.qstring.server_token === '') { - common.returnMessage(params, 404, 'data-migration.token_missing'); - return true; - } - - if (!params.qstring.server_address || params.qstring.server_address === '') { - common.returnMessage(params, 404, 'data-migration.address_missing'); - return true; - } - /** - * callback function for sending data - * @param {object} err - error object - * @param {object} res - result object - * @returns {boolean} true or nothing - */ - function requestCallback(err, res) { - if (err) { - common.returnMessage(params, 404, err.message); - return true; - } - else { - var msg = res.statusMessage; - if (res.body && res.body !== '') { - try { - msg = JSON.parse(res.body); - if (msg.result) { - msg = msg.result; - } - } - catch (exp) { - log.e('Parse ' + res.body + ' JSON failed'); - } - } - - if (res.statusCode >= 400 && res.statusCode < 500) { - if (msg === "Invalid path") { - msg = "data-migration.invalid-server-path"; - } - common.returnMessage(params, 404, msg); - } - else if (res.statusCode === 200 && msg === "valid") { - common.returnMessage(params, 200, 'data-migration.connection-is-valid'); - } - else { - msg = "data-migration.target-server-not-valid"; - common.returnMessage(params, 404, msg); - } - } - return; - } - //remove forvarding slashes - params.qstring.server_address = trim_ending_slashes(params.qstring.server_address); - var r = request.post({url: params.qstring.server_address + '/i/datamigration/import?test_con=1&auth_token=' + params.qstring.server_token}, requestCallback); - r.form(); - }); - return true; - - }); - - //Send exported - //@server_address - remote server address - //@server_token - token generated on remote server - //@exportid = export id - plugins.register("/i/datamigration/sendexport", function(ob) { - var params = ob.params; - - if (params.qstring.args) { - try { - params.qstring.args = JSON.parse(params.qstring.args); - } - catch (SyntaxError) { - log.e('Parse ' + params.qstring.args + ' JSON failed'); - } - } - validateCreate(params, FEATURE_NAME, function() { - if (params.qstring.exportid) { - if (!params.qstring.server_token || params.qstring.server_token === '') { - common.returnMessage(params, 404, 'data-migration.token_missing'); - return true; - } - - if (!params.qstring.server_address || params.qstring.server_address === '') { - common.returnMessage(params, 404, 'data-migration.address_missing'); - return true; - } - - //remove forvarding slashes - params.qstring.server_address = trim_ending_slashes(params.qstring.server_address); - - if (params.qstring.redirect_traffic && parseInt(params.qstring.redirect_traffic) === 1) { - params.qstring.redirect_traffic = true; - } - else { - params.qstring.redirect_traffic = false; - } - - var myreq = JSON.stringify({headers: params.req.headers}); - update_progress(params.qstring.exportid, "packing", "progress", 100, "", true, {stopped: false, only_export: false, server_address: params.qstring.server_address, server_token: params.qstring.server_token, redirect_traffic: params.qstring.redirect_traffic, userid: params.member._id, email: params.member.email, myreq: myreq}); - - common.returnMessage(params, 200, "Success"); - - var data_migrator = new migration_helper(common.db); - data_migrator.send_export(params.qstring.exportid, common.db); - - } - else { - common.returnMessage(params, 404, 'data-migration.invalid-exportid'); - } - - }); - return true; - }); - -}(pluginOb)); - -module.exports = pluginOb; diff --git a/plugins/data_migration/api/data_migration_helper.js b/plugins/data_migration/api/data_migration_helper.js deleted file mode 100644 index 25175f8fde6..00000000000 --- a/plugins/data_migration/api/data_migration_helper.js +++ /dev/null @@ -1,1554 +0,0 @@ - -var crypto = require('crypto'); -var Promise = require("bluebird"); -var plugins = require('../../pluginManager.js'); -const fs = require('fs'); -const fse = require('fs-extra'); -var path = require('path'); -var countlyFs = require('../../../api/utils/countlyFs.js'); -var cp = require('child_process'); //call process -var spawn = cp.spawn; //for calling comannd line -const os = require('os'); //hostname, eol -const request = require('countly-request')(plugins.getConfig("security")); -var common = require('../../../api/utils/common.js'); - -module.exports = function(my_db) { - var db = ""; - if (my_db) { - db = my_db; - } - if (!db) { - db = common.db; - } - var params = ""; - - var my_logpath = ""; - var exportid = ""; - var exp_count = 0; - var exp_prog = 0; - var log = common.log('datamigration:api'); - - var self = this; - var safeDataMigrationId = function(my_exportid) { - my_exportid = my_exportid + ""; - if (my_exportid && common.sanitizeFilename(my_exportid) === my_exportid) { - return my_exportid; - } - return null; - }; - var create_con_strings = function() { - var dbargs = []; - var db_params = plugins.getDbConnectionParams('countly'); - for (var p in db_params) { - dbargs.push("--" + p); - dbargs.push(db_params[p]); - } - var dbargs_drill = []; - db_params = plugins.getDbConnectionParams('countly_drill'); - for (var k in db_params) { - dbargs_drill.push("--" + k); - dbargs_drill.push(db_params[k]); - } - - var dbargs_out = []; - db_params = plugins.getDbConnectionParams('countly_out'); - for (var r in db_params) { - dbargs_out.push("--" + r); - dbargs_out.push(db_params[r]); - } - - return {dbargs: dbargs, dbargs_drill: dbargs_drill, dbargs_out: dbargs_out}; - }; - - - var check_ids = function(apps) { - return new Promise(function(resolve, reject) { - var bad_ids = []; - var app_names = []; - var object_array = []; - for (var i = 0; i < apps.length; i++) { - try { - object_array.push(db.ObjectID(apps[i])); - } - catch (err) { - bad_ids.push(apps[i]); - } - } - - if (bad_ids.length > 0) { - reject(Error("data-migration.invalid_app_id" + bad_ids.join())); - } - db.collection("apps").find({_id: { $in: object_array }}).toArray(function(err, res) { - if (err) { - log.e(err); reject(); - } - else { - for (var k = 0; k < apps.length; k++) { - bad_ids.push(apps[k]); - } - - for (var j = 0; j < res.length; j++) { - app_names.push(res[j].name); - if (bad_ids.indexOf(res[j]._id)) { - bad_ids.splice(bad_ids.indexOf(res[j]._id), 1); - } - } - if (bad_ids.length > 0) { - reject(Error("data-migration.some_bad_ids")); - } - else { - resolve(app_names); - } - } - }); - }); - }; - - var create_and_validate_export_id = function(apps) { - return new Promise(function(resolve, reject) { - //exportid - defined at the top of file - exportid = crypto.createHash('SHA1').update(JSON.stringify(apps)).digest('hex'); - db.collection("data_migrations").findOne({_id: exportid}, function(err, res) { - if (err) { - reject(err); - } - else { - var havefile = false; - var dir = __dirname + '/../export/' + common.sanitizeFilename(exportid) + '.tar.gz'; - havefile = fs.existsSync(dir); - - if (res) { - if ((res.step === 'sending' || res.step === 'importing') && res.status === 'failed') { - if (havefile) { - reject(Error('data-migration.you-have-valid-export-failed-in-sending')); - } - else { - resolve(); - } - } - else if (res.status === 'finished' && res.step === "exporting" && havefile) { - reject(Error("data-migration.you-have-already-exported-data")); - } - else if (res.stopped === false && res.status !== 'finished' && res.status !== 'failed') { - reject(Error('data-migration.already-running-exporting-process')); - } - else { - self.clean_up_data('export', exportid, true).then( - function() { - resolve(); - }, - function(err1) { - reject(err1); - } - ); - } - } - else { - self.clean_up_data('export', exportid, true).then( - function() { - resolve(); - }, - function(err1) { - reject(err1); - } - ); - } - } - }); - }); - }; - - - var update_progress = function(my_exportid, step, status, dif, reason, reset_progress, more_fields) { - exp_prog = exp_prog + dif; - if (reset_progress) { - exp_prog = dif; - } - var progress = 0; - if (exp_count !== 0) { - progress = Math.round(100 * exp_prog / exp_count); - } - else { - progress = exp_prog; - } - - if (typeof reason === 'undefined') { - reason = ""; - } - - var set_data = {step: step, status: status, progress: progress, ts: Date.now(), reason: reason}; - if (more_fields) { - for (var k in more_fields) { - if (Object.prototype.hasOwnProperty.call(more_fields, k)) { - set_data[k] = more_fields[k]; - } - } - } - var updatea = {_id: my_exportid}; - if (!reset_progress) { - updatea.stopped = false; - } - db.collection("data_migrations").update(updatea, {$set: set_data}, {upsert: true}, function(err) { - if (err) { - log.e("Unable to update export status in db"); - } - else if ((status === 'failed' || status === 'finished')) { - db.collection("data_migrations").findOne({_id: my_exportid}, function(err1, res) { - if (err1) { - log.e("db error"); - } - else { - if (res) { - try { - res.myreq = JSON.parse(res.myreq); - } - catch (SyntaxError) { - res.myreq = ""; - } - plugins.dispatch("/systemlogs", {params: {req: res.myreq}, user: {_id: res.userid, email: res.email}, action: "export_" + status, data: {app_ids: res.apps, status: status, message: reason}}); - } - } - }); - - } - - }); - }; - - - this.clean_up_data = function(folder, my_exportid, remove_archive) { - return new Promise(function(resolve, reject) { - my_exportid = safeDataMigrationId(my_exportid); - if (my_exportid) { - if (['import', 'export'].indexOf(folder) === -1) { - reject(Error('data-migration.invalid-folder')); - return; - } - var baseFolder = path.resolve(__dirname, './../' + folder); - if (remove_archive) { - var archivePath = common.resolvePathInBase(baseFolder, my_exportid + '.tar.gz'); - if (archivePath && fs.existsSync(archivePath)) { - try { - fs.unlinkSync(archivePath); - } - catch (err) { - log.e(err); - } - } - } - //cleans up default(if exist), then special - new Promise(function(resolve0, reject0) { - var dataPath = common.resolvePathInBase(baseFolder, my_exportid); - if (dataPath && fs.existsSync(dataPath)) { - //removes default folder if exists - fse.remove(dataPath, err => { - if (err) { - reject0(Error('data-migration.unable-to-remove-directory')); - } - else { - resolve0(); - } - - }); - } - else { - resolve0(); - } - }).then(function() { - if (folder === 'export') { - db.collection("data_migrations").findOne({_id: my_exportid}, function(err, res) { - if (err) { - log.e(err.message); reject(err); - } - else { - if (res && res.export_path && res.export_path !== '') { - if (remove_archive) { - try { - fs.unlinkSync(res.export_path); - } - catch (err1) { - log.e(err1); - } - } - var my_dir = path.dirname(res.export_path); - var exportPath = my_dir ? common.resolvePathInBase(my_dir, my_exportid) : null; - if (exportPath && fs.existsSync(exportPath)) { - fse.remove(exportPath, err1 => { - if (err1) { - reject(Error('data-migration.unable-to-remove-directory')); - } - else { - resolve(); - } - }); - } - else { - resolve(); - } - } - else { - var defaultPath = common.resolvePathInBase(baseFolder, my_exportid); - if (defaultPath && fs.existsSync(defaultPath)) { - fse.remove(defaultPath, err1 => { - if (err1) { - reject(Error('data-migration.unable-to-remove-directory')); - } - else { - resolve(); - } - }); - } - else { - resolve(); - } - } - } - }); - } - else if (folder === 'import') { - var infofile = path.resolve(__dirname, './../import/' + common.sanitizeFilename(my_exportid) + '.json'); - if (fs.existsSync(infofile)) { - try { - var data = fs.readFileSync(infofile); - var mydata = JSON.parse(data); - if (mydata && mydata.my_folder) { - var importPath = common.resolvePathInBase(mydata.my_folder, my_exportid); - if (!importPath) { - reject(Error('data-migration.invalid-exportid')); - return; - } - fse.remove(importPath, err => { - if (err) { - reject(Error('data-migration.unable-to-remove-directory')); - } - else { - resolve(); - } - }); - } - else { - resolve(); - } - } - catch (e) { - if (e) { - log.e("Json parse error"); - } - resolve(); - } - } - else { - resolve(); - } - } - else { - resolve(); - }//there is nothing to remove - }, - function(err) { - reject(Error(err)); - }); - } - else { - reject(Error('data-migration.no-export-id-given')); - } - }); - }; - - - var log_me = function(logpath, message, is_error) { - if (is_error) { - log.e(message); - } - try { - if (message.indexOf(os.EOL) === -1) { - fs.writeFileSync(logpath, message + os.EOL, {'flag': 'a'}); - } - else { - fs.writeFileSync(logpath, message, {'flag': 'a'}); - } - } - catch (err) { - log.e('Unable to log import process:' + message); - } - }; - - var run_command = function(my_command, my_args, update = true) { - return new Promise(function(resolve, reject) { - var starr = ['inherit', 'inherit', 'inherit']; - if (my_logpath !== '') { - const out = fs.openSync(my_logpath, 'a'); - const err = fs.openSync(my_logpath, 'a'); - starr = [ 'ignore', out, err ]; - log_me(my_logpath, "running command " + my_command + " " + my_args.join(" "), false); - } - var child = spawn(my_command, my_args, {shell: false, cwd: __dirname, detached: false, stdio: starr}, function(error) { - if (error) { - reject(Error('error:' + JSON.stringify(error))); - return; - } - }); - - child.on('error', function(error) { - if (my_logpath !== '') { - log_me(my_logpath, error.message, false); - } - return resolve(); - }); - child.on('exit', function(code) { - if (code === 0) { - if (update && exp_count > 0 && exportid && exportid !== "") { - update_progress(exportid, "exporting", "progress", 1, ""); - } - return resolve(); - } - else { - if (my_logpath !== '') { - log_me(my_logpath, "Exited with error code: " + code, false); - } - return resolve(); - } - }); - }); - }; - - var generate_events_scripts = function(data) { - return new Promise(function(resolve, reject) { - db.collection("events").find({_id: db.ObjectID(data.appid)}).toArray(function(err, res) { - if (err) { - reject(Error(err)); - } - var scripts = []; - if (res && res.length > 0) { - for (var j = 0; j < res.length; j++) { - if (res[j].list && res[j].list.length > 0) { - for (var z = 0; z < res[j].list.length; z++) { - var eventCollName = "events" + crypto.createHash('sha1').update(res[j].list[z] + data.appid).digest('hex'); - //old data, can be removed once we are sure that we are only using merged events_data collection - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', eventCollName, '--out', data.my_folder]}); - - if (plugins.isPluginEnabled('drill')) { - eventCollName = "drill_events" + crypto.createHash('sha1').update(res[j].list[z] + data.appid).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs_drill, '--collection', eventCollName, '--out', data.my_folder]}); - } - } - } - } - //new data - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', "events_data", '-q', '{ "_id": {"$regex":"^' + data.appid + '_.*"}}', '--out', data.my_folder]}); - if (plugins.isPluginEnabled('drill')) { - scripts.push({cmd: 'mongodump', args: [...data.dbargs_drill, '--collection', "drill_events", '-q', '{ "a": "' + data.appid + '"}', '--out', data.my_folder]}); - } - } - resolve(scripts); - } - ); - }); - }; - - var generate_credentials_scripts = function(data) { - return new Promise(function(resolve, reject) { - db.collection("apps").findOne({_id: db.ObjectID(data.appid)}, function(err, res) { - if (err) { - reject(Error(err)); - } - var cid = []; - if (res && res.plugins && res.plugins.push) { - if (res.plugins.push.a && res.plugins.push.a._id) { - cid.push('{"$oid":"' + res.plugins.push.a._id + '"}'); - } - - if (res.plugins.push.i && res.plugins.push.i._id) { - cid.push('{"$oid":"' + res.plugins.push.i._id + '"}'); - } - } - if (cid.length > 0) { - resolve([{cmd: 'mongodump', args: [...data.dbargs, '--collection', 'credentials', '-q', '{ "_id": {"$in":[' + cid.join(',') + ']}}', '--out', data.my_folder]}]); - } - else { - resolve([]); - } - }); - }); - }; - - var createScriptsForViews = function(data) { - return new Promise(function(resolve/*, reject*/) { - var scripts = []; - var appId = data.appid; - db.collection("views").findOne({'_id': db.ObjectID(appId)}, {}, function(err, viewInfo) { - - var colName = "app_viewdata" + crypto.createHash('sha1').update(appId).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', colName, '--out', data.my_folder]}); - if (viewInfo) { - for (let segKey in viewInfo.segments) { - colName = "app_viewdata" + crypto.createHash('sha1').update(segKey + appId).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', colName, '--out', data.my_folder]}); - } - } - colName = "app_viewdata" + crypto.createHash('sha1').update('platform' + appId).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', colName, '--out', data.my_folder]}); - resolve(scripts); - }); - - }); - }; - - var create_export_scripts = function(data) { - return new Promise(function(resolve, reject) { - var appid = data.appid; - var my_folder = data.my_folder; - - var scripts = []; - var dbargs = []; - var dbargs0 = []; - var countly_db_name = ""; - var db_params = plugins.getDbConnectionParams('countly'); - for (var p in db_params) { - dbargs.push("--" + p); - dbargs.push(db_params[p]); - if (p !== 'db') { - dbargs0.push("--" + p); - dbargs0.push(db_params[p]); - } - else { - countly_db_name = db_params[p]; - } - } - - var dbargs_drill = []; - db_params = plugins.getDbConnectionParams('countly_drill'); - for (var z in db_params) { - dbargs_drill.push("--" + z); - dbargs_drill.push(db_params[z]); - } - - var dbargs_out = []; - db_params = plugins.getDbConnectionParams('countly_out'); - for (var g in db_params) { - dbargs_out.push("--" + g); - dbargs_out.push(db_params[g]); - } - - db.collection("apps").findOne({_id: db.ObjectID(appid)}, function(err, res) { - if (err || !res) { - reject(Error("data-migration.invalid-app-id")); - } - else { - if (!res.redirect_url || res.redirect_url === "") { - scripts.push({cmd: 'mongodump', args: [...dbargs, "--collection", "apps", "-q", '{ "_id": {"$oid":"' + appid + '"}}', "--out", my_folder]}); - } - else { - //remove redirect field and add it after dump. - scripts.push({cmd: 'mongo', args: [countly_db_name, ...dbargs0, "--eval", 'db.apps.update({ "_id": ObjectId("' + appid + '")}, { "$unset": { "redirect_url": 1 } })']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, "--collection", "apps", "-q", '{ "_id": {"$oid":"' + appid + '"}}', "--out", my_folder]}); - scripts.push({cmd: 'mongo', args: [countly_db_name, ...dbargs0, "--eval", 'db.apps.update({ "_id": ObjectId("' + appid + '")}, { $set: { redirect_url: "' + res.redirect_url + '" } })']}); - } - - var appDocs = ['app_users', 'metric_changes', 'app_crashes', 'app_crashgroups', 'app_crashusers', 'app_nxret', 'app_viewdata', 'app_views', 'app_userviews', 'app_viewsmeta', 'blocked_users', 'campaign_users', 'consent_history', 'crashes_jira', 'event_flows', 'timesofday', 'feedback', 'push_', 'apm', "nps", "survey", "completed_surveys"]; - for (let j = 0; j < appDocs.length; j++) { - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', appDocs[j] + appid, '--out', my_folder]}); - } - - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'campaigndata', '-q', '{ "a": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'campaigns', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'crash_share', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'feedback_widgets', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'notes', '-q', '{ "app_id":"' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'messages', '-q', '{ "apps": {"$oid":"' + appid + '"}}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'cohortdata', '-q', '{ "a": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'cohorts', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'server_stats_data_points', '-q', '{ "a": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'consent_history', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'flow_schemas', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'flow_data', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'times_of_day', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder]}); - - //concurrent_users - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'concurrent_users_max', '-q', '{"$or":[{ "app_id": "' + appid + '"},{ "_id": {"$in" :["' + appid + '_overall", "' + appid + '_overall_new"]}}]}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'concurrent_users_alerts', '-q', '{ "app": "' + appid + '"}', '--out', my_folder]}); - - - var sameStructures = ["browser", "carriers", "cities", "consents", "crashdata", "density", "device_details", "devices", "langs", "sources", "users", "retention_daily", "retention_weekly", "retention_monthly", "server_stats_data_points"]; - - for (var k = 0; k < sameStructures.length; k++) { - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', sameStructures[k], '-q', '{ "_id": {"$regex": "^' + appid + '_.*" }}', '--out', my_folder]}); - } - if (dbargs_out && dbargs_out.length) { - scripts.push({cmd: 'mongodump', args: [...dbargs_out, '--collection', "ab_testing_experiments" + appid, '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs_out, '--collection', "remoteconfig_parameters" + appid, '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs_out, '--collection', "remoteconfig_conditions" + appid, '--out', my_folder]}); - } - - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'max_online_counts', '-q', '{"_id": {"$oid":"' + appid + '"}}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'top_events', '-q', '{ "app_id": {"$oid":"' + appid + '"}}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'events', '-q', '{ "_id": {"$oid":"' + appid + '"}}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'views', '-q', '{ "_id": {"$oid":"' + appid + '"}}', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'funnels', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'calculated_metrics', '-q', '{ "app": "' + appid + '" }', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'datamanager_transforms', '-q', '{ "app": "' + appid + '" }', '--out', my_folder]}); - - - //event Timeline data: - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'eventTimes' + appid, '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'timelineStatus', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder]}); - - //internal events - for (let j = 0; j < plugins.internalEvents.length; j++) { - let eventCollName = "events" + crypto.createHash('sha1').update(plugins.internalEvents[j] + appid).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', eventCollName, '--out', my_folder]}); - } - - if (plugins.isPluginEnabled('drill')) { - //export drill - var drill_events = plugins.internalDrillEvents; - - for (let j = 0; j < drill_events.length; j++) { - let eventCollName = "drill_events" + crypto.createHash('sha1').update(drill_events[j] + appid).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', eventCollName, '--out', my_folder]}); - } - - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', 'drill_bookmarks', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', 'drill_meta' + appid, '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', 'drill_meta', '-q', '{ "_id": {"$regex": "^' + appid + '_.*" }}', '--out', my_folder]}); - } - //export symbolication files - if (data.aditional_files) { - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'app_crashsymbols' + appid, '--out', my_folder]}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'symbolication_jobs', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder]}); - } - - //events sctipts - generate_events_scripts({appid: appid, my_folder: my_folder, dbargs: dbargs, dbargs_drill: dbargs_drill}, db) - .then( - function(result) { - if (result && Array.isArray(result)) { - scripts = scripts.concat(result); - } - - return generate_credentials_scripts({appid: appid, my_folder: my_folder, dbargs: dbargs, dbargs_drill: dbargs_drill}); - }) - .then( - function(result) { - if (result && Array.isArray(result)) { - scripts = scripts.concat(result); - } - - return createScriptsForViews({appid: appid, my_folder: my_folder, dbargs: dbargs, dbargs_drill: dbargs_drill}); - }) - .then( - function(result) { - if (result && Array.isArray(result)) { - scripts = scripts.concat(result); - } - return resolve(scripts); - }, - function(error) { - reject(Error(error.message)); - } - ).catch(err1 => { - reject(err1); - }); - } - }); - }); - }; - - var copy_app_image = function(data) { - return new Promise(function(resolve) { - var imagepath = path.resolve(__dirname, './../../../frontend/express/public/appimages/' + data.appid + ".png"); - countlyFs.exists("appimages", imagepath, {id: data.appid + ".png"}, function(err, exist) { - if (exist) { - countlyFs.getStream("appimages", imagepath, {id: data.appid + ".png"}, function(err1, stream) { - if (!err1 && stream) { - var wstream = fs.createWriteStream(data.image_folder + '/' + data.appid + '.png'); - - wstream.on('error', function(errw) { - log.e("Couldn't copy file: " + errw); - }); - - stream.pipe(wstream); - stream.on('end', () => { - resolve("Icon copied: " + data.appid + '.png'); - }); - stream.on('error', ()=>{ - resolve(); - }); - } - }); - } - else { - resolve("Icon doesn't exist:" + data.appid + '.png'); - } - }); - }); - }; - - var pack_data = function(my_exportid, target_path) { - return new Promise(function(resolve, reject) { - update_progress(my_exportid, "packing", "progress", 0, "", true); - var my_command = "tar"; - var my_args = ["-zcvf"]; - if (target_path !== '') { - let my_dir = path.dirname(target_path); - my_args.push(target_path); - my_args.push("--directory=" + my_dir); - my_args.push(my_exportid); - } - else { - let my_dir = path.resolve(__dirname, "./../export"); - my_args.push(my_dir + "/" + my_exportid + ".tar.gz"); - my_args.push("--directory=" + my_dir); - my_args.push(my_exportid); - } - run_command(my_command, my_args).then( - function() { - return resolve(); - }, - function(error) { - return reject(Error(error.message)); - } - ); - }); - }; - - var uploadFile = function(myfile) { - return new Promise(function(resolve, reject) { - var var_name = myfile.name; - var tmp_path = myfile.path; - if (var_name.length < 6 || var_name.substr(var_name.length - 6, var_name.length - 1) !== "tar.gz") { - fs.unlink(tmp_path, function() {}); - reject(Error("Invalid file format")); - } - else { - var target_path = path.resolve(__dirname, '../import/' + var_name); - fs.rename(tmp_path, target_path, (err) => { - if (err) { - reject(Error("data-migration.unable-to-copy-file")); - } - resolve(); - }); - } - }); - }; - - var import_app_icons = function(folder) { - return new Promise(function(resolve, reject) { - folder = fix_my_path(folder); - if (folder === false) { - reject(Error('Bad Archive')); - } - folder = folder + '/countly_app_icons'; - if (!fs.existsSync(folder)) { - resolve("There are no icons"); - } - else { - var myfiles = fs.readdirSync(folder); - - var objlist = []; - for (var i = 0; i < myfiles.length; i++) { - objlist.push({imagepath: path.resolve(__dirname, './../../../frontend/express/public/appimages/' + myfiles[i]), source: folder + '/' + myfiles[i], id: myfiles[i]}); - } - Promise.all(objlist.map(function(obj) { - return new Promise(function(resolve1, reject1) { - countlyFs.saveFile("appimages", obj.imagepath, obj.source, {id: obj.id}, function(err) { - if (err) { - reject1(err); - } - else { - resolve1('Icon coppied:' + obj.id); - } - }); - }); - })) - .then( - function(result) { - resolve(result); - }, - function(err) { - reject(Error(err)); - } - - ); - - - } - }); - }; - - var import_symbolication_files = function(folder) { - return new Promise(function(resolve, reject) { - folder = fix_my_path(folder); - if (folder === false) { - reject(Error('Bad Archive')); - } - folder = folder + '/countly_symbolication_files'; - if (!fs.existsSync(folder)) { - resolve("data-migration.there-are-no-symbolication-files"); - } - else { - var myapps = fs.readdirSync(folder); - var objlist = []; - for (var i = 0; i < myapps.length; i++) { - var myfiles = fs.readdirSync(folder + '/' + myapps[i]); - for (var j = 0; j < myfiles.length; j++) { - objlist.push({imagepath: path.resolve(__dirname, './../../crash_symbolication/crashsymbols/' + myapps[i] + '/' + myfiles[j]), source: folder + '/' + myapps[i] + '/' + myfiles[j], id: myapps[i] + '.' + myfiles[j]}); - } - } - - if (objlist.length === 0) { - resolve(); - } - else { - Promise.all(objlist.map(function(obj) { - return new Promise(function(resolve1, reject1) { - countlyFs.saveFile("crash_symbols", obj.imagepath, obj.source, {id: obj.id}, function(err) { - if (err) { - reject1(err); - } - else { - resolve1('Crash file coppied:' + obj.id); - } - }); - }); - })) - .then( - function(result) { - resolve(result); - }, - function(err) { - reject(Error(err)); - } - - ); - } - } - }); - }; - var fix_my_path = function(my_path) { - if (fs.existsSync(my_path)) { - var myfolder = fs.readdirSync(my_path); - if (myfolder.length === 1) { - var mm = myfolder[0].split("."); - //is folder - if (mm.length === 1) { - var sub_ok = fix_my_path(my_path + "/" + myfolder[0]); - if (sub_ok !== false) { - return sub_ok; - } - } - } - else { - for (var i = 0; i < myfolder.length; i++) { - if (myfolder[i].slice(-5) === '.bson') { - return false; - } - } - } - return my_path; - } - else { - return false; - } - }; - - var copy_sybolication_file = function(obj) { - return new Promise(function(resolve, reject) { - var tmp_path = path.resolve(__dirname, './../../crash_symbolication/crashsymbols/' + obj.appid + "/" + obj.symbolid + ".cly_symbol"); - - countlyFs.exists("crash_symbols", tmp_path, {id: obj.appid + "." + obj.symbolid + ".cly_symbol"}, function(err, exist) { - if (exist) { - countlyFs.getStream("crash_symbols", tmp_path, {id: obj.appid + "." + obj.symbolid + ".cly_symbol"}, function(err1, stream) { - if (err1 || !stream) { - reject(); - } - else { - if (!fs.existsSync(obj.folder + '/' + obj.appid)) { - try { - fs.mkdirSync(obj.folder + '/' + obj.appid, 484); - } - catch (err2) { - log_me(my_logpath, err2.message, true); - } - } - var wstream = fs.createWriteStream(obj.folder + '/' + obj.appid + '/' + obj.symbolid + ".cly_symbol"); - stream.pipe(wstream); - stream.on('end', () => { - resolve("Symbolication file copied: " + obj.appid + "/" + obj.symbolid + ".cly_symbol"); - }); - stream.on('error', ()=>{ - reject(); - }); - } - }); - } - else { - resolve("File doesn't exist:" + obj.appid + "/" + obj.symbolid + ".cly_symbol"); - } - }); - }); - }; - - var copy_symbolication_files = function(data) { - return new Promise(function(resolve, reject) { - //aditional_files:path.resolve(my_folder,'./countly_symbolication_files') - db.collection("app_crashsymbols" + data.appid).find().toArray(function(err, res) { - if (err) { - log.e(err); reject(); - } - else { - var symb_files = []; - for (var i = 0; i < res.length; i++) { - symb_files.push({folder: data.aditional_files, symbolid: res[i]._id, appid: data.appid}); - } - if (symb_files.length > 0) { - Promise.all(symb_files.map(copy_sybolication_file) - ).then( - function(result) { - resolve(result); - }, - function(err1) { - reject(Error(err1)); - } - ); - } - else { - resolve(); - } - } - }); - - }); - }; - - - var report_import = function(my_params, message, status, my_exportid) { - if (status !== 'finished') { - status = 'failed'; - } - - var imported_apps = []; - var imported_ids = []; - try { - var data = fs.readFileSync(path.resolve(__dirname, "./../import/" + common.sanitizeFilename(my_exportid) + '.json')); - var mydata = JSON.parse(data); - if (mydata && mydata.app_names) { - imported_apps = mydata.app_names.split(','); - } - - if (mydata && mydata.app_ids) { - imported_ids = mydata.app_ids.split(','); - } - } - catch (err) { - log.e("JSON parse error" + err); - } - - var moredata = {"app_ids": imported_ids, "app_names": imported_apps, "exportid": my_exportid, reason: message}; - if (my_params && my_params.qstring && my_params.qstring.exportid) { - moredata.using_token = true; - moredata.token = my_params.qstring.auth_token || my_params.req.headers["countly-token"]; - moredata.serverip = my_params.req.headers["x-real-ip"]; - moredata.host = my_params.req.headers.host; - - request.post({url: 'http://' + moredata.serverip + '/i/datamigration/report_import?token=' + moredata.token + "&exportid=" + my_exportid + "&status=" + status + "&message=" + message, agentOptions: {rejectUnauthorized: false}}, function(err, res) { - if (err) { - plugins.dispatch("/systemlogs", {params: my_params, action: "import_" + status + "_response_failed", data: moredata}); - } - else { - if (res.statusCode >= 400 && res.statusCode < 500) { - var msg = res.statusMessage; - - if (res.body && res.body !== '') { - try { - msg = JSON.parse(res.body); - if (msg.result) { - msg = msg.result; - } - log.e('Failue to report import'); - log.e(msg); - } - catch (SyntaxError) { - log.e(SyntaxError); - } - } - plugins.dispatch("/systemlogs", {params: my_params, action: "import_" + status + "_response_failed", data: moredata}); - } - else { - plugins.dispatch("/systemlogs", {params: my_params, action: "import_" + status + "_response_ok", data: moredata}); - } - } - - }); - } - else { - plugins.dispatch("/systemlogs", {params: my_params, action: "import_" + status, data: moredata}); - } - }; - - var import_me = function(folder, logpath, my_import_id) { - return new Promise(function(resolve, reject) { - var basefolder = folder; - folder = fix_my_path(folder); - var mydata = {}; - if (folder === false) { - reject(Error('Bad Archive')); - } - - try { - try { - var data = fs.readFileSync(folder + '/info.json'); - mydata = JSON.parse(data); - } - catch (error1) { - log.e(error1); - } - mydata.my_folder = basefolder; - fs.writeFileSync(path.resolve(__dirname, './../import/' + my_import_id + '.json'), JSON.stringify(mydata)); - } - catch (error) { - log.e(error); - } - - var myfiles = fs.readdirSync(folder); - var myscripts = []; - - var constr = create_con_strings(); - - for (let i = 0; i < myfiles.length; i++) { - //folder for each app - if (myfiles[i] !== '.' && myfiles[i] !== '..' && fs.lstatSync(path.resolve(folder, './' + myfiles[i])).isDirectory() && myfiles[i] !== 'countly_app_icons') { - var subdirectory = fs.readdirSync(path.resolve(folder, './' + myfiles[i])); - for (var j = 0; j < subdirectory.length; j++) { - if (constr.dbargs.indexOf(subdirectory[j]) > -1) { - myscripts.push({cmd: 'mongorestore', args: [...constr.dbargs, '--dir', folder + '/' + myfiles[i] + '/' + subdirectory[j]]}); - } - else if (constr.dbargs_drill.indexOf(subdirectory[j]) > -1) { - myscripts.push({cmd: 'mongorestore', args: [...constr.dbargs_drill, '--dir', folder + '/' + myfiles[i] + '/' + subdirectory[j]]}); - } - else if (constr.dbargs_out.indexOf(subdirectory[j]) > -1) { - myscripts.push({cmd: 'mongorestore', args: [...constr.dbargs_out, '--dir', folder + '/' + myfiles[i] + '/' + subdirectory[j]]}); - } - } - } - } - if (myscripts.length > 0) { - log_me(logpath, 'Scripts generated sucessfully', false); - my_logpath = logpath; - Promise.each(myscripts, function(command) { - return run_command(command.cmd, command.args); - }).then( - function() { - //update messages - if (mydata && mydata.app_ids) { - log_me(logpath, 'Updating records in messages collection', false); - var imported_ids = mydata.app_ids.split(','); - var objectIDS = []; - for (let z = 0; z < imported_ids.length; z++) { - if (imported_ids[z] !== "") { - objectIDS.push(common.db.ObjectID(imported_ids[z])); - } - } - common.db.collection('messages').find({"apps": {"$in": objectIDS}}).toArray(function(err1, list) { - if (err1) { - log_me(logpath, err1, false); - } - for (let z = 0; z < list.length; z++) { - if (list[z].auto === true) { - if ((list[z].result.status & 2) > 0) { - list[z].result.status = list[z].result.status & ~2; - common.db.collection('messages').update({_id: list[z]._id}, { $set: {'result.status': list[z].result.status}}, function(err/*, res*/) { - if (err) { - log_me(logpath, err, false); - } - }); - } - } - else if (list[z].auto !== true) { - if ((list[z].result.status & 2) > 0) { - list[z].result.status = list[z].result.status & ~2 | 16 | 1024; - common.db.collection('messages').update({_id: list[z]._id}, { $set: {'result.error': 'Already scheduled messages are not migrated and will be sent from your old server', 'result.status': list[z].result.status}}, function(err/*, res*/) { - if (err) { - log_me(logpath, err, false); - } - }); - } - } - } - resolve(); - }); - } - else { - resolve(); - } - }, - function(err) { - reject(Error(err.message)); - }); - } - else { - reject(Error('data-migration.there-is-no-data-to-insert')); - } - }); - }; - - this.send_export = function(my_exportid, passed_db) { - if (passed_db) { - db = passed_db; - } - /** - * Request callback function - * @param {object} err error object - * @param {object} res result object - */ - function requestCallback(err, res) { - if (err) { - update_progress(my_exportid, "sending", "failed", 0, err.message, true); - } - else { - var msg = res.statusMessage; - if (res.body && res.body !== '') { - try { - msg = JSON.parse(res.body); - if (msg.result) { - msg = msg.result; - } - } - catch (SyntaxError) { - log.e(SyntaxError); - } - } - if (res.statusCode >= 400 && res.statusCode < 500) { - if (msg === "Invalid path") { - msg = "data-migration.invalid-server-path"; - } - update_progress(my_exportid, "sending", "failed", 0, msg, true, {}); - } - else if (res.statusCode === 200 && msg === "data-migration.import-started") { - update_progress(my_exportid, "importing", "progress", 0, msg, true); - } - else { - msg = "data-migration.sending-failed-server-address-wrong"; - update_progress(my_exportid, "sending", "failed", 0, msg, true, {}); - } - } - } - db.collection("data_migrations").findOne({_id: my_exportid}, function(err, res) { - if (err) { - log.e(err.message); - } - else { - if (res && res.stopped === false) { - update_progress(my_exportid, "validating_files", "progress", 0, "", true); - var dir = path.resolve(__dirname, './../export/' + my_exportid + '.tar.gz'); - if (res.export_path && res.export_path !== '') { - dir = res.export_path; - } - if (!fs.existsSync(dir)) { - update_progress(my_exportid, "validating_files", "failed", 0, "Export file missing", true, {}); - return; - } - - update_progress(my_exportid, "sending", "progress", 0, "", true); - const fileData = { - fileField: 'import_file', - fileStream: fs.createReadStream(dir) - }; - - request.post({ - url: res.server_address + '/i/datamigration/import?exportid=' + my_exportid + '&auth_token=' + res.server_token, - form: fileData - }, requestCallback); - } - } - }); - }; - - this.update_progress = function(my_exportid, step, status, dif, reason, reset_progress, more_fields) { - update_progress(my_exportid, step, status, dif, reason, reset_progress, more_fields); - }; - - this.create_export_commands = function(apps, my_params, passed_db, passed_log) { - return new Promise(function(resolve, reject) { - if (passed_db) { - db = passed_db; - } - if (my_params) { - params = my_params; - } - if (passed_log) { - log = passed_log; - } - - apps = apps.sort(); - //clear out duplicates - for (let i = 1; i < apps.length - 1; i++) { - if (apps[i - 1] === apps[i]) { - apps.splice(i, 1); i--; - } - } - - var scriptobj = []; - exportid = crypto.createHash('SHA1').update(JSON.stringify(apps)).digest('hex'); - var my_folder = path.resolve(__dirname, './../export/' + exportid); - var image_folder = path.resolve(my_folder, './countly_app_icons'); - for (let i = 0; i < apps.length; i++) { - let subfolder = path.resolve(my_folder, './' + apps[i]); - scriptobj.push({appid: apps[i], my_folder: subfolder, image_folder: image_folder, additional_files: path.resolve(my_folder, './countly_symbolication_files')}); - } - - - Promise.all(scriptobj.map(create_export_scripts)).then(function(result) { - var lines = []; - if (result && Array.isArray(result)) { - for (var i = 0; i < result.length; i++) { - if (Array.isArray(result[i]) && result[i].length > 0) { - for (let j = 0; j < result[i].length; j++) { - lines.push(result[i][j].cmd + " '" + result[i][j].args.join("' '") + "'"); - } - } - } - } - var data = lines.join("\n"); - //save document in gridfs - resolve(data); - - - }).catch(function(err) { - log.e(err); - reject(Error(err.message)); - }); - }); - - }; - this.export_data = function(apps, my_params, passed_db, passed_log) { - return new Promise(function(resolve, reject) { - if (passed_db) { - db = passed_db; - } - if (my_params) { - params = my_params; - } - if (passed_log) { - log = passed_log; - } - - apps = apps.sort(); - var app_names = []; - //clear out duplicates - for (let i = 1; i < apps.length - 1; i++) { - if (apps[i - 1] === apps[i]) { - apps.splice(i, 1); i--; - } - } - - check_ids(apps).then( - function(result) { - if (result && Array.isArray(result)) { - app_names = result; - } - return create_and_validate_export_id(apps); - } - ).then( - function() { - var my_folder = path.resolve(__dirname, './../export/' + exportid); - - if (params.qstring.target_path && params.qstring.target_path !== "") { - my_folder = params.qstring.target_path + "/" + exportid; - } - - if (!fs.existsSync(my_folder)) { - try { - fs.mkdirSync(my_folder, 484); - } - catch (err) { - log.e(err.message); - } - } - - var created = Date.now(); - var myreq = JSON.stringify({headers: params.req.headers}); - - var logname = 'dm-export_' + exportid + '.log'; - my_logpath = path.resolve(__dirname, './../../../log/' + logname); - if (fs.existsSync(my_logpath)) { - try { - fs.unlinkSync(path.resolve(__dirname, './../../../log/' + logname)); - } - catch (err) { - log.e(err.message); - } - } - - - var filepath = ""; - if (params.qstring.target_path && params.qstring.target_path !== "") { - filepath = params.qstring.target_path; - filepath = path.resolve(params.qstring.target_path, './' + exportid + '.tar.gz'); - } - update_progress(exportid, "exporting", "progress", 0, "", true, {created: created, stopped: false, only_export: params.qstring.only_export, server_address: params.qstring.server_address, server_token: params.qstring.server_token, redirect_traffic: params.qstring.redirect_traffic, aditional_files: params.qstring.aditional_files, apps: apps, app_names: app_names, userid: params.member._id, email: params.member.email, myreq: myreq, log: logname, export_path: filepath}); - - var scriptobj = []; - - //creates dir for app icons - var image_folder = path.resolve(my_folder, './countly_app_icons'); - if (!fs.existsSync(image_folder)) { - try { - fs.mkdirSync(image_folder, 484); - } - catch (err) { - log.e(err.message); - } - } - - for (let i = 0; i < apps.length; i++) { - let subfolder = path.resolve(my_folder, './' + apps[i]); - scriptobj.push({appid: apps[i], my_folder: subfolder, image_folder: image_folder, aditional_files: path.resolve(my_folder, './countly_symbolication_files')}); - if (!fs.existsSync(subfolder)) { - try { - fs.mkdirSync(subfolder, 484); - } - catch (err) { - log.e(err.message); - } - } - } - - Promise.all(scriptobj.map(create_export_scripts)) - .then(function(result) { - var scripts = []; - if (result && Array.isArray(result)) { - for (var i = 0; i < result.length; i++) { - if (Array.isArray(result[i]) && result[i].length > 0) { - scripts = scripts.concat(result[i]); - } - } - } - - if (scripts && scripts.length > 0) { - log_me(my_logpath, "Export scripts created", false); - exp_count = scripts.length; - resolve(exportid); - Promise.each(scripts, function(command) { - return run_command(command.cmd, command.args); - }).then( - function() { - log_me(my_logpath, "Files generated sucessfully", false); - //create info file - try { - fs.writeFileSync(path.resolve(my_folder, './info.json'), '{"id":"' + exportid + '","app_names":"' + app_names.join() + '","app_ids":"' + apps.join() + '"}', {'flag': 'a'}); - } - catch (error) { - log.e(error); - } - - //creates dir for app icons - var subfolder = path.resolve(my_folder, './countly_app_icons'); - if (!fs.existsSync(subfolder)) { - try { - fs.mkdirSync(subfolder, 484); - } - catch (err) { - log.e(err.message); - } - } - - Promise.all(scriptobj.map(copy_app_image)) - .then(function(result0) { - log_me(my_logpath, result0, false); - if (params.qstring.aditional_files) { - //creates folder for symbolication files - subfolder = path.resolve(my_folder, './countly_symbolication_files'); - if (!fs.existsSync(subfolder)) { - try { - fs.mkdirSync(subfolder, 484); - } - catch (err) { - log.e(err.message); - } - } - return Promise.all(scriptobj.map(copy_symbolication_files)); - } - else { - return Promise.resolve(); - } - }) - .then(function(result1) { - if (Array.isArray(result1)) { - log_me(my_logpath, result1, false); - } - return pack_data(exportid, filepath); - }) - .then( - function() { - log_me(my_logpath, "Files packed", false); - log_me(my_logpath, "Starting clean up", false); - //deletes folder with files(not needed anymore because we have archive - self.clean_up_data('export', exportid, false).then( - function() { - log_me(my_logpath, "Clean up completed", false); - if (params.qstring.only_export && params.qstring.only_export === true) { - update_progress(exportid, "exporting", "finished", 0, "", true, {}, params); - } - else { - log_me(my_logpath, "Preparing for sending files", false); - self.send_export(exportid); - } - }, - function() { - log_me(my_logpath, "Clean up failed", false); - if (params.qstring.only_export && params.qstring.only_export === true) { - update_progress(exportid, "exporting", "finished", 0, "data-migration.export-completed-unable-to-delete", true, {}); - } - else { - log_me(my_logpath, "Preparing for sending files", false); - self.send_export(exportid); - } - } - ); - }, - function(err) { - update_progress(exportid, "packing", "failed", 0, err.message, true, {}); - } - ); - }, - function(err) { - update_progress(exportid, "exporting", "failed", 0, err.message, true, {}); - }); - - } - else { - reject(Error('data-migration.failed-generate-scripts')); - } - - }, - function(error) { - update_progress(exportid, "exporting", "failed", 0, error.message, true, {}); - reject(Error('data-migration.failed-generate-scripts')); - }); - }, - function(error) { - return reject(error); - } - ); - - }); - }; - - this.importExistingData = function(my_file, my_params, logpath, passed_log, foldername) { - my_logpath = logpath; - params = my_params; - if (passed_log) { - log = passed_log; - } - - log_me(my_logpath, 'Starting import process', false); - var current_dir = path.dirname(my_file); - - if (!fs.existsSync(path.resolve(__dirname, './../import'))) { - try { - fs.mkdirSync(path.resolve(__dirname, './../import'), 484); - } - catch (err) { - log_me(logpath, err.message, true); - } - } - - try { - fs.mkdirSync(path.resolve(current_dir, './' + foldername), 484); - } - catch (err) { - log_me(logpath, err.message, true); - } - //creates forder for info - try { - fs.mkdirSync(path.resolve(__dirname, './../import/' + foldername), 484); - } - catch (err) { - log_me(logpath, err.message, true); - } - - import_process(my_file, logpath, foldername, current_dir + "/" + foldername); - }; - - var import_process = function(import_file, logpath, foldername, process_dir) { - if (!process_dir) { - process_dir = path.resolve(__dirname, './../import/' + foldername); - } - - run_command("tar", ["xvzf", import_file, "-C", process_dir], false) //unpack file - .then( - function() { - log_me(logpath, 'File unarchived sucessfully', false); - return import_me(process_dir, logpath, foldername);//create and run db scripts - }) - .then( - function() { - log_me(logpath, 'Data imported', false); - return import_app_icons(process_dir); //copy icons - } - ) - .then(function(result) { - if (Array.isArray(result)) { - log_me(logpath, result, false); - } - log_me(logpath, 'Exported icons imported', false); - return import_symbolication_files(process_dir); //copy symbolication files - }) - .then(function(result) { - - if (Array.isArray(result)) { - log_me(logpath, result, false); - } - log_me(logpath, 'Symbolication folder imported', false); - return self.clean_up_data('import', foldername, true); //delete files - }) - .then(function() { - log_me(logpath, 'Cleanup sucessfull', false); - report_import(params, "systemlogs.action.import_finished", "finished", foldername); - }, - function(err) { - log_me(logpath, err.message, true); - report_import(params, err.message, "failed", foldername); - } - ).catch(err => { - report_import(params, err.message, "failed", foldername); - }); - - }; - this.import_data = function(my_file, my_params, logpath, passed_log, foldername) { - my_logpath = logpath; - params = my_params; - if (passed_log) { - log = passed_log; - } - - log_me(my_logpath, 'Starting import process', false); - var dir = path.resolve(__dirname, './../import'); - - if (!fs.existsSync(dir)) { - try { - fs.mkdirSync(dir, 484); - } - catch (err) { - log_me(logpath, err.message, true); - } - } - - try { - fs.mkdirSync(path.resolve(__dirname, './../import/' + foldername), 484); - } - catch (err) { - log_me(logpath, err.message, true); - } - - uploadFile(my_file) - .then(function() { - log_me(logpath, 'File uploaded sucessfully', false); - import_process(path.resolve(__dirname, './../import/' + my_file.name), logpath, foldername); - }).catch(err => { - report_import(params, err.message, "failed", foldername); - }); - - }; -}; diff --git a/plugins/data_migration/api/jobs/migrate.js b/plugins/data_migration/api/jobs/migrate.js deleted file mode 100644 index 78d9b24bfb4..00000000000 --- a/plugins/data_migration/api/jobs/migrate.js +++ /dev/null @@ -1,82 +0,0 @@ -'use strict'; - -const job = require('../../../../api/parts/jobs/job.js'), - retry = require('../../../../api/parts/jobs/retry.js'), - log = require('../../../../api/utils/log.js')('job:data_migration:migrate'), - MigrationResource = require('../parts/resource.js'); - -/** class - ValidateJob */ -class MigrateJob extends job.TransientJob { - /** - * constructor - * @param {string} name - job name - * @param {object} data - job data - */ - constructor(name, data) { - super(name, data); - } - - /** - * Prepare job - * - * @returns {Promise} promise, resolved(always) - */ - prepare(/*manager, db*/) { - return Promise.resolve(); - } - - /** - * Resource name - * - * @returns {string} 'migration' - */ - resourceName() { - return 'migration'; - } - - /** - * Create resource - * - * @param {string} _id - id - * @param {string} name - name - * @returns {object} MigrationResource - */ - createResource(_id, name) { - // TODO: custom data - log.d('creating resource in migrate.js'); - return new MigrationResource(_id, name, {exportid: this.data.exportid}, this.db()); - } - - /** - * Release Resource - * @param {object} resource to call on - * @returns {Promise} result on close - */ - releaseResource(resource) { - return resource.close(); - } - - /** - * Retry policy for this job - probably no retries needed - * @returns {object} retry policy - */ - retryPolicy() { - return new retry.NoRetryPolicy(); - } - - /** - * Main job function - * @return {Any} not used - */ - async run() { - log.i('[%d] running migration %j', process.pid, this.data); - - let result = await this.resource.migrate(['a', 'b', 'c']); - - log.i('[%d] migration done with %j', process.pid, result); - - return result; - } -} - -module.exports = MigrateJob; \ No newline at end of file diff --git a/plugins/data_migration/api/parts/resource.js b/plugins/data_migration/api/parts/resource.js deleted file mode 100644 index 50ec638f958..00000000000 --- a/plugins/data_migration/api/parts/resource.js +++ /dev/null @@ -1,82 +0,0 @@ -const res = require('../../../../api/parts/jobs/resource.js'), - log = require('../../../../api/utils/log.js')('job:data_migration:resource:' + process.pid); -var Migrator = require("./../data_migration_helper.js"); - -/** Migration resource class */ -class MigrationResource extends res.Resource { - /** - * Constroctor - * @param {string} _id - id - * @param {string} name -name - * @param {object} args - args - * @param {object} db - db connection - */ - constructor(_id, name, args, db) { - super(_id, name); - this.db = db; - // TODO: custom data - this.exportid = args.exportid; - log.d('[%d]: Initializing data_migration resource with %j / %j / %j', process.pid, _id, name, args); - } - - /** - * Open resource: prepare some data, open connections, etc. - * @returns {Promise} promise - */ - open() { - log.d('[%s]: Opening %j', this._id, this.exportid); - this.opened(); //to give call that i am open (mandatory) - return Promise.resolve(); - } - - /** - * Close resource: release data, close connections, etc. - * @returns {Promise} promise - */ - close() { - log.d('[%s]: Closing %j', this._id, this.exportid); - return Promise.resolve(); - } - - /** - * Some custom method to call from job - * @param {object} stuff - message - * @returns {Promise} promise - */ - migrate(stuff) { - log.d('migrating: %j', stuff); - console.log("running export"); - return new Promise((resolve/*, reject*/) => { - var migration_helper = new Migrator(this.db); - migration_helper.runExportFunction(this.exportid, this.db, function(/*err1, res1*/) { - log.d("migration exited"); - - resolve(); - migration_helper.closeAssets(); - }); - }); - } - - /** - * Keep-alive / cheking resource is still online - * @returns {Promise} - promise - */ - checkActive() { - return new Promise((resolve) => { - log.d('checkActive'); - setTimeout(() => { - resolve(true); - }, 2000); - }); - } - - /** - * Don't terminate resource process on master exit - * @returns {boolean} always false - */ - canBeTerminated() { - return false; - } -} - -module.exports = MigrationResource; \ No newline at end of file diff --git a/plugins/data_migration/api/resource.js b/plugins/data_migration/api/resource.js deleted file mode 100644 index 6f002a388c0..00000000000 --- a/plugins/data_migration/api/resource.js +++ /dev/null @@ -1,71 +0,0 @@ -const res = require('../../../../api/parts/jobs/resource.js'), - log = require('../../../../api/utils/log.js')('job:data_migration:resource:' + process.pid); - -/** resource class */ -class MigrationResource extends res.Resource { - /** - * Constroctor - * @param {string} _id - id - * @param {string} name -name - * @param {object} args - args - * @param {object} db - db connection - */ - constructor(_id, name, args, db) { - super(_id, name); - this.db = db; - // TODO: custom data - this.x = this.data.x; - log.d('[%d]: Initializing data_migration resource with %j / %j / %j', process.pid, _id, name, args); - } - - /** - * Open resource: prepare some data, open connections, etc. - * @returns {Promise} promise - */ - open() { - log.d('[%s]: Opening %j', this._id, this.x); - return Promise.resolve(); - } - - /** - * Close resource: release data, close connections, etc. - * @returns {Promise} promise - */ - close() { - return Promise.resolve(); - } - - /** - * Some custom method to call from job - * @param {object} stuff - message - * @returns {Promise} promise - */ - migrate(stuff) { - log.d('migrating: %j', stuff); - console.log("running export"); - return Promise.resolve(); - } - - /** - * Keep-alive / cheking resource is still online - * @returns {Promise} - promise - */ - checkActive() { - return new Promise((resolve) => { - log.d('checkActive'); - setTimeout(() => { - resolve(true); - }, 2000); - }); - } - - /** - * Don't terminate resource process on master exit - * @returns {boolean} false - */ - canBeTerminated() { - return false; - } -} - -module.exports = MigrationResource; \ No newline at end of file diff --git a/plugins/data_migration/frontend/app.js b/plugins/data_migration/frontend/app.js deleted file mode 100644 index 7f9f0ed4084..00000000000 --- a/plugins/data_migration/frontend/app.js +++ /dev/null @@ -1,84 +0,0 @@ -var pluginOb = {}, - countlyConfig = require("../../../frontend/express/config"); -const fs = require('fs'); -const path = require('path'); -const common = require('../../../api/utils/common.js'); - -/** - * Validate a filename before using it as a path segment. - * @param {string} filename - filename - * @returns {string|null} safe filename or null - */ -function safeFilename(filename) { - filename = filename + ""; - if (filename && common.sanitizeFilename(filename) === filename) { - return filename; - } - return null; -} - -(function(plugin) { - plugin.init = function(app, countlyDb) { - app.get(countlyConfig.path + '/data-migration/download', function(req, res) { - if (req.session && req.session.gadm) { - //asked by query id - if (req.query && req.query.id) { - var exportid = safeFilename(req.query.id); - if (!exportid) { - res.status(400).send('Invalid export file'); - return; - } - countlyDb.collection("data_migrations").findOne({_id: exportid}, function(err, data) { - if (!err && data) { - var myfile = common.resolvePathInBase(path.resolve(__dirname, './../export'), exportid + '.tar.gz'); - if (data.export_path && data.export_path !== '') { - var customExportPath = path.resolve(data.export_path + ""); - if (path.basename(customExportPath) !== exportid + '.tar.gz') { - res.status(400).send('Invalid export file'); - return; - } - myfile = customExportPath; - } - if (fs.existsSync(myfile)) { - res.set('Content-Type', 'application/x-gzip'); - res.download(myfile, req.query.id + '.tar.gz'); - return; - } - else { - res.status(404).send('Export file not found'); - return; - } - } - else if (err) { - res.status(500).send('Error loading export file'); - return; - } - else { - res.status(404).send('Export file not found'); - return; - } - }); - } - if (req.query && req.query.logfile) { - var logFilePath = safeFilename(req.query.logfile) ? common.resolvePathInBase(path.resolve(__dirname, '../../../log'), req.query.logfile) : null; - if (!logFilePath) { - res.status(400).send('Invalid log file'); - return; - } - if (fs.existsSync(logFilePath)) { - res.set('Content-Type', 'text/plain'); - res.download(logFilePath, req.query.logfile); - return; - } - else { - res.status(404).send('Log file not found'); - return; - } - - } - } - }); - }; -}(pluginOb)); - -module.exports = pluginOb; diff --git a/plugins/data_migration/frontend/public/javascripts/countly.models.js b/plugins/data_migration/frontend/public/javascripts/countly.models.js deleted file mode 100644 index 25973372a66..00000000000 --- a/plugins/data_migration/frontend/public/javascripts/countly.models.js +++ /dev/null @@ -1,308 +0,0 @@ -/*global countlyCommon, CountlyHelpers, jQuery*/ -(function(countlyDataMigration, $) { - //we will store our data here - var _data = {}; - var _import_list = ""; - var _export_list = ""; - /* - * Initialization function. Loads configuration - */ - countlyDataMigration.initialize = function() { - return $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/o/datamigration/get_config", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - }, - success: function(json) { - if (json && json.result) { - _data = json.result; - } - }, - error: function() {} - }); - }; - /* - * Function loads all exports - */ - countlyDataMigration.loadExportList = function() { - return $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/o/datamigration/getmyexports", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - }, - success: function(json) { - if (json.result && Array.isArray(json.result)) { - for (var i = 0; i < json.result.length; i++) { - if (!json.result[i].apps) { - json.result[i].apps = []; - } - json.result[i].appnames = CountlyHelpers.appIdsToNames(json.result[i].apps); - - var dd = new Date(json.result[i].ts); - json.result[i].time = dd.toLocaleDateString("en-US") + ' ' + dd.toLocaleTimeString("en-US"); - json.result[i].step = jQuery.i18n.map["data-migration.step." + json.result[i].step]; - json.result[i].status_text = jQuery.i18n.map["data-migration.status." + json.result[i].status]; - json.result[i].applist = json.result[i].apps.join(); - - if ((json.result[i].status === 'failed' || json.result[i].status === 'finished' || json.result[i].stopped === true) && json.result[i].can_download === true) { - json.result[i].can_sendExport = true; - } - else { - json.result[i].can_resend = false; - } - - if (json.result[i].stopped === false && (json.result[i].status !== 'failed' && json.result[i].status !== 'finished')) { - json.result[i].can_stop = true; - } - else { - json.result[i].can_stop = false; - } - } - _export_list = {result: "success", data: json.result}; - } - else { - _export_list = {result: "success", data: ""}; - } - }, - error: function(xhr, status, error) { - _export_list = {result: "error", data: {xhr: xhr, status: status, error: error}}; - } - }); - }; - - countlyDataMigration.loadImportList = function() { - return $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/o/datamigration/getmyimports", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - }, - success: function(json) { - if (json.result && typeof json.result === 'object') { - var my_imports = []; - for (var key in json.result) { - if (Object.prototype.hasOwnProperty.call(json.result, key)) { - json.result[key].key = key; - - if (json.result[key].last_update && json.result[key].last_update !== '') { - var dd = new Date(json.result[key].last_update); - json.result[key].last_update = dd.toLocaleDateString("en-US") + ' ' + dd.toLocaleTimeString("en-US"); - } - if (json.result[key].type === '') { - json.result[key].status_text = jQuery.i18n.map["data-migration.status.finished"]; - } - else { - json.result[key].status_text = jQuery.i18n.map["data-migration.status.progress"]; - } - - my_imports.push(json.result[key]); - } - } - - if (my_imports.length > 0) { - _import_list = {result: "success", data: my_imports}; - } - else { - _import_list = {result: "success", data: ""}; - } - - } - else { - _import_list = {result: "success", data: ""}; - } - - }, - error: function(xhr, status, error) { - _import_list = {result: "error", data: {xhr: xhr, status: status, error: error}}; - } - }); - }; - - countlyDataMigration.stopExport = function(exportid, callback) { - $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/i/datamigration/stop_export", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - "exportid": exportid - }, - success: function(json) { - if (callback) { - callback({result: "success", data: json.result}); - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - countlyDataMigration.deleteExport = function(exportid, callback) { - $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/i/datamigration/delete_export", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - "exportid": exportid - }, - success: function(json) { - if (callback) { - callback({result: "success", data: json.result}); - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - countlyDataMigration.deleteImport = function(exportid, callback) { - $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/i/datamigration/delete_import", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - "exportid": exportid - }, - success: function(json) { - if (callback) { - callback({result: "success", data: json.result}); - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - countlyDataMigration.saveImport = function(importData, callback) { - $.ajax({ - type: "POST", - url: countlyCommon.API_URL + "/i/datamigration/import", - dataType: 'multipart/form-data', - data: importData, - success: function(json) { - if (callback) { - callback({result: "success", data: json.result}); - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - countlyDataMigration.saveExport = function(exportData, callback) { - $.ajax({ - type: "POST", - url: countlyCommon.API_URL + "/i/datamigration/export", - //dataType: 'application/json', - data: exportData, - success: function(json) { - if (callback) { - if (json.result) { - callback({result: "success", data: json.result}); - } - else { - callback({result: "success", data: json}); - } - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - countlyDataMigration.createToken = function(callback) { - $.ajax({ - type: "POST", - url: countlyCommon.API_URL + "/o/datamigration/createimporttoken", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - }, - success: function(json) { - if (callback) { - callback({result: "success", data: json.result}); - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - countlyDataMigration.testConnection = function(token, address, callback) { - $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/o/datamigration/validateconnection", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - "server_token": token, - "server_address": address - }, - success: function(json) { - if (callback) { - callback({result: "success", data: json.result}); - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - countlyDataMigration.sendExport = function(exportid, token, address, rediret_traffic, callback) { - $.ajax({ - type: "GET", - url: countlyCommon.API_URL + "/i/datamigration/sendexport", - data: { - "app_id": countlyCommon.ACTIVE_APP_ID, - "server_token": token, - "server_address": address, - "exportid": exportid, - 'redirect_traffic': rediret_traffic - }, - success: function(json) { - if (callback) { - callback({result: "success", data: json.result}); - } - }, - error: function(xhr, status, error) { - if (callback) { - callback({result: "error", data: {xhr: xhr, status: status, error: error}}); - } - } - }); - }; - - //return data that we have - countlyDataMigration.getData = function() { - return _data; - }; - - countlyDataMigration.getExportList = function() { - return _export_list; - }; - - countlyDataMigration.getImportList = function() { - return _import_list; - }; - -}(window.countlyDataMigration = window.countlyDataMigration || {}, jQuery)); \ No newline at end of file diff --git a/plugins/data_migration/frontend/public/javascripts/countly.views.js b/plugins/data_migration/frontend/public/javascripts/countly.views.js deleted file mode 100644 index 6809148087a..00000000000 --- a/plugins/data_migration/frontend/public/javascripts/countly.views.js +++ /dev/null @@ -1,476 +0,0 @@ -/*global countlyVue, CV, countlyCommon, CountlyHelpers, countlyGlobal, countlyDataMigration, app,jQuery */ -(function() { - var FEATURE_NAME = 'data_migration'; - - var ImportsTab = countlyVue.views.create({ - template: CV.T("/data_migration/templates/imports-tab.html"), - props: {}, - mixins: [ - countlyVue.mixins.auth(FEATURE_NAME) - ], - data: function() { - return { - list: [], - importsTablePersistKey: 'imports_table_' + countlyCommon.ACTIVE_APP_ID, - isLoading: false - }; - }, - methods: { - refresh: function(force) { - this.loadImports(force); - }, - loadImports: function(forceLoading) { - if (forceLoading) { - this.isLoading = true; - } - var self = this; - countlyDataMigration.loadImportList() - .then(function(res) { - if (typeof res.result === "object") { - var finalArr = []; - for (var key in res.result) { - var element = res.result[key]; - finalArr.push(element); - } - self.list = finalArr; - } - else if (typeof res.result === "string") { - self.list = []; - } - self.isLoading = false; - }); - }, - handleCommand: function(command, scope, row) { - switch (command) { - case 'download-log': - window.location.href = "/data-migration/download?logfile=" + row.log; - break; - case 'delete-export': - var self = this; - CountlyHelpers.confirm(CV.i18n('data-migration.delete-export-confirm'), "popStyleGreen", function(result) { - if (!result) { - return true; - } - countlyDataMigration.deleteImport(row.key, function(res) { - if (res.result === 'success') { - self.loadImports(); - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.export-deleted') - }); - } - else { - CountlyHelpers.notify({ - type: 'error', - message: CV.i18n(res.data.xhr.responseJSON.result) - }); - } - }); - }, [], { title: CV.i18n('management-users.warning'), image: 'delete-exports' }); - break; - } - } - }, - created: function() { - this.loadImports(true); - } - }); - - var ExportsTab = countlyVue.views.create({ - template: CV.T("/data_migration/templates/exports-tab.html"), - props: {}, - mixins: [ - countlyVue.mixins.auth(FEATURE_NAME) - ], - data: function() { - return { - list: [], - exportsTablePersistKey: 'exports_table_' + countlyCommon.ACTIVE_APP_ID, - isLoading: false, - isGlobalAdmin: countlyGlobal.member.global_admin - }; - }, - methods: { - refresh: function(force) { - this.loadExports(force); - }, - loadExports: function(forceLoading) { - var self = this; - if (forceLoading) { - this.isLoading = true; - } - countlyDataMigration.loadExportList() - .then(function(res) { - if (typeof res.result === "object") { - self.list = res.result; - } - else if (typeof res.result === "string") { - self.list = []; - } - self.isLoading = false; - }); - }, - handleCommand: function(command, scope, row) { - var self = this; - switch (command) { - case 'download-log': - window.location.href = "/data-migration/download?logfile=" + row.log; - break; - case 'download-export': - window.location.href = "/data-migration/download?id=" + row._id; - break; - case 'resend': - countlyDataMigration.sendExport(row._id, row.server_token, row.server_address, row.redirect_traffic, function(res) { - if (res.result === 'success') { - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.export-started') - }); - } - else { - CountlyHelpers.notify({ - type: 'error', - message: CV.i18n(res.data.xhr.responseJSON.result) - }); - } - }); - break; - case 'delete-export': - CountlyHelpers.confirm(CV.i18n('data-migration.delete-export-confirm'), "popStyleGreen", function(result) { - if (!result) { - return true; - } - countlyDataMigration.deleteExport(row._id, function(res) { - if (res.result === 'success') { - self.loadExports(); - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.export-deleted') - }); - } - else { - CountlyHelpers.notify({ - type: 'error', - message: CV.i18n(res.data.xhr.responseJSON.result) - }); - } - }); - }, [], { title: CV.i18n('management-users.warning'), image: 'delete-exports' }); - break; - case 'stop-export': - countlyDataMigration.stopExport(row._id, function(res) { - if (res.result === 'success') { - self.loadExports(); - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.export-stopped') - }); - } - else { - CountlyHelpers.notify({ - type: 'error', - message: CV.i18n(res.data.xhr.responseJSON.result) - }); - } - }); - break; - } - } - }, - created: function() { - this.loadExports(true); - } - }); - - var ImportDrawer = countlyVue.views.create({ - template: CV.T("/data_migration/templates/drawer-import.html"), - props: { - settings: Object, - controls: Object - }, - data: function() { - return { - serverDomain: countlyGlobal.domain, - serverToken: '', - tokenGenerated: false, - importDropzoneOptions: { - createImageThumbnails: false, - autoProcessQueue: false, - addRemoveLinks: true, - acceptedFiles: 'application/gzip,application/x-gzip', - dictDefaultMessage: this.i18n('feedback.drop-message'), - dictRemoveFile: this.i18n('feedback.remove-file'), - url: "/i/datamigration/import", - paramName: "import_file", - params: { api_key: countlyGlobal.member.api_key, app_id: countlyCommon.ACTIVE_APP_ID } - }, - importDrawerCancelButtonLabel: CV.i18n('data-migration.cancel'), - importDrawerSaveButtonLabel: CV.i18n('data-migration.import-title') - }; - }, - methods: { - onSubmit: function(submitted) { - var self = this; - if (submitted.from_server === 1) { - countlyDataMigration.createToken(function(res) { - if (res.result === "success") { - self.serverToken = res.data; - self.tokenGenerated = true; - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.generated-token') - }); - self.importDrawerCancelButtonLabel = CV.i18n('data-migration.close'); - self.importDrawerSaveButtonLabel = ""; - } - else { - CountlyHelpers.notify({ - type: 'error', - message: CV.i18n(res.data.xhr.responseJSON.result) - }); - } - // set drawer pending state false - self.$refs.importDrawer.isSubmitPending = false; - }); - } - else { - self.$refs.importDropzone.processQueue(); - } - }, - onComplete: function(res) { - if (res.xhr.status === 200) { - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.import-started') - }); - } - else { - CountlyHelpers.notify({ - type: 'error', - message: CV.i18n(res.data.xhr.responseJSON.result) - }); - } - // set pending false and - // close drawer - this.$refs.importDrawer.isSubmitPending = false; - this.$refs.importDrawer.doClose(); - }, - onOpen: function() { - this.tokenGenerated = false; - this.importDrawerCancelButtonLabel = CV.i18n('data-migration.cancel'); - this.importDrawerSaveButtonLabel = CV.i18n('data-migration.create-token'); - }, - updateImportType: function(type) { - if (type === 1) { - this.importDrawerSaveButtonLabel = CV.i18n('data-migration.create-token'); - } - else { - this.importDrawerSaveButtonLabel = CV.i18n('data-migration.import-title'); - } - }, - copy: function(type) { - var text = document.querySelector('#data-migration-server-' + type + '-input'); - text.select(); - document.execCommand("copy"); - var message = ''; - if (type === 'token') { - message = 'data-migration.tokken-coppied-in-clipboard'; - } - else { - message = 'data-migration.address-coppied-in-clipboard'; - } - CountlyHelpers.notify({ - type: 'info', - message: CV.i18n(message) - }); - } - } - }); - - var ExportDrawer = countlyVue.views.create({ - template: CV.T("/data_migration/templates/drawer-export.html"), - props: { - settings: Object, - controls: Object - }, - data: function() { - return { - apps: [], - exportDrawerSaveButtonLabel: CV.i18n('data-migration.export-data-button') - }; - }, - methods: { - onClose: function() {}, - onSubmit: function(submitted) { - var API_KEY = countlyGlobal.member.api_key; - var APP_ID = countlyCommon.ACTIVE_APP_ID; - - var requestData = submitted; - requestData.api_key = API_KEY; - requestData.app_id = APP_ID; - requestData.apps = submitted.apps.join(","); - requestData.aditional_files = requestData.aditional_files ? 1 : 0; - requestData.redirect_traffic = requestData.redirect_traffic ? 1 : 0; - - countlyDataMigration.saveExport(requestData, function(res) { - if (res.result === "success") { - if (requestData.only_export === 2) { - var data = res.data; - //pack data and download - var blob = new Blob([data], { type: 'application/x-sh' }); - var url = URL.createObjectURL(blob); - var a = document.createElement('a'); - a.href = url; - a.download = 'export_commands.sh'; - document.body.appendChild(a); - a.click(); - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.download-auto') - }); - } - else { - CountlyHelpers.notify({ - type: 'success', - message: CV.i18n('data-migration.export-started') - }); - } - } - else { - CountlyHelpers.notify({ - type: 'error', - message: CV.i18n(res.data.xhr.responseJSON.result) - }); - } - }); - }, - onOpen: function() {} - }, - created: function() { - var apps = Object.keys(countlyGlobal.apps); - for (var i = 0; i < apps.length; i++) { - this.apps.push({ - label: countlyGlobal.apps[apps[i]].name, - value: countlyGlobal.apps[apps[i]]._id - }); - } - - this.apps.sort(function(a, b) { - const aLabel = a?.label || ''; - const bLabel = b?.label || ''; - const locale = countlyCommon.BROWSER_LANG || 'en'; - - if (aLabel && bLabel) { - return aLabel.localeCompare(bLabel, locale, { numeric: true }) || 0; - } - - // Move items with no label to the end - if (!aLabel && bLabel) { - return 1; - } - - if (aLabel && !bLabel) { - return -1; - } - - return 0; - }); - } - }); - - var DataMigrationMain = countlyVue.views.create({ - template: CV.T("/data_migration/templates/main.html"), - components: { - 'import-drawer': ImportDrawer, - 'export-drawer': ExportDrawer - }, - mixins: [ - countlyVue.mixins.hasDrawers("import"), - countlyVue.mixins.hasDrawers("export"), - countlyVue.mixins.auth(FEATURE_NAME) - ], - data: function() { - return { - dynamicTab: "imports", - tabs: [ - { - title: CV.i18n('data-migration.imports'), - name: "imports", - component: ImportsTab - }, - { - title: CV.i18n('data-migration.exports'), - name: "exports", - component: ExportsTab - } - ], - drawerSettings: { - import: { - title: CV.i18n('data-migration.import-data') - }, - export: { - title: CV.i18n('data-migration.export-data') - } - } - }; - }, - methods: { - create: function(type) { - if (typeof type === "undefined") { - type = "import"; - } - var initialDrawerObject = { - import: { - import_file: "", - from_server: 1 - }, - export: { - target_path: "", - server_address: "", - server_token: "", - apps: [], // comma separated strings - only_export: 0, // 1 - aditional_files: 0, // 1 - redirect_traffic: 0 // 1 - } - }; - - this.openDrawer(type, initialDrawerObject[type]); - }, - handleCommand: function(command) { - this.create(command); - } - } - }); - - var DataMigrationMainView = new countlyVue.views.BackboneWrapper({ - component: DataMigrationMain - }); - - //register route - app.route('/manage/data-migration', 'datamigration', function() { - this.renderWhenReady(DataMigrationMainView); - }); - - - //switching apps. show message if redirect url is set - app.addAppSwitchCallback(function(appId) { - if (appId && countlyGlobal.apps[appId] && countlyGlobal.apps[appId].redirect_url && countlyGlobal.apps[appId].redirect_url !== "") { - var mm = "

" + jQuery.i18n.map["data-migration.app-redirected"].replace('{app_name}', countlyGlobal.apps[appId].name) + "

" + jQuery.i18n.map["data-migration.app-redirected-explanation"] + " " + countlyGlobal.apps[appId].redirect_url + "

" + jQuery.i18n.map["data-migration.app-redirected-remove"] + ""; - var msg = { - title: jQuery.i18n.map["data-migration.app-redirected"].replace('{app_name}', countlyGlobal.apps[appId].name), - message: mm, - info: jQuery.i18n.map["data-migration.app-redirected-remove"], - sticky: true, - clearAll: true, - type: "warning", - onClick: function() { - app.navigate("#/manage/apps", true); - } - }; - CountlyHelpers.notify(msg); - } - }); - - app.addMenu("management", {code: "data-migration", permission: FEATURE_NAME, url: "#/manage/data-migration", text: "data-migration.page-title", icon: '

', priority: 70}); -})(); diff --git a/plugins/data_migration/frontend/public/localization/data_migration.properties b/plugins/data_migration/frontend/public/localization/data_migration.properties deleted file mode 100644 index f50aad25f77..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration.properties +++ /dev/null @@ -1,172 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate data from one Countly server to another. -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As displayed in your browser. -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -data-migration.export-type-get-export-scripts = Get only database export commands -data-migration.download-auto = Your download will start automatically. -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied to the clipboard -data-migration.address-coppied-in-clipboard = Server address copied to the clipboard -data-migration.close-without-copy = If you close this tab, you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete the export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is invalid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is too large to upload to the server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_ar.properties b/plugins/data_migration/frontend/public/localization/data_migration_ar.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_ar.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_bg.properties b/plugins/data_migration/frontend/public/localization/data_migration_bg.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_bg.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_ca.properties b/plugins/data_migration/frontend/public/localization/data_migration_ca.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_ca.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_de.properties b/plugins/data_migration/frontend/public/localization/data_migration_de.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_de.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_el.properties b/plugins/data_migration/frontend/public/localization/data_migration_el.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_el.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_es.properties b/plugins/data_migration/frontend/public/localization/data_migration_es.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_es.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_et.properties b/plugins/data_migration/frontend/public/localization/data_migration_et.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_et.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_fr.properties b/plugins/data_migration/frontend/public/localization/data_migration_fr.properties deleted file mode 100644 index 1e907089829..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_fr.properties +++ /dev/null @@ -1,174 +0,0 @@ - -#titres -data-migration.plugin-title = Migration de données -data-migration.page-title = Migration de données -data-migration.plugin-description = Migrer les données d'un serveur Countly à un autre. -data-migration.import-export-button-title = Exporter ou importer des données -data-migration.export-data = Exporter des données -data-migration.import-data = Importer des données - -#formulaire d'exportation de données -data-migration.export-title = Exporter des données -data-migration.applications = Applications -data-migration.export-type = Type d'exportation -data-migration.export-type-transfer-label = Exporter et transférer les données vers un autre serveur Countly -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Exporter et télécharger les données -data-migration.export-type-download-description = -data-migration.server-token = Jeton du serveur -data-migration.server-address=Adresse du serveur -data-migration.server-token-description = Jeton généré dans le serveur cible "Formulaire d'importation" -data-migration.server-address-description= Comme affiché dans votre navigateur. -data-migration.export-data-button = Exporter les données -data-migration.send-export-button = Envoyer l'exportation -data-migration.test-connection = Tester la connexion -data-migration.export-other-path = Dossier d'exportation : -data-migration.export-additional-files = Exporter les symboles de crash -data-migration.redirect-traffic = Rediriger le trafic vers le nouveau serveur après la migration -data-migration.export-completed-unable-to-delete = Exportation terminée. Impossible de supprimer les fichiers -data-migration.export-type-get-export-scripts = Obtenir uniquement les commandes d'exportation de la base de données -data-migration.download-auto = Votre téléchargement commencera automatiquement. -#formulaire d'importation de données -data-migration.import-title = Importer des données -data-migration.import-type = Type d'importation -data-migration.import-from-another-server = Importer depuis un autre serveur -data-migration.import-type-token-label = Importer des données depuis un autre serveur -data-migration.import-type-token-description = Pour importer des données depuis un autre serveur, vous devez fournir au propriétaire du serveur source votre adresse serveur et le jeton que vous allez créer maintenant -data-migration.import-type-upload-label = Importer depuis un fichier d'exportation existant -data-migration.import-type-upload-description = -data-migration.select-file = Sélectionner le fichier -data-migration.import-from-file = Importer en téléchargeant un fichier précédemment exporté -data-migration.import-from-server = Importer des données depuis un autre serveur -data-migration.import-from-file-info = Parcourez votre PC pour trouver le fichier -data-migration.import-from-server-info = Vous devez générer un nouveau jeton et fournir au propriétaire du serveur source votre nom de domaine et le jeton d'accès. -data-migration.token = Jeton -data-migration.create-token = Créer un jeton -data-migration.create-another-token = Créer un autre jeton -data-migration.generated-token = Le jeton a été généré -data-migration.copy-url = Copier l'URL -data-migration.copy-token = Copier le jeton -data-migration.tokken-coppied-in-clipboard = Jeton du serveur copié dans le presse-papiers -data-migration.address-coppied-in-clipboard = Adresse du serveur copiée dans le presse-papiers -data-migration.close-without-copy = Si vous fermez cet onglet, vous ne pourrez plus copier ce jeton. Voulez-vous le fermer ? -data-migration.close-confirm-title = Fermer l'onglet ? -data-migration.cancel = Annuler -data-migration.continue-and-close = Continuer et fermer -data-migration.close = Fermer -data-migration.upload-title = Télécharger les fichiers exportés -data-migration.drag-drop = Glissez-déposez les fichiers ici -data-migration.browse = parcourir -data-migration.upload-description = Téléchargez votre exportation au format archive .tar.gz -data-migration.drag-drop-unable = Impossible de glisser-déposer les fichiers. - - -#tables -data-migration.my-exports = Mes exportations -data-migration.my-imports = Mes importations -data-migration.imports = Importations -data-migration.exports = Exportations -data-migration.table.app-name = Applications -data-migration.table.step = Étape -data-migration.table.status = Statut -data-migration.table.last-update = Dernière mise à jour -data-migration.step.exporting = exportation -data-migration.step.packing = Emballage -data-migration.step.importing = Importation sur le serveur cible -data-migration.step.sending = Envoi -data-migration.step.awaiting_reply = Importation sur le serveur cible -data-migration.status.failed = échec -data-migration.status.finished = terminé -data-migration.status.complete = complet -data-migration.status.progress = en cours -data-migration.no-imports = Vous n'avez aucune importation -data-migration.no-exports = Vous n'avez aucune exportation -data-migration.no-export-id-given = Aucun identifiant d'exportation fourni -#actions du tableau export/import -data-migration.download-log = Télécharger le journal -data-migration.delete-log = Supprimer le journal -data-migration.download-export = Télécharger l'exportation -data-migration.delete-export = Supprimer l'exportation -data-migration.export-deleted = Fichiers d'exportation supprimés -data-migration.delete-import = Supprimer l'importation -data-migration.yes-delete-export = Oui, supprimer les fichiers -data-migration.stop-export = Arrêter l'exportation -data-migration.resend-export = Envoyer l'exportation -#confirmations, notifications -data-migration.delete-log-confirm = Voulez-vous vraiment supprimer le fichier journal ? -data-migration.delete-export-confirm = Voulez-vous vraiment supprimer les fichiers d'exportation ? -data-migration.delete-import-confirm = Voulez-vous vraiment supprimer les fichiers d'exportation et le fichier journal ? -data-migration.app-redirected = L'application ({app_name}) est redirigée. -data-migration.app-redirected-explanation = Tous les appels SDK entrants pour cette application sont redirigés vers le serveur distant : -data-migration.app-redirected-remove = Cliquez pour accéder aux paramètres de l'application - -#form -data-migration.please-wait = Veuillez patienter -data-migration.creating-new-token = Création d'un nouveau jeton.... -data-migration.complete = Terminé -data-migration.new-token-created = Nouveau jeton créé -data-migration.error = Erreur -data-migration.unable-create-token = Impossible de générer le jeton - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Échec de l'envoi. L'adresse du serveur cible n'est pas valide -data-migration.you-have-valid-export-failed-in-sending = Vous avez une exportation valide qui a échoué lors de l'envoi -data-migration.exportid_not_provided = Veuillez fournir un identifiant d'exportation -data-migration.export_not_found = Exportation avec l'identifiant donné introuvable -data-migration.status-missing = Statut manquant -data-migration.token_missing = Jeton manquant -data-migration.address_missing = Adresse manquante -data-migration.invalid-app-id = Identifiant d'application invalide -data-migration.no_app_ids = Veuillez fournir au moins un identifiant d'application pour exporter les données. -data-migration.some_bad_ids = Vous n'avez aucune application avec les identifiants donnés : -data-migration.invalid_app_id = L'identifiant d'application donné n'est pas valide : -data-migration.apps_not_found = Vous n'avez aucune application pour exporter des données. -data-migration.already-running-exporting-process = Processus d'exportation déjà en cours -data-migration.you-have-already-exported-data = Vous avez déjà exporté des données. -data-migration.export_in_process = Vous avez un processus d'exportation en cours avec les mêmes données. -data-migration.export_files_missing = Vous n'avez aucun fichier d'exportation. Veuillez démarrer un nouveau processus d'exportation. -data-migration.export_before_failed = Vous avez déjà essayé d'exporter des fichiers. L'importation de données échoue sur l'autre serveur. -data-migration.existing_process = Vous avez un processus d'exportation en cours pour les mêmes applications. Si vous voulez démarrer un nouveau processus, veuillez supprimer les données du précédent. -data-migration.failed-generate-scripts = Échec de la génération des scripts d'exportation -data-migration.failed-sending = Votre exportation précédente a échoué lors de la phase d'envoi. Vous pouvez essayer de renvoyer les données ou supprimer les données précédemment exportées et démarrer un nouveau processus d'exportation. -data-migration.export-stopped = Le processus d'exportation est arrêté. Vous pouvez supprimer les données et démarrer un nouveau processus. -data-migration.export-started = Le processus d'exportation a démarré avec succès. -data-migration.target-server-not-valid= L'adresse du serveur cible n'est pas valide -data-migration.enter-your-server-address = Saisir l'adresse de votre serveur -data-migration.enter-your-server-token = Saisir le jeton de votre serveur -data-migration.enter-your-export-folder = Saisir votre dossier d'exportation -data-migration.connection-is-valid = La connexion est valide -data-migration.invalid-server-path = Chemin invalide. Le serveur Countly est accessible, mais il semble que le plugin de migration de données ne soit pas activé dessus. -data-migration.export-already-failed = L'exportation a déjà échoué -data-migration.export-already-finished = L'exportation est déjà terminée -data-migration.export-already-stopped = Le processus d'exportation est arrêté -data-migration.export-already-sent = Les données ont déjà été envoyées -data-migration.export-already-done = Il y a déjà une exportation terminée pour la(les) même(s) application(s). Pour démarrer un nouveau processus d'exportation, le précédent doit être supprimé. -data-migration.exportid-missing= Identifiant d'exportation manquant -data-migration.invalid-exportid = Identifiant d'exportation invalide -data-migration.unable-to-remove-directory = Impossible de supprimer le répertoire -data-migration.unable-to-copy-file = Impossible de copier le fichier -data-migration.there-are-no-symbolication-files = Il n'y a aucun fichier de symbolisation de crash -#error messages(import) -data-migration.there-is-no-data-to-insert = Il n'y a aucune donnée à insérer -data-migration.import-started = Le processus d'importation a démarré avec succès -data-migration.import-file-missing = Le fichier d'importation est manquant. -data-migration.import-process-exist = Il y a un processus d'importation en cours sur le serveur cible avec les mêmes applications. Effacez les données sur le serveur cible pour démarrer un nouveau processus d'importation. -data-migration.file-to-big-warning = Le fichier choisi dépasse la limitation du serveur pour la taille maximale de fichier. Veuillez modifier les paramètres de votre serveur pour permettre le téléchargement de fichiers plus volumineux. -data-migration.file-to-big-error = Le fichier est trop volumineux pour être téléchargé sur le serveur. Veuillez modifier les paramètres de votre serveur pour permettre le téléchargement de fichiers plus volumineux. -data-migration.badformat = Le fichier téléchargé doit être au format .tar.gz -data-migration.could-not-find-file = Impossible de trouver le fichier sur le serveur -data-migration.unable-to-delete-log-file = Impossible de supprimer le fichier journal -#systemlogs -systemlogs.action.export_finished = Exportation réussie -systemlogs.action.export_failed = Échec de l'exportation -systemlogs.action.import_finished_response_ok = Importation réussie -systemlogs.action.import_finished = Importation réussie -systemlogs.action.import_failed = Échec de l'importation -systemlogs.action.import_failed_response_failed = Échec de l'importation. Échec de la notification au serveur source. -systemlogs.action.import_failed_response_ok = Échec de l'importation. Serveur source notifié. -systemlogs.action.import_finished_response_failed = Importation réussie. Échec de la notification au serveur source concernant l'importation réussie. -systemlogs.action.app_redirected = Application redirigée -#for app redirect settings -management-applications.redirect_url= URL de redirection -management-applications.redirect_url.help = Les données entrantes pour cette application sont redirigées vers l'adresse donnée. Désactivez pour arrêter la redirection des données. -management-applications.table.redirect_not_set = Aucune -management-applications.table.remove_redirect = Supprimer la redirection diff --git a/plugins/data_migration/frontend/public/localization/data_migration_hu.properties b/plugins/data_migration/frontend/public/localization/data_migration_hu.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_hu.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_ir.properties b/plugins/data_migration/frontend/public/localization/data_migration_ir.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_ir.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_it.properties b/plugins/data_migration/frontend/public/localization/data_migration_it.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_it.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_ja.properties b/plugins/data_migration/frontend/public/localization/data_migration_ja.properties deleted file mode 100644 index c67a9c13e48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_ja.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = データマイグレーション -data-migration.page-title = データマイグレーション -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = データのエクスポートまたはインポート -data-migration.export-data = エクスポートデータ -data-migration.import-data = データのインポート - -#export data form -data-migration.export-title = エクスポートデータ -data-migration.applications = アプリ -data-migration.export-type = エクスポートタイプ -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = データのエクスポートとダウンロード -data-migration.export-type-download-description = -data-migration.server-token = サーバートークン -data-migration.server-address=サーバーアドレス -data-migration.server-token-description = 対象サーバー"インポート元"で生成されたトークン -data-migration.server-address-description= ブラウザに表示されているとおり -data-migration.export-data-button = データをエクスポート -data-migration.send-export-button = エクスポートを送信 -data-migration.test-connection = テスト接続 -data-migration.export-other-path = エクスポートフォルダー: -data-migration.export-additional-files = クラッシュ記号をエクスポート -data-migration.redirect-traffic = マイグレーション完了後に新しいサーバーへトラフィックをリダイレクトします -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = データのインポート -data-migration.import-type = インポートタイプ -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = 別のサーバーからデータをインポートする -data-migration.import-type-token-description = 別のサーバーからデータをインポートするには、貴社のサーバーアドレスおよび貴社が作成中のトークンでソースサーバーオーナーを提供する必要があります -data-migration.import-type-upload-label = 既存のエクスポートファイルからインポート -data-migration.import-type-upload-description = -data-migration.select-file = ファイルを選択 -data-migration.import-from-file = 以前エクスポートされたファイルのアップロードをインポート -data-migration.import-from-server = 別のサーバーからデータをインポートする -data-migration.import-from-file-info = PCでファイルを検索 -data-migration.import-from-server-info = 新しいトークンを生成して貴社のドメイン名とアクセストークンがあるソースサーバーのオーナーを提供してください。 -data-migration.token = トークン -data-migration.create-token = トークンの作成 -data-migration.create-another-token = 別のトークンを作成 -data-migration.generated-token = トークンが生成されました -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = サーバートークンがクリップボードにコピーされました -data-migration.address-coppied-in-clipboard = サーバーアドレスがクリップボードにコピーされました -data-migration.close-without-copy = このタブを閉じるとこのトークンをこれ以上コピーできなくなります。閉じてよろしいですか? -data-migration.close-confirm-title = タブを閉じますか? -data-migration.cancel = キャンセル -data-migration.continue-and-close = 続行して閉じる -data-migration.close = Close -data-migration.upload-title = エクスポート済みファイルをアップロード -data-migration.drag-drop = ここにファイルをドラッグしてドロップしてください -data-migration.browse = 参照 -data-migration.upload-description = エクスポートを .tar.gz アーカイブとしてアップロード -data-migration.drag-drop-unable = ファイルのドラッグとドロップができませんでした。 - -#tables -data-migration.my-exports = マイエクスポート -data-migration.my-imports = マイインポート -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = アプリ -data-migration.table.step = ステップ -data-migration.table.status = ステータス -data-migration.table.last-update = 最新の更新 -data-migration.step.exporting = エクスポート中 -data-migration.step.packing = 圧縮 -data-migration.step.importing = 対象サーバー上でのインポート -data-migration.step.sending = 送信中 -data-migration.step.awaiting_reply = 対象サーバー上でインポート中です -data-migration.status.failed = 失敗しました -data-migration.status.finished = 終了しました -data-migration.status.complete = 完了 -data-migration.status.progress = 進行 -data-migration.no-imports = インポートがありません -data-migration.no-exports = エクスポートがありません -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = ダウンロードログ -data-migration.delete-log = ログを削除 -data-migration.download-export = エクスポートをダウンロード -data-migration.delete-export = エクスポートを削除 -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = はい、ファイルを削除します -data-migration.stop-export = エクスポートを停止 -data-migration.resend-export = エクスポートを送信 -#confirms,notifications -data-migration.delete-log-confirm = ログファイルを本当に削除しますか? -data-migration.delete-export-confirm = エクスポートファイルを本当に削除しますか? -data-migration.delete-import-confirm = エクスポートファイルとログファイルを本当に削除しますか? -data-migration.app-redirected = アプリ ({app_name}) はリダイレクトされます。 -data-migration.app-redirected-explanation = このアプリに対する全ての受信SDKコールはリモートサーバーへリダイレクトされます。 -data-migration.app-redirected-remove = クリックしてアプリ設定に移動 - -#form -data-migration.please-wait = お待ちください -data-migration.creating-new-token = 新しいトークンを作成中.... -data-migration.complete = 完了 -data-migration.new-token-created = 新しいトークンが作成されました -data-migration.error = エラー -data-migration.unable-create-token = トークンを生成することができません - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = エクスポートIDを提供してください -data-migration.export_not_found = 提供されたIDのエクスポートが見つかりません -data-migration.status-missing = Status missing -data-migration.token_missing = トークンがありません -data-migration.address_missing = アドレスがありません -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = データをエクスポートするためにアプリIDを最低1つ提供してください。 -data-migration.some_bad_ids = 所定のIDのアプリはありません: -data-migration.invalid_app_id = 提供されたアプリIDが無効です: -data-migration.apps_not_found = データをエクスポートするアプリはありません。 -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = 同じデータを持つ進行中のエクスポートプロセスがあります。 -data-migration.export_files_missing = エクスポートファイルがありません。新しいエクスポートプロセスを開始してください。 -data-migration.export_before_failed = ファイルをすでにエクスポートしようとしました。データのインポートが別のサーバーで失敗しています。 -data-migration.existing_process = 同じアプリで進行中のエクスポートプロセスがあります。新しいプロセスを開始したい場合は、以前のものからデータを削除してください。 -data-migration.failed-generate-scripts = エクスポートスクリプトの生成に失敗しました -data-migration.failed-sending = 以前のエクスポートが送信フェーズで失敗しました。データの再送信または以前エクスポートされたデータの削除を試すか、新しいエクスポートプロセスを開始してください。 -data-migration.export-stopped = エクスポートプロセスが停止しました。データを削除して新しいプロセスを開始してください。 -data-migration.export-started = エクスポートプロセスが開始されました。 -data-migration.target-server-not-valid= 対象のサーバーアドレスが有効ではありません -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = 接続が有効です -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = エクスポートはすでに終了しました -data-migration.export-already-stopped = エクスポートプロセスが停止しました -data-migration.export-already-sent = データはすでに送信されています -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= エクスポートIDがありません -data-migration.invalid-exportid = 無効なエクスポートIDです -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = インポートプロセスが開始されました -data-migration.import-file-missing = インポートファイルがありません。 -data-migration.import-process-exist = 同じアプリの対象サーバーで進行中のインポートプロセスがあります。対象サーバーでデータを消去して新しいインポートプロセスを開始してください。 -data-migration.file-to-big-warning = 選択したファイルが最大ファイルサイズに関するサーバー制限を超過しています。大き目のファイルをアップロードできるようにサーバー設定を変更する必要があります。 -data-migration.file-to-big-error = サーバーへアップロードするにはファイルが大きすぎます。大き目のファイルをアップロードできるようにサーバー設定を変更する必要があります。 -data-migration.badformat = アップロード済みファイルは .tar.gz です -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = エクスポートに成功しました -systemlogs.action.export_failed = エクスポートに失敗しました -systemlogs.action.import_finished_response_ok = インポートに成功しました -systemlogs.action.import_finished = インポートに成功しました -systemlogs.action.import_failed = インポートに失敗しました -systemlogs.action.import_failed_response_failed = インポートに失敗しました。ソースサーバーへの通知に失敗しました。 -systemlogs.action.import_failed_response_ok = インポートに失敗しました。ソースサーバーへ通知しました。 -systemlogs.action.import_finished_response_failed = インポートに成功しました。インポートの成功についてソースサーバーへ通知することに失敗しました。 -systemlogs.action.app_redirected = アプリがリダイレクトされました -#for app redirect settings -management-applications.redirect_url= リダイレクトURL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = なし -management-applications.table.remove_redirect = リダイレクトを削除 diff --git a/plugins/data_migration/frontend/public/localization/data_migration_ko.properties b/plugins/data_migration/frontend/public/localization/data_migration_ko.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_ko.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_lv.properties b/plugins/data_migration/frontend/public/localization/data_migration_lv.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_lv.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_nl.properties b/plugins/data_migration/frontend/public/localization/data_migration_nl.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_nl.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_no.properties b/plugins/data_migration/frontend/public/localization/data_migration_no.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_no.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_pl.properties b/plugins/data_migration/frontend/public/localization/data_migration_pl.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_pl.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_pt.properties b/plugins/data_migration/frontend/public/localization/data_migration_pt.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_pt.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_ro.properties b/plugins/data_migration/frontend/public/localization/data_migration_ro.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_ro.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_ru.properties b/plugins/data_migration/frontend/public/localization/data_migration_ru.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_ru.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_sv.properties b/plugins/data_migration/frontend/public/localization/data_migration_sv.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_sv.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_tr.properties b/plugins/data_migration/frontend/public/localization/data_migration_tr.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_tr.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_uk.properties b/plugins/data_migration/frontend/public/localization/data_migration_uk.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_uk.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_vi.properties b/plugins/data_migration/frontend/public/localization/data_migration_vi.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_vi.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/localization/data_migration_zh.properties b/plugins/data_migration/frontend/public/localization/data_migration_zh.properties deleted file mode 100644 index 22b25003a48..00000000000 --- a/plugins/data_migration/frontend/public/localization/data_migration_zh.properties +++ /dev/null @@ -1,170 +0,0 @@ -#titles -data-migration.plugin-title = Data Migration -data-migration.page-title = Data Migration -data-migration.plugin-description = Migrate your data from one Countly server to another -data-migration.import-export-button-title = Export or import data -data-migration.export-data = Export data -data-migration.import-data = Import data - -#export data form -data-migration.export-title = Export data -data-migration.applications = Applications -data-migration.export-type = Export type -data-migration.export-type-transfer-label = Export and transfer data to another Countly server -data-migration.export-type-transfer-description = -data-migration.export-type-download-label = Export and download data -data-migration.export-type-download-description = -data-migration.server-token = Server token -data-migration.server-address=Server address -data-migration.server-token-description = Token generated in target server "Import form" -data-migration.server-address-description= As seen in browser -data-migration.export-data-button = Export data -data-migration.send-export-button = Send export -data-migration.test-connection = Test connection -data-migration.export-other-path = Export folder: -data-migration.export-additional-files = Export crash symbols -data-migration.redirect-traffic = Redirect traffic to new server after migration is completed -data-migration.export-completed-unable-to-delete = Export completed. Unable to delete files -#import data form -data-migration.import-title = Import data -data-migration.import-type = Import type -data-migration.import-from-another-server = Import from another server -data-migration.import-type-token-label = Import data from another server -data-migration.import-type-token-description = In order to import data from another server you must provide source server owner with your server address and the token you'll create now -data-migration.import-type-upload-label = Import from an existing export file -data-migration.import-type-upload-description = -data-migration.select-file = Select file -data-migration.import-from-file = Import uploading previously exported file -data-migration.import-from-server = Import data from another server -data-migration.import-from-file-info = Browse your PC for file -data-migration.import-from-server-info = You need to generate a new token and provide source server owner with your domain name and access token. -data-migration.token = Token -data-migration.create-token = Create token -data-migration.create-another-token = Create another token -data-migration.generated-token = Token has been generated -data-migration.copy-url = Copy URL -data-migration.copy-token = Copy Token -data-migration.tokken-coppied-in-clipboard = Server token copied into the clipboard -data-migration.address-coppied-in-clipboard = Server address copied into the clipboard -data-migration.close-without-copy = If you close this tab you won't be able to copy this token anymore. Do you want to close it? -data-migration.close-confirm-title = Close tab? -data-migration.cancel = Cancel -data-migration.continue-and-close = Continue and close -data-migration.close = Close -data-migration.upload-title = Upload exported files -data-migration.drag-drop = Drag & drop files here -data-migration.browse = browse -data-migration.upload-description = Upload your export as .tar.gz archive -data-migration.drag-drop-unable = Unable to drag and drop files. - -#tables -data-migration.my-exports = My exports -data-migration.my-imports = My Imports -data-migration.imports = Imports -data-migration.exports = Exports -data-migration.table.app-name = Applications -data-migration.table.step = Step -data-migration.table.status = Status -data-migration.table.last-update = Last update -data-migration.step.exporting = exporting -data-migration.step.packing = Packing -data-migration.step.importing = Importing on target server -data-migration.step.sending = Sending -data-migration.step.awaiting_reply = Importing on target server -data-migration.status.failed = failed -data-migration.status.finished = finished -data-migration.status.complete = complete -data-migration.status.progress = progress -data-migration.no-imports = You don't have any imports -data-migration.no-exports = You don't have any exports -data-migration.no-export-id-given = No exportid given -#export/import table - actions -data-migration.download-log = Download log -data-migration.delete-log = Delete log -data-migration.download-export = Download export -data-migration.delete-export = Delete export -data-migration.export-deleted = Export files deleted -data-migration.delete-import = Delete import -data-migration.yes-delete-export = Yes, delete files -data-migration.stop-export = Stop export -data-migration.resend-export = Send export -#confirms,notifications -data-migration.delete-log-confirm = Do you really want to delete the log file? -data-migration.delete-export-confirm = Do you really want to delete export files? -data-migration.delete-import-confirm = Do you really want to delete export files and the log file? -data-migration.app-redirected = App({app_name}) is redirected. -data-migration.app-redirected-explanation = All incoming SDK calls for this app are redirected to remote server: -data-migration.app-redirected-remove = Click to go to app settings - -#form -data-migration.please-wait = Please wait -data-migration.creating-new-token = Creating new token.... -data-migration.complete = Complete -data-migration.new-token-created = New token created -data-migration.error = Error -data-migration.unable-create-token = Unable to generate token - -#error messages(export) -data-migration.sending-failed-server-address-wrong = Sending failed. Target server address is not valid -data-migration.you-have-valid-export-failed-in-sending = You have valid export failed in sending state -data-migration.exportid_not_provided = Please provide exportid -data-migration.export_not_found = Export with given id not found -data-migration.status-missing = Status missing -data-migration.token_missing = Token missing -data-migration.address_missing = Address missing -data-migration.invalid-app-id = Invalid app id -data-migration.no_app_ids = Please provide at least one app id to export data. -data-migration.some_bad_ids = You don't have any apps with given ids: -data-migration.invalid_app_id = Given app id is/are not valid: -data-migration.apps_not_found = You don't have any apps to export data from. -data-migration.already-running-exporting-process = Already running exporting process -data-migration.you-have-already-exported-data = You have already exported data. -data-migration.export_in_process = You have an ongoing export process with the same data. -data-migration.export_files_missing = You don't have any export files. Please start a new export process. -data-migration.export_before_failed = You have already tried to export files. Data import is failing on the other server. -data-migration.existing_process = You have an ongoing export process for the same apps. If you want to start a new process please delete data from the previous one. -data-migration.failed-generate-scripts = Failed to generate export scripts -data-migration.failed-sending = Your previous export has failed in sending phase. You can try to resend data or delete previously exported data and start a new export process. -data-migration.export-stopped = Export process is stopped. You can delete data and start new process. -data-migration.export-started = Export process has successfully started. -data-migration.target-server-not-valid= Target server address is not valid -data-migration.enter-your-server-address = Enter your server address -data-migration.enter-your-server-token = Enter your server token -data-migration.enter-your-export-folder = Enter your export folder -data-migration.connection-is-valid = Connection is valid -data-migration.invalid-server-path = Invalid path. Countly server is reachable, but it looks like data migration plugin is not enabled on it. -data-migration.export-already-failed = Export already failed -data-migration.export-already-finished = Export already finished -data-migration.export-already-stopped = Export process stopped -data-migration.export-already-sent = Data has already been sent -data-migration.export-already-done = There is already done export for same app(apps). To start new export process previous one has to be deleted. -data-migration.exportid-missing= Missing export ID -data-migration.invalid-exportid = Invalid export ID -data-migration.unable-to-remove-directory = Unable to remove directory -data-migration.unable-to-copy-file = Unable to copy file -data-migration.there-are-no-symbolication-files = There are no symbolication crash files -#error messages(import) -data-migration.there-is-no-data-to-insert = There is no data for insert -data-migration.import-started = Import process started successfully -data-migration.import-file-missing = Import file is missing. -data-migration.import-process-exist = There is an ongoing import process on target server with same apps. Clear out the data on target server to start a new import process. -data-migration.file-to-big-warning = Chosen file exceeds server limitation for maximum file size. Please modify your server settings to allow uploading bigger files. -data-migration.file-to-big-error = File is to large to upload to server. Please modify your server settings to allow uploading bigger files. -data-migration.badformat = Uploaded file should be .tar.gz -data-migration.could-not-find-file = Could not find file on server -data-migration.unable-to-delete-log-file = Unable to delete log file -#systemlogs -systemlogs.action.export_finished = Export Successful -systemlogs.action.export_failed = Export Failed -systemlogs.action.import_finished_response_ok = Import Successful -systemlogs.action.import_finished = Import Successful -systemlogs.action.import_failed = Import Failed -systemlogs.action.import_failed_response_failed = Import Failed. Failed to notify source server. -systemlogs.action.import_failed_response_ok = Import Failed. Source server notified. -systemlogs.action.import_finished_response_failed = Import successful. Failed to notify source server about successful import. -systemlogs.action.app_redirected = App redirected -#for app redirect settings -management-applications.redirect_url= Redirect URL -management-applications.redirect_url.help = Incoming data for this app is redirected to given address. Disable to stop data redirection. -management-applications.table.redirect_not_set = None -management-applications.table.remove_redirect = Remove redirect diff --git a/plugins/data_migration/frontend/public/stylesheets/main.scss b/plugins/data_migration/frontend/public/stylesheets/main.scss deleted file mode 100644 index 3c76a8e6217..00000000000 --- a/plugins/data_migration/frontend/public/stylesheets/main.scss +++ /dev/null @@ -1,83 +0,0 @@ -.data_migration__main { - &__drawer-box { - border: 1px dashed #0166D6; - border-radius: 4px; - } - - &__link-text { - color: #0166D6; - cursor: pointer; - } - - &__description { - display: block; - color: #81868D; - } - - .cly-vue-status-tag { - // bulma margin classes doesn't have 6px value - // they are squares of 4 - margin-top: 6px; - } - - .cly-vue-more-options { - float: right; - } -} - -.data-migration__drawer { - &__full-input { - width: 100%; - } - - &__success-icon { - background-color: #12AF51; - color: white; - width: fit-content; - padding: 14px 16px; - font-size: 18px; - border-radius: 50%; - } - - &__title { - width: 25%; - } - - &__full { - width: 100%; - } - - &__half { - width: 49%; - } - - &__tokens-area { - background-color: #F8FAFD; - border-radius: 6px; - } - - &__token-generated { - height: 300px; - } - - &__dropzone { - border: 1px dashed #0166D6 !important; - border-radius: 4px; - } - - &__radio-item { - margin-right: 0px !important; - width: 50%; - height: 50px !important; - align-items: center; - padding: 0px 12px !important; - } - - &__export-radio-item { - margin-right: 0px !important; - margin-left: 0px !important; - height: 50px !important; - align-items: center; - padding: 0px 12px !important; - } -} \ No newline at end of file diff --git a/plugins/data_migration/frontend/public/templates/drawer-export.html b/plugins/data_migration/frontend/public/templates/drawer-export.html deleted file mode 100644 index 312edb12508..00000000000 --- a/plugins/data_migration/frontend/public/templates/drawer-export.html +++ /dev/null @@ -1,135 +0,0 @@ -
- - - -
\ No newline at end of file diff --git a/plugins/data_migration/frontend/public/templates/drawer-import.html b/plugins/data_migration/frontend/public/templates/drawer-import.html deleted file mode 100644 index f5f3de97f11..00000000000 --- a/plugins/data_migration/frontend/public/templates/drawer-import.html +++ /dev/null @@ -1,110 +0,0 @@ -
- - - -
\ No newline at end of file diff --git a/plugins/data_migration/frontend/public/templates/exports-tab.html b/plugins/data_migration/frontend/public/templates/exports-tab.html deleted file mode 100644 index 74de4784232..00000000000 --- a/plugins/data_migration/frontend/public/templates/exports-tab.html +++ /dev/null @@ -1,46 +0,0 @@ -
- - - -
\ No newline at end of file diff --git a/plugins/data_migration/frontend/public/templates/imports-tab.html b/plugins/data_migration/frontend/public/templates/imports-tab.html deleted file mode 100644 index 7c0c9f17a96..00000000000 --- a/plugins/data_migration/frontend/public/templates/imports-tab.html +++ /dev/null @@ -1,43 +0,0 @@ -
- - - -
\ No newline at end of file diff --git a/plugins/data_migration/frontend/public/templates/main.html b/plugins/data_migration/frontend/public/templates/main.html deleted file mode 100644 index 89c23ccbe86..00000000000 --- a/plugins/data_migration/frontend/public/templates/main.html +++ /dev/null @@ -1,32 +0,0 @@ -
- - - - - - -
\ No newline at end of file diff --git a/plugins/data_migration/install.js b/plugins/data_migration/install.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/package-lock.json b/plugins/data_migration/package-lock.json deleted file mode 100644 index 7e5f50926f9..00000000000 --- a/plugins/data_migration/package-lock.json +++ /dev/null @@ -1,12 +0,0 @@ -{ - "name": "countly-data-migration", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "countly-data-migration", - "version": "1.0.0" - } - } -} diff --git a/plugins/data_migration/package.json b/plugins/data_migration/package.json deleted file mode 100644 index 99c3b09d1ac..00000000000 --- a/plugins/data_migration/package.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "name": "countly-data-migration", - "title": "Data Migration", - "version": "1.0.0", - "description": "Migrate your data from one Countly server to another", - "author": "Anna Sosina", - "homepage": "https://count.ly/plugins/data-migration", - "repository": { - "type": "git", - "url": "http://github.com/Countly/countly-server.git" - }, - "bugs": { - "url": "http://github.com/Countly/countly-server/issues" - }, - "keywords": [ - "countly", - "analytics", - "mobile", - "plugins", - "migration" - ], - "private": true -} diff --git a/plugins/data_migration/scripts/getExportScripts.js b/plugins/data_migration/scripts/getExportScripts.js deleted file mode 100644 index 5192359c2f0..00000000000 --- a/plugins/data_migration/scripts/getExportScripts.js +++ /dev/null @@ -1,321 +0,0 @@ -//PUT in list of appIDS you want to export. If none put in - ALL apps will be exported. -var apps = []; -var export_crashes = true; //Set to false if you do not want to export crashes. -var filePath = "./myfolder"; //Path where to output files when export script runs. - -var plugins = require('./../../../plugins/pluginManager.js'); -var common = require('../../../api/utils/common.js'); -var crypto = require('crypto'); -var db; - -function getApps(countlyDb, callback) { - var query = {}; - if (apps.length > 0) { - var qq = []; - for (var z = 0; z < apps.length; z++) { - qq.push(countlyDb.ObjectID(apps[z])); - } - query = {"_id": {"$in": qq}}; - } - countlyDb.collection("apps").find(query).toArray(callback); -} - -var generate_events_scripts = function(data) { - return new Promise(function(resolve, reject) { - db.collection("events").find({_id: db.ObjectID(data.appid)}).toArray(function(err, res) { - if (err) { - reject(Error(err)); - return; - } - var scripts = []; - if (res && res.length > 0) { - for (var j = 0; j < res.length; j++) { - if (res[j].list && res[j].list.length > 0) { - for (var z = 0; z < res[j].list.length; z++) { - var eventCollName = "events" + crypto.createHash('sha1').update(res[j].list[z] + data.appid).digest('hex'); - //old data, can be removed once we are sure that we are only using merged events_data collection - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', eventCollName, '--out', data.my_folder, '--gzip']}); - - if (plugins.isPluginEnabled('drill')) { - eventCollName = "drill_events" + crypto.createHash('sha1').update(res[j].list[z] + data.appid).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs_drill, '--collection', eventCollName, '--out', data.my_folder, '--gzip']}); - } - } - } - } - //new data - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', "events_data", '-q', '{ "_id": {"$regex":"^' + data.appid + '_.*"}}', '--out', data.my_folder, '--gzip']}); - if (plugins.isPluginEnabled('drill')) { - scripts.push({cmd: 'mongodump', args: [...data.dbargs_drill, '--collection', "drill_events", '-q', '{ "a": "' + data.appid + '"}', '--out', data.my_folder, '--gzip']}); - } - } - resolve(scripts); - } - ); - }); -}; - -var generate_credentials_scripts = function(data) { - return new Promise(function(resolve, reject) { - db.collection("apps").findOne({_id: db.ObjectID(data.appid)}, function(err, res) { - if (err) { - reject(Error(err)); - return; - } - var cid = []; - if (res && res.plugins && res.plugins.push) { - if (res.plugins.push.a && res.plugins.push.a._id) { - cid.push('{"$oid":"' + res.plugins.push.a._id + '"}'); - } - - if (res.plugins.push.i && res.plugins.push.i._id) { - cid.push('{"$oid":"' + res.plugins.push.i._id + '"}'); - } - } - if (cid.length > 0) { - resolve([{cmd: 'mongodump', args: [...data.dbargs, '--collection', 'credentials', '-q', '{ "_id": {"$in":[' + cid.join(',') + ']}}', '--out', data.my_folder, '--gzip']}]); - } - else { - resolve([]); - } - }); - }); -}; - -var createScriptsForViews = function(data) { - return new Promise(function(resolve/*, reject*/) { - var scripts = []; - var appId = data.appid; - db.collection("views").findOne({'_id': db.ObjectID(appId)}, {}, function(err, viewInfo) { - - var colName = "app_viewdata" + crypto.createHash('sha1').update(appId).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', colName, '--out', data.my_folder, '--gzip']}); - if (viewInfo) { - for (let segKey in viewInfo.segments) { - colName = "app_viewdata" + crypto.createHash('sha1').update(segKey + appId).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', colName, '--out', data.my_folder, '--gzip']}); - } - } - colName = "app_viewdata" + crypto.createHash('sha1').update('platform' + appId).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...data.dbargs, '--collection', colName, '--out', data.my_folder, '--gzip']}); - resolve(scripts); - }); - - }); -}; - -var create_export_scripts = function(data) { - return new Promise(function(resolve, reject) { - var appid = data.appid; - var my_folder = data.my_folder; - - var scripts = []; - var dbargs = []; - var dbargs0 = []; - var countly_db_name = ""; - var db_params = plugins.getDbConnectionParams('countly'); - for (var p in db_params) { - dbargs.push("--" + p); - dbargs.push(db_params[p]); - if (p !== 'db') { - dbargs0.push("--" + p); - dbargs0.push(db_params[p]); - } - else { - countly_db_name = db_params[p]; - } - } - - var dbargs_drill = []; - db_params = plugins.getDbConnectionParams('countly_drill'); - for (var z in db_params) { - dbargs_drill.push("--" + z); - dbargs_drill.push(db_params[z]); - } - - var dbargs_out = []; - db_params = plugins.getDbConnectionParams('countly_out'); - for (var g in db_params) { - dbargs_out.push("--" + g); - dbargs_out.push(db_params[g]); - } - - db.collection("apps").findOne({_id: db.ObjectID(appid)}, function(err, res) { - if (err || !res) { - reject(Error("data-migration.invalid-app-id")); - } - else { - if (!res.redirect_url || res.redirect_url === "") { - scripts.push({cmd: 'mongodump', args: [...dbargs, "--collection", "apps", "-q", '{ "_id": {"$oid":"' + appid + '"}}', "--out", my_folder, '--gzip']}); - } - else { - //remove redirect field and add it after dump. - scripts.push({cmd: 'mongo', args: [countly_db_name, ...dbargs0, "--eval", 'db.apps.update({ "_id": ObjectId("' + appid + '")}, { "$unset": { "redirect_url": 1 } })']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, "--collection", "apps", "-q", '{ "_id": {"$oid":"' + appid + '"}}', "--out", my_folder, '--gzip']}); - scripts.push({cmd: 'mongo', args: [countly_db_name, ...dbargs0, "--eval", 'db.apps.update({ "_id": ObjectId("' + appid + '")}, { $set: { redirect_url: "' + res.redirect_url + '" } })']}); - } - - var appDocs = ['app_users', 'metric_changes', 'app_crashes', 'app_crashgroups', 'app_crashusers', 'app_nxret', 'app_viewdata', 'app_views', 'app_userviews', 'app_viewsmeta', 'blocked_users', 'campaign_users', 'consent_history', 'crashes_jira', 'event_flows', 'timesofday', 'feedback', 'push_', 'apm', "nps", "survey", "completed_surveys"]; - for (let j = 0; j < appDocs.length; j++) { - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', appDocs[j] + appid, '--out', my_folder, '--gzip']}); - } - - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'campaigndata', '-q', '{ "a": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'campaigns', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'crash_share', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'feedback_widgets', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'notes', '-q', '{ "app_id":"' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'messages', '-q', '{ "apps": {"$oid":"' + appid + '"}}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'cohortdata', '-q', '{ "a": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'cohorts', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'server_stats_data_points', '-q', '{ "a": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'consent_history', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'flow_schemas', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'flow_data', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'times_of_day', '-q', '{ "app_id": "' + appid + '"}', '--out', my_folder, '--gzip']}); - - //concurrent_users - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'concurrent_users_max', '-q', '{"$or":[{ "app_id": "' + appid + '"},{ "_id": {"$in" :["' + appid + '_overall", "' + appid + '_overall_new"]}}]}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'concurrent_users_alerts', '-q', '{ "app": "' + appid + '"}', '--out', my_folder, '--gzip']}); - - - var sameStructures = ["browser", "carriers", "cities", "consents", "crashdata", "density", "device_details", "devices", "langs", "sources", "users", "retention_daily", "retention_weekly", "retention_monthly", "server_stats_data_points"]; - - for (var k = 0; k < sameStructures.length; k++) { - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', sameStructures[k], '-q', '{ "_id": {"$regex": "^' + appid + '_.*" }}', '--out', my_folder, '--gzip']}); - } - if (dbargs_out && dbargs_out.length) { - scripts.push({cmd: 'mongodump', args: [...dbargs_out, '--collection', "ab_testing_experiments" + appid, '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs_out, '--collection', "remoteconfig_parameters" + appid, '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs_out, '--collection', "remoteconfig_conditions" + appid, '--out', my_folder, '--gzip']}); - } - - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'max_online_counts', '-q', '{"_id": {"$oid":"' + appid + '"}}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'top_events', '-q', '{ "app_id": {"$oid":"' + appid + '"}}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'events', '-q', '{ "_id": {"$oid":"' + appid + '"}}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'views', '-q', '{ "_id": {"$oid":"' + appid + '"}}', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'funnels', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'calculated_metrics', '-q', '{ "app": "' + appid + '" }', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'datamanager_transforms', '-q', '{ "app": "' + appid + '" }', '--out', my_folder, '--gzip']}); - - - //event Timeline data: - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'eventTimes' + appid, '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'timelineStatus', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder, '--gzip']}); - - //internal events - for (let j = 0; j < plugins.internalEvents.length; j++) { - let eventCollName = "events" + crypto.createHash('sha1').update(plugins.internalEvents[j] + appid).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', eventCollName, '--out', my_folder, '--gzip']}); - } - - if (plugins.isPluginEnabled('drill')) { - //export drill - var drill_events = plugins.internalDrillEvents; - - for (let j = 0; j < drill_events.length; j++) { - let eventCollName = "drill_events" + crypto.createHash('sha1').update(drill_events[j] + appid).digest('hex'); - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', eventCollName, '--out', my_folder, '--gzip']}); - } - - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', 'drill_bookmarks', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', 'drill_meta' + appid, '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs_drill, '--collection', 'drill_meta', '-q', '{ "_id": {"$regex": "^' + appid + '_.*" }}', '--out', my_folder, '--gzip']}); - } - //export symbolication files - if (data.aditional_files) { - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'app_crashsymbols' + appid, '--out', my_folder, '--gzip']}); - scripts.push({cmd: 'mongodump', args: [...dbargs, '--collection', 'symbolication_jobs', '-q', '{ "app_id": "' + appid + '" }', '--out', my_folder, '--gzip']}); - } - - //events sctipts - generate_events_scripts({appid: appid, my_folder: my_folder, dbargs: dbargs, dbargs_drill: dbargs_drill}) - .then( - function(result) { - if (result && Array.isArray(result)) { - scripts = scripts.concat(result); - } - - return generate_credentials_scripts({appid: appid, my_folder: my_folder, dbargs: dbargs, dbargs_drill: dbargs_drill}); - }) - .then( - function(result) { - if (result && Array.isArray(result)) { - scripts = scripts.concat(result); - } - - return createScriptsForViews({appid: appid, my_folder: my_folder, dbargs: dbargs, dbargs_drill: dbargs_drill}); - }) - .then( - function(result) { - if (result && Array.isArray(result)) { - scripts = scripts.concat(result); - } - return resolve(scripts); - }, - function(error) { - reject(Error(error.message)); - } - ).catch(err1 => { - reject(err1); - }); - } - }); - }); -}; - -Promise.all([plugins.dbConnection("countly"), plugins.dbConnection("countly_drill")]).then(function([countlyDb, drillDb]) { - common.drillDb = drillDb; - common.db = countlyDb; - db = countlyDb; - plugins.loadConfigs(countlyDb, function() { - getApps(countlyDb, async function(err, apps) { - if (err) { - console.log(err); - console.log("exiting"); - countlyDb.close(); - drillDb.close(); - return; - } - apps = apps || []; - - if (apps.length === 0) { - console.log("0 apps found"); - console.log("exiting"); - countlyDb.close(); - drillDb.close(); - return; - } - else { - try { - // Process apps sequentially with native promises - const results = []; - for (const app of apps) { - const result = await create_export_scripts({ - appid: app._id + "", - my_folder: filePath, - aditional_files: export_crashes - }); - results.push(result); - } - - for (var k = 0; k < results.length; k++) { - console.log("#Scripts for app " + apps[k]._id + ":"); - for (var j = 0; j < results[k].length; j++) { - console.log(results[k][j].cmd + " '" + results[k][j].args.join("' '") + "'"); - } - - } - console.log("# Completed generating export commands."); - countlyDb.close(); - drillDb.close(); - } - catch (error) { - console.log(error); - countlyDb.close(); - drillDb.close(); - } - } - }); - }); -}); diff --git a/plugins/data_migration/tests/b18e10498ec0f41a85bb8155ccd4a209819319a3.tar.gz b/plugins/data_migration/tests/b18e10498ec0f41a85bb8155ccd4a209819319a3.tar.gz deleted file mode 100644 index edfb626aa44..00000000000 Binary files a/plugins/data_migration/tests/b18e10498ec0f41a85bb8155ccd4a209819319a3.tar.gz and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7.tar.gz b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7.tar.gz deleted file mode 100644 index fac0af1c530..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7.tar.gz and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_device5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_device5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 3b694c53095..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_device5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_device5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_device5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index c4fe57fe650..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_device5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.apm_device5f589b9e8df39d7b85474921"},{"v":2,"key":{"expireAt":1},"name":"expireAt_1","ns":"countly.apm_device5f589b9e8df39d7b85474921","expireAfterSeconds":0,"background":true},{"v":2,"key":{"b":1},"name":"b_1","ns":"countly.apm_device5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"metric":1},"name":"metric_1","ns":"countly.apm_device5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"name":1},"name":"name_1","ns":"countly.apm_device5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"p":1},"name":"p_1","ns":"countly.apm_device5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"c":1},"name":"c_1","ns":"countly.apm_device5f589b9e8df39d7b85474921","background":true}],"uuid":"62e1da42b9cf4cc19563da65b7025d27"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_network5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_network5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 68d2a488a1d..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_network5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_network5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_network5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 57c4aaf46b8..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apm_network5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.apm_network5f589b9e8df39d7b85474921"},{"v":2,"key":{"expireAt":1},"name":"expireAt_1","ns":"countly.apm_network5f589b9e8df39d7b85474921","expireAfterSeconds":0,"background":true},{"v":2,"key":{"b":1},"name":"b_1","ns":"countly.apm_network5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"metric":1},"name":"metric_1","ns":"countly.apm_network5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"name":1},"name":"name_1","ns":"countly.apm_network5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"p":1},"name":"p_1","ns":"countly.apm_network5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"c":1},"name":"c_1","ns":"countly.apm_network5f589b9e8df39d7b85474921","background":true}],"uuid":"afbaa25ab612465a9896726daba00497"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashes5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashes5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 4dec2b56763..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashes5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashes5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashes5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 1be72cfd19e..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashes5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_crashes5f589b9e8df39d7b85474921"},{"v":2,"key":{"group":1},"name":"group_1","ns":"countly.app_crashes5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly.app_crashes5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"_fts":"text","_ftsx":1},"name":"name_text","ns":"countly.app_crashes5f589b9e8df39d7b85474921","background":true,"weights":{"name":1},"default_language":"english","language_override":"language","textIndexVersion":3}],"uuid":"90f695c9b3514507bef6a8d8d4adfc01"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashgroups5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashgroups5f589b9e8df39d7b85474921.bson deleted file mode 100644 index d634b2dec82..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashgroups5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashgroups5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashgroups5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 0a19cf95895..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashgroups5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921"},{"v":2,"key":{"name":1},"name":"name_1","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"os":1},"name":"os_1","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"reports":1},"name":"reports_1","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"users":1},"name":"users_1","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"lastTs":1},"name":"lastTs_1","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"latest_version":1},"name":"latest_version_1","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"groups":1},"name":"groups_1","ns":"countly.app_crashgroups5f589b9e8df39d7b85474921","background":true}],"uuid":"b5f0cc055ba04f16a3c4ae2742714abc"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashusers5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashusers5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 09314c1a3f9..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashusers5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashusers5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashusers5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index f0ab104d060..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_crashusers5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_crashusers5f589b9e8df39d7b85474921"},{"v":2,"key":{"group":1,"uid":1},"name":"group_1_uid_1","ns":"countly.app_crashusers5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"group":1,"crashes":1,"fatal":1},"name":"group_1_crashes_1_fatal_1","ns":"countly.app_crashusers5f589b9e8df39d7b85474921","sparse":true,"background":true},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly.app_crashusers5f589b9e8df39d7b85474921","background":true}],"uuid":"160d3378ede041faa5a8962404fdfa51"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_nxret5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_nxret5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 904612f1d79..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_nxret5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_nxret5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_nxret5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 6945693a707..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_nxret5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_nxret5f589b9e8df39d7b85474921"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly.app_nxret5f589b9e8df39d7b85474921"}],"uuid":"99f8678f045245aba55508e78a07d67b"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_users5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_users5f589b9e8df39d7b85474921.bson deleted file mode 100644 index ea60e9f417d..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_users5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_users5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_users5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index ee303ac07b4..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_users5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_users5f589b9e8df39d7b85474921"},{"v":2,"key":{"ls":-1},"name":"ls_-1","ns":"countly.app_users5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly.app_users5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"sc":1},"name":"sc_1","ns":"countly.app_users5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"lac":1,"ls":1},"name":"lac_1_ls_1","ns":"countly.app_users5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"tsd":1},"name":"tsd_1","ns":"countly.app_users5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"did":1},"name":"did_1","ns":"countly.app_users5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"name":1},"name":"name_1","ns":"countly.app_users5f589b9e8df39d7b85474921"},{"v":2,"key":{"hasInfo":1,"name":1},"name":"hasInfo_1_name_1","ns":"countly.app_users5f589b9e8df39d7b85474921"},{"v":2,"key":{"hasInfo":1,"sc":1},"name":"hasInfo_1_sc_1","ns":"countly.app_users5f589b9e8df39d7b85474921"},{"v":2,"key":{"ls":1},"name":"ls_1","ns":"countly.app_users5f589b9e8df39d7b85474921"},{"v":2,"key":{"hasInfo":1,"ls":1},"name":"hasInfo_1_ls_1","ns":"countly.app_users5f589b9e8df39d7b85474921"},{"v":2,"key":{"hasInfo":1,"tsd":1},"name":"hasInfo_1_tsd_1","ns":"countly.app_users5f589b9e8df39d7b85474921"},{"v":2,"key":{"_fts":"text","_ftsx":1},"name":"name_text_email_text","ns":"countly.app_users5f589b9e8df39d7b85474921","weights":{"email":1,"name":1},"default_language":"english","language_override":"language","textIndexVersion":3}],"uuid":"88ad3ea8f6a1475a9588fc0b2f136590"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_userviews5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_userviews5f589b9e8df39d7b85474921.bson deleted file mode 100644 index ae5fdc22268..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_userviews5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_userviews5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_userviews5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index a39650c7860..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_userviews5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_userviews5f589b9e8df39d7b85474921"}],"uuid":"2b4f8fd9b49748cbbeaa64d228e4c469"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdata2d48cda0bfdf8012ef66ef42784c1bf0b49319ef.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdata2d48cda0bfdf8012ef66ef42784c1bf0b49319ef.bson deleted file mode 100644 index ddf82ea7e98..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdata2d48cda0bfdf8012ef66ef42784c1bf0b49319ef.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdata2d48cda0bfdf8012ef66ef42784c1bf0b49319ef.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdata2d48cda0bfdf8012ef66ef42784c1bf0b49319ef.metadata.json deleted file mode 100644 index 67f38bf7db0..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdata2d48cda0bfdf8012ef66ef42784c1bf0b49319ef.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_viewdata2d48cda0bfdf8012ef66ef42784c1bf0b49319ef"}],"uuid":"979a043d3d564c019535fcedc11752b8"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdatad580e2db5aad5d3329fc57212f4bb8032e2af278.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdatad580e2db5aad5d3329fc57212f4bb8032e2af278.bson deleted file mode 100644 index fd7f1f13cf0..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdatad580e2db5aad5d3329fc57212f4bb8032e2af278.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdatad580e2db5aad5d3329fc57212f4bb8032e2af278.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdatad580e2db5aad5d3329fc57212f4bb8032e2af278.metadata.json deleted file mode 100644 index a8a3ab3502b..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewdatad580e2db5aad5d3329fc57212f4bb8032e2af278.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_viewdatad580e2db5aad5d3329fc57212f4bb8032e2af278"}],"uuid":"434322a75c714bf9893621fe49e87c39"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewsmeta5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewsmeta5f589b9e8df39d7b85474921.bson deleted file mode 100644 index a7e8775ca04..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewsmeta5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewsmeta5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewsmeta5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 7057ce0f4f2..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/app_viewsmeta5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.app_viewsmeta5f589b9e8df39d7b85474921"},{"v":2,"unique":true,"key":{"view":1},"name":"view_1","ns":"countly.app_viewsmeta5f589b9e8df39d7b85474921"}],"uuid":"dc83cdc4617a4da69a5221f673ff780f"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apps.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apps.bson deleted file mode 100644 index 5c8b432fc32..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apps.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apps.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apps.metadata.json deleted file mode 100644 index 3d3e1e6fe14..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/apps.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.apps"},{"v":1,"key":{"key":1},"name":"key_1","ns":"countly.apps"}],"uuid":"95c0df231408417bbbd5c8c923ecb3d6"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/browser.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/browser.bson deleted file mode 100644 index fdcb5e56375..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/browser.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/browser.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/browser.metadata.json deleted file mode 100644 index a21c65531f9..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/browser.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.browser"}],"uuid":"f8c1e32cf6864b789d96d80cd903cdcf"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/calculated_metrics.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/calculated_metrics.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/calculated_metrics.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/calculated_metrics.metadata.json deleted file mode 100644 index 145ec2ed5eb..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/calculated_metrics.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.calculated_metrics"},{"v":2,"unique":true,"key":{"app":1,"key":1},"name":"app_1_key_1","ns":"countly.calculated_metrics"}],"uuid":"51f72888c1e5425fa394d178b7a8f172"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaign_users5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaign_users5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 8469d8305b3..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaign_users5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaign_users5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaign_users5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index c246022d97f..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaign_users5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.campaign_users5f589b9e8df39d7b85474921"}],"uuid":"7a36077c3a8a4dd99b443efe5f162757"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigndata.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigndata.bson deleted file mode 100644 index a2121ba2ed7..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigndata.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigndata.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigndata.metadata.json deleted file mode 100644 index bb8ec1bbe9d..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigndata.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.campaigndata"}],"uuid":"22d000a746854b0788b0f3dd3493ec7f"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigns.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigns.bson deleted file mode 100644 index 44fad05014f..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigns.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigns.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigns.metadata.json deleted file mode 100644 index c67625f8fe8..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/campaigns.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.campaigns"}],"uuid":"a4734e70da1d4aa88f4274ab0b43c69c"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/carriers.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/carriers.bson deleted file mode 100644 index 8a1ad5e345b..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/carriers.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/carriers.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/carriers.metadata.json deleted file mode 100644 index 5525d2c32e1..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/carriers.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.carriers"}],"uuid":"d0520314369148c59d3c037d10bbaed9"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cities.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cities.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cities.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cities.metadata.json deleted file mode 100644 index a82d54eccab..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cities.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.cities"}],"uuid":"6b19d1dd9f994e81ae826268269514f2"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohortdata.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohortdata.bson deleted file mode 100644 index 1439feff3b6..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohortdata.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohortdata.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohortdata.metadata.json deleted file mode 100644 index a9c73bd6775..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohortdata.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.cohortdata"}],"uuid":"8aa04e7bec374954a369612d1e938822"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohorts.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohorts.bson deleted file mode 100644 index 1d6c64c3cdd..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohorts.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohorts.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohorts.metadata.json deleted file mode 100644 index 71685cc38ed..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/cohorts.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.cohorts"}],"uuid":"3b4eab7eb6974374bd90bf9755b3a9fa"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/concurrent_users_max.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/concurrent_users_max.bson deleted file mode 100644 index 55b4b432021..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/concurrent_users_max.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/concurrent_users_max.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/concurrent_users_max.metadata.json deleted file mode 100644 index 8eb9cf0a015..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/concurrent_users_max.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.concurrent_users_max"},{"v":2,"key":{"ea":1},"name":"ea_1","ns":"countly.concurrent_users_max","expireAfterSeconds":0},{"v":2,"key":{"app_id":1},"name":"app_id_1","ns":"countly.concurrent_users_max"}],"uuid":"f3fc7d23ffdc49c58b5a078954ceb314"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consent_history5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consent_history5f589b9e8df39d7b85474921.bson deleted file mode 100644 index ef42bbd209a..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consent_history5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consent_history5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consent_history5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 3ea67f97f5a..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consent_history5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.consent_history5f589b9e8df39d7b85474921"},{"v":2,"key":{"device_id":1},"name":"device_id_1","ns":"countly.consent_history5f589b9e8df39d7b85474921"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly.consent_history5f589b9e8df39d7b85474921"},{"v":2,"key":{"type":1},"name":"type_1","ns":"countly.consent_history5f589b9e8df39d7b85474921"},{"v":2,"key":{"ts":1},"name":"ts_1","ns":"countly.consent_history5f589b9e8df39d7b85474921"}],"uuid":"3c67056e36b042218a49be11a2589c77"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consents.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consents.bson deleted file mode 100644 index cb41ab37b1c..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consents.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consents.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consents.metadata.json deleted file mode 100644 index 146eb251714..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/consents.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.consents"}],"uuid":"343226c8452a49e1b7243def716cee30"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crash_share.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crash_share.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crash_share.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crash_share.metadata.json deleted file mode 100644 index 15c507951cd..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crash_share.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.crash_share"}],"uuid":"37375fbcc30d49ec873cf8c349651d73"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crashdata.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crashdata.bson deleted file mode 100644 index 348dfde9ee2..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crashdata.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crashdata.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crashdata.metadata.json deleted file mode 100644 index b7af2d762a7..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/crashdata.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.crashdata"}],"uuid":"13c5eef8892a45208a428363fa695ff1"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/credentials.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/credentials.bson deleted file mode 100644 index b393ec2caa2..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/credentials.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/credentials.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/credentials.metadata.json deleted file mode 100644 index 0b6629ac3e2..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/credentials.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.credentials"}],"uuid":"6b600568b1db4607b41d1629ba365735"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/density.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/density.bson deleted file mode 100644 index 31f88de962f..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/density.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/density.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/density.metadata.json deleted file mode 100644 index 477d228167b..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/density.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.density"}],"uuid":"b214736d79b2403fa6880492a358fc6c"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/device_details.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/device_details.bson deleted file mode 100644 index f0847157b65..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/device_details.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/device_details.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/device_details.metadata.json deleted file mode 100644 index 7effd28e141..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/device_details.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.device_details"}],"uuid":"076a778d007141db81212debac3811e3"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/devices.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/devices.bson deleted file mode 100644 index bc952408633..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/devices.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/devices.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/devices.metadata.json deleted file mode 100644 index 7865e674fad..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/devices.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.devices"}],"uuid":"cb46a33740204eed97a560b8711dcf5b"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/event_flows5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/event_flows5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 85435938eae..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/event_flows5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/event_flows5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/event_flows5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index fc189c65f9a..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/event_flows5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.event_flows5f589b9e8df39d7b85474921"}],"uuid":"6a3d1a1a8251461fac3028870b07d563"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events.bson deleted file mode 100644 index 7fe9468f091..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events.metadata.json deleted file mode 100644 index a1129f86bb8..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.events"}],"uuid":"63a2d0c8a88749d3a78b2158e24a2c46"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events095df1d117e767aa550f7f0985de163550421d8f.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events095df1d117e767aa550f7f0985de163550421d8f.bson deleted file mode 100644 index d066b5386e9..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events095df1d117e767aa550f7f0985de163550421d8f.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events095df1d117e767aa550f7f0985de163550421d8f.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events095df1d117e767aa550f7f0985de163550421d8f.metadata.json deleted file mode 100644 index 9455142c8c3..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events095df1d117e767aa550f7f0985de163550421d8f.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.events095df1d117e767aa550f7f0985de163550421d8f"}],"uuid":"23b84ffa777d48bf9a61db8696f18c41"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1c0dbc32b774a46b136547a81ccc65ea483dba23.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1c0dbc32b774a46b136547a81ccc65ea483dba23.bson deleted file mode 100644 index 434f7bfb897..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1c0dbc32b774a46b136547a81ccc65ea483dba23.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1c0dbc32b774a46b136547a81ccc65ea483dba23.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1c0dbc32b774a46b136547a81ccc65ea483dba23.metadata.json deleted file mode 100644 index 7628429447f..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1c0dbc32b774a46b136547a81ccc65ea483dba23.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.events1c0dbc32b774a46b136547a81ccc65ea483dba23"}],"uuid":"f5bff51fa1874a508b8a09c38e6eda6d"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1cbf9143622b823ffccbfbc60a2f94974d2d6748.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1cbf9143622b823ffccbfbc60a2f94974d2d6748.bson deleted file mode 100644 index 46b9224fbae..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1cbf9143622b823ffccbfbc60a2f94974d2d6748.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1cbf9143622b823ffccbfbc60a2f94974d2d6748.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1cbf9143622b823ffccbfbc60a2f94974d2d6748.metadata.json deleted file mode 100644 index cb21cf6ba9e..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events1cbf9143622b823ffccbfbc60a2f94974d2d6748.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.events1cbf9143622b823ffccbfbc60a2f94974d2d6748"}],"uuid":"a0826cd8820941169d1b1dd2abd4ac59"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.bson deleted file mode 100644 index fba17358baf..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.metadata.json deleted file mode 100644 index 619a856eec7..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.events2fabb923ac1fd5308e430abd3c6ea00d8578edc4"}],"uuid":"7364b2b622674a4780c68abd2d4bff57"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events5ad2f47b3114a378c1ebbe6724857e428448393c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events5ad2f47b3114a378c1ebbe6724857e428448393c.bson deleted file mode 100644 index 678c9ab92f4..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events5ad2f47b3114a378c1ebbe6724857e428448393c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events5ad2f47b3114a378c1ebbe6724857e428448393c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events5ad2f47b3114a378c1ebbe6724857e428448393c.metadata.json deleted file mode 100644 index f23cf6ba98d..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events5ad2f47b3114a378c1ebbe6724857e428448393c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.events5ad2f47b3114a378c1ebbe6724857e428448393c"}],"uuid":"f77073a6f86e466b87ebcd1a9fda90de"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events71529530dfb2194009459245ee0d2b99ea8a07ba.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events71529530dfb2194009459245ee0d2b99ea8a07ba.bson deleted file mode 100644 index 6dd69729028..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events71529530dfb2194009459245ee0d2b99ea8a07ba.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events71529530dfb2194009459245ee0d2b99ea8a07ba.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events71529530dfb2194009459245ee0d2b99ea8a07ba.metadata.json deleted file mode 100644 index 9ac36cb2487..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/events71529530dfb2194009459245ee0d2b99ea8a07ba.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.events71529530dfb2194009459245ee0d2b99ea8a07ba"}],"uuid":"801d7b2a5659498780d98d629417a022"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.bson deleted file mode 100644 index bd4725a35fb..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.metadata.json deleted file mode 100644 index d31b8232915..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c"}],"uuid":"0f6aedf6460244068bfabb2c8426409c"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsddda5d0b9266540914d7654ca78a07864f18780c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsddda5d0b9266540914d7654ca78a07864f18780c.bson deleted file mode 100644 index b60a323d390..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsddda5d0b9266540914d7654ca78a07864f18780c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsddda5d0b9266540914d7654ca78a07864f18780c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsddda5d0b9266540914d7654ca78a07864f18780c.metadata.json deleted file mode 100644 index fb7b0f17489..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsddda5d0b9266540914d7654ca78a07864f18780c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.eventsddda5d0b9266540914d7654ca78a07864f18780c"}],"uuid":"acb86558d6244321adb52535f504aef1"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsec76a844e312a94d248b2e262d5cfe050bdd790c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsec76a844e312a94d248b2e262d5cfe050bdd790c.bson deleted file mode 100644 index e5c5abff0a5..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsec76a844e312a94d248b2e262d5cfe050bdd790c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsec76a844e312a94d248b2e262d5cfe050bdd790c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsec76a844e312a94d248b2e262d5cfe050bdd790c.metadata.json deleted file mode 100644 index d7adcf73a2c..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/eventsec76a844e312a94d248b2e262d5cfe050bdd790c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.eventsec76a844e312a94d248b2e262d5cfe050bdd790c"}],"uuid":"9153996a804c491ea5b8cfbad3c7d2e0"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 15610b6db84..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 4f6188ecc1d..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.feedback5f589b9e8df39d7b85474921"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly.feedback5f589b9e8df39d7b85474921"},{"v":2,"key":{"ts":1},"name":"ts_1","ns":"countly.feedback5f589b9e8df39d7b85474921"}],"uuid":"7aec148b8d384f11bb76818199073f40"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback_widgets.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback_widgets.bson deleted file mode 100644 index 56bac95ed9e..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback_widgets.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback_widgets.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback_widgets.metadata.json deleted file mode 100644 index 0fd8faa334c..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/feedback_widgets.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.feedback_widgets"}],"uuid":"d784df03d3714b4d8c6c846d119a771a"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/funnels.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/funnels.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/funnels.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/funnels.metadata.json deleted file mode 100644 index 97ac9f42499..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/funnels.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.funnels"}],"uuid":"1b8d0c1aeba343438bcd5935720d5f92"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/langs.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/langs.bson deleted file mode 100644 index cce442f9883..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/langs.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/langs.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/langs.metadata.json deleted file mode 100644 index ab70cd7ba7d..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/langs.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.langs"}],"uuid":"8f8bd66afc9044ba800f7192143bdd08"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/max_online_counts.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/max_online_counts.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/max_online_counts.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/max_online_counts.metadata.json deleted file mode 100644 index 76b96644721..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/max_online_counts.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.max_online_counts"},{"v":2,"key":{"nu":1},"name":"nu_1","ns":"countly.max_online_counts"},{"v":2,"key":{"ou":1},"name":"ou_1","ns":"countly.max_online_counts"}],"uuid":"8d2d9782af0b4709bcdb6369edd9d0a9"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/messages.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/messages.bson deleted file mode 100644 index 0867e22934a..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/messages.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/messages.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/messages.metadata.json deleted file mode 100644 index d38feab9d3d..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/messages.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.messages"},{"v":1,"key":{"apps":1},"name":"apps_1","ns":"countly.messages"},{"v":1,"key":{"created":1},"name":"created_1","ns":"countly.messages"},{"v":1,"key":{"apps":1,"deleted":1},"name":"apps_1_deleted_1","ns":"countly.messages"},{"v":2,"key":{"result.status":1,"apps":1,"created":1,"source":1,"auto":1,"tx":1},"name":"result.status_1_apps_1_created_1_source_1_auto_1_tx_1","ns":"countly.messages","background":true}],"uuid":"9fd3af2a1736415c9cb8ccc33ef36689"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/metric_changes5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/metric_changes5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 55480b48cbe..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/metric_changes5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/metric_changes5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/metric_changes5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 21e0c19a565..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/metric_changes5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.metric_changes5f589b9e8df39d7b85474921"},{"v":2,"key":{"ts":-1},"name":"ts_-1","ns":"countly.metric_changes5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"ts":1,"cc.o":1},"name":"ts_1_cc.o_1","ns":"countly.metric_changes5f589b9e8df39d7b85474921","background":true},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly.metric_changes5f589b9e8df39d7b85474921","background":true}],"uuid":"9eed891a9dd54fabbf4b9aa90797eb07"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/notes.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/notes.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/notes.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/notes.metadata.json deleted file mode 100644 index 7c2be619704..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/notes.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.notes"}],"uuid":"99ed102565114192b050d2ffb9c4c80f"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/push_5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/push_5f589b9e8df39d7b85474921.bson deleted file mode 100644 index fb5aa6392ad..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/push_5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/push_5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/push_5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 64f5298b437..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/push_5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.push_5f589b9e8df39d7b85474921"}],"uuid":"bc36f13324ef469c81448ee3ad07ac61"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_daily.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_daily.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_daily.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_daily.metadata.json deleted file mode 100644 index d9808e0ec42..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_daily.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.retention_daily"},{"v":1,"key":{"d":1},"name":"d_1","ns":"countly.retention_daily"}],"uuid":"5471bd319a09422c990035f04293637f"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_monthly.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_monthly.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_monthly.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_monthly.metadata.json deleted file mode 100644 index 0e4658ca6c4..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_monthly.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.retention_monthly"},{"v":1,"key":{"d":1},"name":"d_1","ns":"countly.retention_monthly"}],"uuid":"85d4babe5ae74c68a16e4e4249fda433"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_weekly.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_weekly.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_weekly.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_weekly.metadata.json deleted file mode 100644 index 1702e344731..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/retention_weekly.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.retention_weekly"},{"v":1,"key":{"d":1},"name":"d_1","ns":"countly.retention_weekly"}],"uuid":"634ffafaca2a4391aa5816b19bdf81e3"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/server_stats_data_points.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/server_stats_data_points.bson deleted file mode 100644 index b57dea58bcf..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/server_stats_data_points.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/server_stats_data_points.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/server_stats_data_points.metadata.json deleted file mode 100644 index 92960af99dc..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/server_stats_data_points.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.server_stats_data_points"}],"uuid":"28bf0fce403f459f90688ea2392532ff"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/sources.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/sources.bson deleted file mode 100644 index 4b4cb39eeb9..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/sources.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/sources.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/sources.metadata.json deleted file mode 100644 index d2ebf44e529..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/sources.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.sources"}],"uuid":"ddd41386157348fc9ab7a63edc6a4c0e"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/symbolication_jobs.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/symbolication_jobs.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/symbolication_jobs.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/symbolication_jobs.metadata.json deleted file mode 100644 index 4d27e63ac5d..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/symbolication_jobs.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.symbolication_jobs"},{"v":1,"key":{"cd":1},"name":"cd_1","ns":"countly.symbolication_jobs","expireAfterSeconds":604800}],"uuid":"ce7694b9830843ecb5404e6ce41cc22b"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/timesofday5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/timesofday5f589b9e8df39d7b85474921.bson deleted file mode 100644 index 67a9b60119a..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/timesofday5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/timesofday5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/timesofday5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 2348ebad379..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/timesofday5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.timesofday5f589b9e8df39d7b85474921"}],"uuid":"4d0899e2ff724203a2017bd1c75e0fec"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/top_events.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/top_events.bson deleted file mode 100644 index 705552648f6..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/top_events.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/top_events.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/top_events.metadata.json deleted file mode 100644 index 05891f7600d..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/top_events.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.top_events"}],"uuid":"a10275b6ab2e48a98eccb24894e3a490"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/users.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/users.bson deleted file mode 100644 index 89ebd51c905..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/users.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/users.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/users.metadata.json deleted file mode 100644 index aaa1798d225..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/users.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly.users"}],"uuid":"ee4fadb449e44c7a866656c1b7e6084a"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/views.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/views.bson deleted file mode 100644 index afcab4a4197..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/views.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/views.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/views.metadata.json deleted file mode 100644 index 9762f34cad4..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly/views.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly.views"}],"uuid":"0cbe28bcbd8846ceaa52709ccdc40d64"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_bookmarks.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_bookmarks.bson deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_bookmarks.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_bookmarks.metadata.json deleted file mode 100644 index f2021df6a2b..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_bookmarks.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_bookmarks"}],"uuid":"61d8181df8bc4fdea65d5e6042133783"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8.bson deleted file mode 100644 index d8285d631a8..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8.metadata.json deleted file mode 100644 index 21857ef5ae3..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_events020d2b1b1a88056ce6d0078fcb8dfa98e41c42f8","background":true}],"uuid":"9a6e190f1be44b789fe42a7222fe6b13"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events095df1d117e767aa550f7f0985de163550421d8f.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events095df1d117e767aa550f7f0985de163550421d8f.bson deleted file mode 100644 index 8cbd5e64ccb..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events095df1d117e767aa550f7f0985de163550421d8f.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events095df1d117e767aa550f7f0985de163550421d8f.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events095df1d117e767aa550f7f0985de163550421d8f.metadata.json deleted file mode 100644 index 8d643be58f9..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events095df1d117e767aa550f7f0985de163550421d8f.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_events095df1d117e767aa550f7f0985de163550421d8f"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_events095df1d117e767aa550f7f0985de163550421d8f","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_events095df1d117e767aa550f7f0985de163550421d8f","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_events095df1d117e767aa550f7f0985de163550421d8f","background":true}],"uuid":"eef51d2435a248d782cc94ecf8f3e464"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748.bson deleted file mode 100644 index afc78411b1e..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748.metadata.json deleted file mode 100644 index 687679bc3b1..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_events1cbf9143622b823ffccbfbc60a2f94974d2d6748","background":true}],"uuid":"70d9188fc0fa4ea5965f0904d9e23577"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.bson deleted file mode 100644 index 82fed9b91a7..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.metadata.json deleted file mode 100644 index e2a58385f67..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_events2fabb923ac1fd5308e430abd3c6ea00d8578edc4","background":true}],"uuid":"bc9f956eedea429eb8a05f9cea887ac7"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f.bson deleted file mode 100644 index 5de474ed34d..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f.metadata.json deleted file mode 100644 index b4432ba6f03..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f"},{"v":2,"key":{"ts":1,"sg.name":1},"name":"ts_1_sg.name_1","ns":"countly_drill.drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f","background":true},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_events57cc14d00680690a9b5b3198f7b1eed775d6a13f","background":true}],"uuid":"33f32da4d6a84e61a1ae452ccd685df7"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events5ad2f47b3114a378c1ebbe6724857e428448393c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events5ad2f47b3114a378c1ebbe6724857e428448393c.bson deleted file mode 100644 index 4a4e768de8c..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events5ad2f47b3114a378c1ebbe6724857e428448393c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events5ad2f47b3114a378c1ebbe6724857e428448393c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events5ad2f47b3114a378c1ebbe6724857e428448393c.metadata.json deleted file mode 100644 index b249a31af0f..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_events5ad2f47b3114a378c1ebbe6724857e428448393c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_events5ad2f47b3114a378c1ebbe6724857e428448393c"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_events5ad2f47b3114a378c1ebbe6724857e428448393c","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_events5ad2f47b3114a378c1ebbe6724857e428448393c","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_events5ad2f47b3114a378c1ebbe6724857e428448393c","background":true}],"uuid":"3fcd73b6a94b4295853d77c5ad62164a"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc.bson deleted file mode 100644 index 214669bd8e7..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc.metadata.json deleted file mode 100644 index 7d64e4a2b40..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_eventsa60f5b8ea09ee00fba8393b0779385f3d6010adc","background":true}],"uuid":"b396751d6a9f4f3d935fef2273a64e22"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.bson deleted file mode 100644 index f4c473b9d73..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.metadata.json deleted file mode 100644 index 227180d2b48..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_eventsd705431e059f13cc71c8e42ad943b9c8c4a1ce7c","background":true}],"uuid":"5c535d1573f848499be46aad72e717a7"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsddda5d0b9266540914d7654ca78a07864f18780c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsddda5d0b9266540914d7654ca78a07864f18780c.bson deleted file mode 100644 index f9cc42d0cc6..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsddda5d0b9266540914d7654ca78a07864f18780c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsddda5d0b9266540914d7654ca78a07864f18780c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsddda5d0b9266540914d7654ca78a07864f18780c.metadata.json deleted file mode 100644 index ca16c53dfe9..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsddda5d0b9266540914d7654ca78a07864f18780c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_eventsddda5d0b9266540914d7654ca78a07864f18780c"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_eventsddda5d0b9266540914d7654ca78a07864f18780c","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_eventsddda5d0b9266540914d7654ca78a07864f18780c","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_eventsddda5d0b9266540914d7654ca78a07864f18780c","background":true}],"uuid":"f889bf1d92474f21a2785b211d921a3e"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f.bson deleted file mode 100644 index 5f3f3f81ff8..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f.metadata.json deleted file mode 100644 index eeb254fb9b6..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f"},{"v":2,"key":{"ts":1,"sg.name":1},"name":"ts_1_sg.name_1","ns":"countly_drill.drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f","background":true},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_eventse234b3ab81c7a0e13a08d93ac8b945b077509c7f","background":true}],"uuid":"c3689805e6d940468dcd4b10fed546be"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66.bson deleted file mode 100644 index ec586bc45c8..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66.metadata.json deleted file mode 100644 index f1f915e2105..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66","background":true},{"v":2,"key":{"ts":1,"sg.name":1,"uid":1,"c":1},"name":"ts_1_sg.name_1_uid_1_c_1","ns":"countly_drill.drill_eventseb1879cd38d151b52ba3a9a9e6f794851fbecd66","background":true}],"uuid":"de2e9e8c00834908b3a6055c30cdc43f"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c.bson deleted file mode 100644 index af58922c7f2..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c.metadata.json deleted file mode 100644 index d2eb72afc69..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_eventsec76a844e312a94d248b2e262d5cfe050bdd790c","background":true}],"uuid":"889fc6ea35f64fe9bb8270ea86e51139"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53.bson deleted file mode 100644 index 78acf251747..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53.metadata.json deleted file mode 100644 index af368bd4e7a..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53"},{"v":2,"key":{"uid":1},"name":"uid_1","ns":"countly_drill.drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53","background":true},{"v":2,"key":{"ts":1,"up.cc":1,"uid":1},"name":"ts_1_up.cc_1_uid_1","ns":"countly_drill.drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53","background":true},{"v":2,"key":{"ts":1,"m":1,"w":1,"d":1,"h":1,"uid":1,"c":1,"s":1,"dur":1},"name":"ts_1_m_1_w_1_d_1_h_1_uid_1_c_1_s_1_dur_1","ns":"countly_drill.drill_eventsf564b6c940ea315dc8942e58cca70bf7552f6d53","background":true}],"uuid":"b3915417444c4fd883f67c00fdbb2621"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_meta5f589b9e8df39d7b85474921.bson b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_meta5f589b9e8df39d7b85474921.bson deleted file mode 100644 index e5b1d7e2b56..00000000000 Binary files a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_meta5f589b9e8df39d7b85474921.bson and /dev/null differ diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_meta5f589b9e8df39d7b85474921.metadata.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_meta5f589b9e8df39d7b85474921.metadata.json deleted file mode 100644 index 7a3a4bd4f57..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/5f589b9e8df39d7b85474921/countly_drill/drill_meta5f589b9e8df39d7b85474921.metadata.json +++ /dev/null @@ -1 +0,0 @@ -{"options":{},"indexes":[{"v":2,"key":{"_id":1},"name":"_id_","ns":"countly_drill.drill_meta5f589b9e8df39d7b85474921"}],"uuid":"dbd4bfdba7ef400999afb72bf73ce6c2"} \ No newline at end of file diff --git a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/info.json b/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/info.json deleted file mode 100644 index db6ca88e272..00000000000 --- a/plugins/data_migration/tests/f9b35d90be5f2240eafced7c6bfdf130856cd0a7/info.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"f9b35d90be5f2240eafced7c6bfdf130856cd0a7","app_names":"DemoData","app_ids":"5f589b9e8df39d7b85474921"} \ No newline at end of file diff --git a/plugins/data_migration/tests/index.js b/plugins/data_migration/tests/index.js deleted file mode 100644 index dc7536703be..00000000000 --- a/plugins/data_migration/tests/index.js +++ /dev/null @@ -1,1275 +0,0 @@ -var request = require('supertest'); -var should = require('should'); -var testUtils = require("../../../test/testUtils"); -//var request = request(testUtils.url); -var request = request.agent(testUtils.url); -var path = require("path"); -var fs = require("fs"), - readline = require('readline'), - stream = require('stream'); -var os = require('os'); -var cp = require('child_process'); //call process -const exec = cp.exec; //for calling command line -const fse = require('fs-extra'); // delete folders -var crypto = require('crypto'); -var APP_KEY = ""; -var API_KEY_ADMIN = ""; -var APP_ID = ""; - -var testapp = ""; -var test_export_id = ""; - -//Validating empty upload+ logging in - -var TIMEOUT_FOR_DATA_MIGRATION_TEST = 10000; -var TIMES_FOR_DATA_MIGRATION_TEST = 10; - -var counter = 0; -var run_command = function(my_command, my_args, callback) { - - - exec('sudo ' + my_command + ' ' + my_args.join(" "), (error, stdout, stderr) => { - if (error) { - console.error(`exec error: ${error}`); - callback("err"); - } - else { - setTimeout(callback, 1000); - } - }); - -}; - - -function validate_log(exportid, callback) { - fs.readFile(path.resolve(__dirname, './../../../log/dm-export_' + exportid + '.log'), 'utf8', function(err, data) { - if (err) { - callback(err); - } - var good_errors = ["Failed: error counting countly_out.: Invalid namespace specified 'countly_out.", "Exited with error code: 1", "Failed: error counting countly.: Invalid namespace specified 'countly.'", "Failed: error counting countly_drill.: Invalid namespace specified 'countly_drill.'"]; - - const lines = data.split(/\r?\n/); - var badErrors = []; - // print all lines - lines.forEach((line) => { - if (line.indexOf("error") > -1) { - var bad = true; - for (var z = 0; z < good_errors.length; z++) { - if (line.indexOf(good_errors[z]) > -1) { - bad = false; - break; - } - } - if (bad) { - badErrors.push(line); - } - } - }); - - if (badErrors.length > 0) { - callback(badErrors.join(",")); - } - else { - callback(); - } - }); -} -function validate_files(exportid, apps, export_path, callback) { - var simpleDocs = ["apm_device{1}.bson", "apm_device{1}.metadata.json", "apm_network{1}.bson", "apm_network{1}.metadata.json", "app_crashes{1}.bson", "app_crashes{1}.metadata.json", "app_crashgroups{1}.bson", "app_crashgroups{1}.metadata.json", "app_crashusers{1}.bson", "app_crashusers{1}.metadata.json", "app_nxret{1}.bson", "app_nxret{1}.metadata.json", "app_users{1}.bson", "app_users{1}.metadata.json", "app_viewsmeta{1}.bson", "app_viewsmeta{1}.metadata.json", "apps.bson", "apps.metadata.json", "browser.bson", "browser.metadata.json", "calculated_metrics.bson", "calculated_metrics.metadata.json", "campaigndata.bson", "campaigndata.metadata.json", "campaigns.bson", "campaigns.metadata.json", "carriers.bson", "carriers.metadata.json", "cities.bson", "cities.metadata.json", "cohortdata.bson", "cohortdata.metadata.json", "cohorts.bson", "cohorts.metadata.json", "concurrent_users_max.bson", "concurrent_users_max.metadata.json", "consent_history{1}.bson", "consent_history{1}.metadata.json", "consents.bson", "consents.metadata.json", "crash_share.bson", "crash_share.metadata.json", "crashdata.bson", "crashdata.metadata.json", "density.bson", "density.metadata.json", "device_details.bson", "device_details.metadata.json", "devices.bson", "devices.metadata.json", "events.bson", "events.metadata.json", "feedback{1}.bson", "feedback{1}.metadata.json", "feedback_widgets.bson", "feedback_widgets.metadata.json", "funnels.bson", "funnels.metadata.json", "langs.bson", "langs.metadata.json", "max_online_counts.bson", "max_online_counts.metadata.json", "messages.bson", "messages.metadata.json", "metric_changes{1}.bson", "metric_changes{1}.metadata.json", "notes.bson", "notes.metadata.json", "retention_daily.bson", "retention_daily.metadata.json", "retention_monthly.bson", "retention_monthly.metadata.json", "retention_weekly.bson", "retention_weekly.metadata.json", "server_stats_data_points.bson", "server_stats_data_points.metadata.json", "sources.bson", "sources.metadata.json", "symbolication_jobs.bson", "symbolication_jobs.metadata.json", "top_events.bson", "top_events.metadata.json", "users.bson", "users.metadata.json", "views.bson", "views.metadata.json"]; - - export_path = export_path || path.resolve(__dirname, './../export/'); - - - var target_folder = path.resolve(__dirname, './compare_export'); - if (!fs.existsSync(target_folder)) { - try { - fs.mkdirSync(target_folder, 484); - } - catch (err) { - callback(err.message); - return; - } - } - - run_command("tar", ["xvzf", export_path + '/' + exportid + '.tar.gz', "-C", target_folder], function() { - var missing_files = []; - for (var i = 0; i < apps.length; i++) { - var target = target_folder; - - while (fs.existsSync(target + "/" + exportid)) { - target = target + "/" + exportid; - } - - while (fs.existsSync(target + "/" + apps[i])) { - target = target + "/" + apps[i]; - } - target = target + "/countly"; - - var pp = target; - for (var j = 0; j < simpleDocs.length; j++) { - var dir = pp + '/' + simpleDocs[j].replace('{1}', apps[i]); - if (!fs.existsSync(dir)) { - missing_files.push(dir); - } - } - } - if (missing_files.length > 0) { - callback("File(s) missing: " + missing_files.join('/n')); - } - else { - callback(); - } - }); -} -function validate_result(done, max_wait, wait_on, fail_on, options) { - if (counter < TIMES_FOR_DATA_MIGRATION_TEST) { - request - .post('/o/datamigration/getstatus?exportid=' + options.test_export_id + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - var ob = JSON.parse(res.text); - console.log("current status:" + ob.result.status + " current step:" + ob.result.step + " " + ob.result.progress); - if (ob.result.status == wait_on) { - (ob.result._id).should.be.exactly(options.test_export_id); - done(); - } - else if (ob.result.status == fail_on) { - done("Export changed to status " + fail_on + ". Was expected to reach status " + wait_on); - } - else { - counter = counter + 1; - setTimeout(function() { - done(); - }, TIMEOUT_FOR_DATA_MIGRATION_TEST); - } - }); - } - else { - console.log("Stopped waiting for update.(was expected to finish under " + (TIMEOUT_FOR_DATA_MIGRATION_TEST * TIMES_FOR_DATA_MIGRATION_TEST) / 1000 + " seconds). "); - //try getting log file - var dir = path.resolve(__dirname, '../../../log/dm-export_' + options.test_export_id + ".log"); - if (fs.existsSync(dir)) { - var instream = fs.createReadStream(dir); - - var rl = readline.createInterface({ - input: instream - }); - - rl.on('line', function(line) { - console.log(line); - }); - - rl.on('close', function(line) { - done("Unfinished"); - }); - } - else { - console.log("there was no log file"); - done("Unfinished"); - } - - } - -} - -function validate_import_result(done, max_wait, exportid) { - if (counter <= max_wait) { - //check if info file is here - //check log file - if (!fs.existsSync(path.resolve(__dirname, './../import/' + exportid + '.tar.gz')) && - !fs.existsSync(path.resolve(__dirname, './../import/' + exportid + '')) && - fs.existsSync(path.resolve(__dirname, './../import/' + exportid + '.json')) && - fs.existsSync(path.resolve(__dirname, './../../../log/dm-import_' + exportid + '.log')) - ) { - - fs.readFile(path.resolve(__dirname, './../../../log/dm-import_' + exportid + '.log'), 'utf8', function(err, data) { - if (err) { - done(err); - } - else if (data.indexOf("Data imported") > -1) { - setTimeout(done, 1000 * testUtils.testScalingFactor); - } - else { - console.log(data); - counter = counter + 1; - setTimeout(function() { - validate_import_result(done, max_wait, exportid); - }, TIMEOUT_FOR_DATA_MIGRATION_TEST); - } - }); - } - else { - counter = counter + 1; - setTimeout(function() { - validate_import_result(done, max_wait, exportid); - }, TIMEOUT_FOR_DATA_MIGRATION_TEST); - } - } - else { - done("Stopped waiting for update.(was expected to finish under " + max_wait + " seconds). "); - } - -} - - -function check_if_empty_list_test() { - it("Check if export list epmty", function(done) { - request - .post('/o/datamigration/getmyexports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no-exports"); - done(); - }); - }); - -} - -describe("Testing data migration plugin", function() { - describe("Catching invalid export parameters", function() { - it("Check if export list empty", function(done) { - API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); - APP_ID = testUtils.get("APP_ID"); - APP_KEY = testUtils.get("APP_KEY"); - request - .post('/o/datamigration/getmyexports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no-exports"); - done(); - }); - }); - - it('clean list just in case', function(done) { - request - .post('/i/datamigration/delete_all?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("ok"); - setTimeout(done, 1000); - }); - }); - - it("Try exporting without any id", function(done) { - request - .post('/i/datamigration/export?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no_app_ids"); - done(); - }); - }); - check_if_empty_list_test(); - - - it("Token missing", function(done) { - request - .post('/i/datamigration/export?apps=000f1f77bcf86cd799439011&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly('data-migration.token_missing'); - done(); - }); - }); - check_if_empty_list_test(); - - it("Check if export list epmty", function(done) { - request - .post('/o/datamigration/getmyexports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no-exports"); - done(); - }); - }); - - it("Address missing", function(done) { - request - .post('/i/datamigration/export?apps=000f1f77bcf86cd799439011&server_token=111111&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly('data-migration.address_missing'); - done(); - }); - }); - - it("Check if export list epmty", function(done) { - request - .post('/o/datamigration/getmyexports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no-exports"); - done(); - }); - }); - it("Try exporting with invaild id", function(done) { - request - .post('/i/datamigration/export?only_export=1&apps=1246&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.some_bad_ids"); - done(); - }); - }); - - it("Check if export list epmty", function(done) { - request - .post('/o/datamigration/getmyexports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no-exports"); - done(); - }); - }); - - it("Try exporting with not existing app id", function(done) { - request - .post('/i/datamigration/export?only_export=1&apps=507f1f77bcf86cd799439011&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.some_bad_ids"); - done(); - }); - }); - - it("Check if export list epmty", function(done) { - request - .post('/o/datamigration/getmyexports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no-exports"); - done(); - }); - }); - }); - describe("Create simple export", function() { - it('Create dummy app', function(done) { - request - .post('/i/apps/create?api_key=' + API_KEY_ADMIN + '&args={"name":"Test my app","type":"mobile"}') - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - - testapp = JSON.parse(res.text); - testapp.should.have.property("name", "Test my app"); - testapp.should.have.property("type", "mobile"); - done(); - }); - - }); - - it("Run simple export", function(done) { - request - .post('/i/datamigration/export?only_export=1&apps=' + testapp._id + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var apps = [testapp._id]; - test_export_id = crypto.createHash('SHA1').update(JSON.stringify(apps)).digest('hex'); - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly(test_export_id); - done(); - }); - }); - - after(function(done) { - //checking statuss and seeing if it moves to end - counter = 0; - this.timeout(0); - setTimeout(function() { - validate_result(done, 200, "finished", "failed", {test_export_id: test_export_id}); - }, TIMEOUT_FOR_DATA_MIGRATION_TEST); - }); - - }); - - describe("Validate exported files", function() { - it("Check for archive", function(done) { - var dir = path.resolve(__dirname, './../export/' + test_export_id + '.tar.gz'); - var logdir = path.resolve(__dirname, './../../../log/dm-export_' + test_export_id + '.log'); - if (fs.existsSync(dir)) { - if (fs.existsSync(logdir)) { - validate_log(test_export_id, function(err) { - if (err) { - done(err); - } - else { - done(); - //validate_files(test_export_id, [testapp._id], null, done) - } - }); - } - else { - done("Log file not created"); - } - } - else { - done("Archive not created"); - } - }); - }); - - describe("Validate responses for trying to overwrite existing export", function() { - it("Trying same export again(with existing data)", function(done) { - request - .post('/i/datamigration/export?only_export=1&apps=' + testapp._id + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.you-have-already-exported-data"); - done(); - }); - }); - - it("Setting up data to emulate running export", function(done) { - testUtils.db.collection("data_migrations").update({_id: test_export_id}, {$set: {"status": "progress"}}, {upsert: true}, function(err, res) { - if (err) { - done(err); - } - done(); - }); - }); - - it("Trying to rewrite running exporting process", function(done) { - request - .post('/i/datamigration/export?only_export=1&apps=' + testapp._id + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.already-running-exporting-process"); - done(); - }); - }); - }); - describe("delete and validate after delete", function() { - it("delete export", function(done) { - request - .post('/i/datamigration/delete_export?exportid=' + test_export_id + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("ok"); - done(); - }); - }); - - it("Check for files", function(done) { - var dir = path.resolve(__dirname, './../export/' + test_export_id + '.tar.gz'); - var logdir = path.resolve(__dirname, './../../../log/dm-export_' + test_export_id + '.log'); - if (!fs.existsSync(dir)) { - if (!fs.existsSync(logdir)) { - done(); - } - else { - done("Log file not deleted"); - } - } - else { - done("Archive not deleted"); - } - }); - - }); - - describe("Try export in different path", function() { - it("Run simple export", function(done) { - request - .post('/i/datamigration/export?only_export=1&apps=' + testapp._id + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID + '&target_path=' + path.resolve(__dirname, './../../')) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var apps = [testapp._id]; - test_export_id = crypto.createHash('SHA1').update(JSON.stringify(apps)).digest('hex'); - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly(test_export_id); - done(); - }); - }); - - after(function(done) { - //checking statuss and seeing if it moves to end - counter = 0; - this.timeout(0); - setTimeout(function() { - done(); - }, TIMEOUT_FOR_DATA_MIGRATION_TEST); - }); - }); - - describe("Validate exported files", function() { - it("Check for files", function(done) { - - var dir = path.resolve(__dirname, './../../' + test_export_id + '.tar.gz'); - - var logdir = path.resolve(__dirname, './../../../log/dm-export_' + test_export_id + '.log'); - if (fs.existsSync(logdir)) { - if (fs.existsSync(dir)) { - validate_log(test_export_id, function(err) { - if (err) { - done(err); - } - else { - done(); - //validate_files(test_export_id, [testapp._id], path.resolve(__dirname, './../../'), done); - } - }); - } - else { - done("Archive not created"); - } - } - else { - done("Log file not created"); - } - - - }); - - it("delete export", function(done) { - request - .post('/i/datamigration/delete_export?exportid=' + test_export_id + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("ok"); - done(); - }); - }); - - it("Check for files", function(done) { - var dir = path.resolve(__dirname, './../export/' + test_export_id + '.tar.gz'); - var logdir = path.resolve(__dirname, './../../../log/dm-export_' + test_export_id + '.log'); - if (!fs.existsSync(dir)) { - if (!fs.existsSync(logdir)) { - done(); - } - else { - done("Log file not deleted"); - } - } - else { - done("Archive not deleted"); - } - }); - }); - - describe("cleanup", function() { - it("Remove test app", function(done) { - request - .post('/i/apps/delete?api_key=' + API_KEY_ADMIN + '&args={"app_id":"' + testapp._id + '"}') - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - done(); - }); - }); - }); - - - describe("Valiate invalid import", function() { - var mytoken = ""; - it("Trying import without file", function(done) { - request - .post('/i/datamigration/import?app_id=' + APP_ID + '&api_key=' + API_KEY_ADMIN) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.import-file-missing"); - done(); - }); - }); - it("Import without any autentification", function(done) { - request - .post('/i/datamigration/import') - .expect(401) - .end(function(err, res) { - done(); - }); - }); - it("Invalid token", function(done) { - request - .post('/i/datamigration/import') - .set('countly-token', '000000000000') - .expect(400) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly('Token not valid'); - done(); - }); - }); - it("Create token", function(done) { - request - .post('/o/datamigration/createimporttoken?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result != "") { - mytoken = ob.result; - } - else { - done('invalid response. No token provided.' + res.text); - } - done(); - }); - }); - it("Sending without file", function(done) { - request - .post('/i/datamigration/import') - .set('countly-token', mytoken) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.import-file-missing"); - done(); - }); - }); - }); - - - describe("Importing via token", function() { - var mytoken = ""; - var tt = ""; - it("Unauthorised", function(done) { - request - .post('/o/datamigration/createimporttoken') - .expect(401) - .end(function(err, res) { - done(); - }); - }); - it("Create token", function(done) { - request - .post('/o/datamigration/createimporttoken?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result != "") { - mytoken = ob.result; - } - else { - done('invalid response. No token provided.' + res.text); - } - done(); - }); - }); - it("Validate token ", function(done) { - request - .post('/i/datamigration/import?test_con=1') - .set('countly-token', mytoken) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result == "valid") { - done(); - } - else { - done('invalid response.' + res.text); - } - - }); - }); - it("Send test ", function(done) { - tt = "b18e10498ec0f41a85bb8155ccd4a209819319a3"; - - var dir = path.resolve(__dirname, './' + tt + '.tar.gz'); - request - .post('/i/datamigration/import?ts=000000&exportid=' + tt) - .attach('import_file', dir) - .set('countly-token', mytoken) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.import-started"); - done(); - }); - }); - - after(function(done) { - //checking statuss and seeing if it moves to end - counter = 0; - setTimeout(function() { - validate_import_result(done, 10, "b18e10498ec0f41a85bb8155ccd4a209819319a3"); - }, TIMEOUT_FOR_DATA_MIGRATION_TEST); - }); - }); - describe("get my imports", function() { - it("try unautorized ", function(done) { - request - .post('/o/datamigration/getmyimports') - .expect(400) - .end(function(err, res) { - if (err) { - return done(err); - } - done(); - }); - }); - - it("get imports list ", function(done) { - request - .post('/o/datamigration/getmyimports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - ob = ob.result; - if (ob['b18e10498ec0f41a85bb8155ccd4a209819319a3'] && ob['b18e10498ec0f41a85bb8155ccd4a209819319a3']['app_list'] == 'Demo' && ob['b18e10498ec0f41a85bb8155ccd4a209819319a3']['log'] == 'dm-import_b18e10498ec0f41a85bb8155ccd4a209819319a3.log') { - done(); - } - else { - done("Invalid object"); - } - }); - }); - }); - describe("deleting import", function() { - it("try unautorized delete import ", function(done) { - request - .post('/i/datamigration/delete_import?exportid=b18e10498ec0f41a85bb8155ccd4a209819319a3') - .expect(400) - .end(function(err, res) { - if (err) { - return done(err); - } - done(); - - }); - - }); - it("try deleting import without passing exportid", function(done) { - request - .post('/i/datamigration/delete_import?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result == 'data-migration.exportid-missing') { - done(); - } - else { - done('invalid response. No token provided.' + res.text); - } - }); - }); - it("rejects path traversal in import delete exportid", function(done) { - var traversalTarget = path.join(os.tmpdir(), "countly_data_migration_path_traversal_test_" + Date.now()); - var traversalExportId = "../../../../../../../../.." + traversalTarget; - - fs.writeFileSync(traversalTarget, "test"); - request - .post('/i/datamigration/delete_import?exportid=' + encodeURIComponent(traversalExportId) + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(400) - .end(function(err) { - if (err) { - fse.removeSync(traversalTarget); - return done(err); - } - if (!fs.existsSync(traversalTarget)) { - return done("Traversal target was deleted"); - } - fse.removeSync(traversalTarget); - done(); - }); - }); - it("delete import request ", function(done) { - request - .post('/i/datamigration/delete_import?exportid=b18e10498ec0f41a85bb8155ccd4a209819319a3' + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result == "ok") { - done(); - } - else { - done('invalid response. No token provided.' + res.text); - } - - }); - }); - it("get empty import list ", function(done) { - request - .post('/o/datamigration/getmyimports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result == "data-migration.no-imports") { - done(); - } - else { - done('invalid response.' + res.text); - } - }); - }); - }); - - describe("Importing uploaded export on server", function() { - var mytoken = ""; - var tt = ""; - it("Unauthorised", function(done) { - request - .post('/o/datamigration/createimporttoken') - .expect(401) - .end(function(err, res) { - done(); - }); - }); - it("Create token", function(done) { - request - .post('/o/datamigration/createimporttoken?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result != "") { - mytoken = ob.result; - } - else { - done('invalid response. No token provided.' + res.text); - } - done(); - }); - }); - it("Try invalid path", function(done) { - request - .post('/i/datamigration/import?ts=000000&existing_file=var/jsfjkasbfkja/asjghaogha/asjkgfakjbgjka/alsgaklgnl') - .set('countly-token', mytoken) - .expect(404) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.could-not-find-file"); - done(); - }); - }); - - - it("Call import process", function(done) { - tt = "b18e10498ec0f41a85bb8155ccd4a209819319a3"; - var sourcePath = path.resolve(__dirname, './' + tt + '.tar.gz'); - var importDir = path.resolve(__dirname, './../import'); - var importPath = path.resolve(importDir, './' + tt + '.tar.gz'); - if (!fs.existsSync(importDir)) { - fs.mkdirSync(importDir, 484); - } - fs.copyFileSync(sourcePath, importPath); - request - .post('/i/datamigration/import?ts=000000&existing_file=' + tt) - .set('countly-token', mytoken) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.import-started"); - done(); - }); - }); - - after(function(done) { - //checking statuss and seeing if it moves to end - counter = 0; - setTimeout(function() { - validate_import_result(done, 10, tt); - }, 1000); - }); - }); - - /*describe("cleanup", function() { - it("Check if app exists", function(done) { - request - .post('/o/apps/all?api_key=' + API_KEY_ADMIN) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - else { - res = JSON.parse(res.text); - res = res["admin_of"] - for(var k in res){ - if(k === "58650a47cc2ed563c5ad964c"){ - done(); - return; - } - } - done("App missing"); - } - }); - }); - it("Remove test app", function(done) { - request - .post('/i/apps/delete?api_key=' + API_KEY_ADMIN + '&args={"app_id":"58650a47cc2ed563c5ad964c"}') - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - done(); - }); - }); - });*/ - - /*describe("Importing bigger app", function() { - it("Create token and call import process", function(done) { - request - .post('/o/datamigration/createimporttoken?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result != "") { - var tt = "f9b35d90be5f2240eafced7c6bfdf130856cd0a7"; - var dir = path.resolve(__dirname, './' + tt + '.tar.gz'); - request - .post('/i/datamigration/import?ts=000000&existing_file=' + dir) - .set('countly-token', ob.result) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("Importing process started."); - done(); - }); - - } - else { - done('invalid response. No token provided.' + res.text); - } - }); - }); - - after(function(done) { - //checking statuss and seeing if it moves to end - counter = 0; - setTimeout(function() { - validate_import_result(done, 10, "f9b35d90be5f2240eafced7c6bfdf130856cd0a7"); - }, 1000); - }); - }); - describe("some cleanup",function(){ - it("Check if app exists", function(done) { - request - .post('/o/apps/all?api_key=' + API_KEY_ADMIN) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - else { - res = JSON.parse(res.text); - res = res["admin_of"] - for(var k in res){ - if(k === "5f589b9e8df39d7b85474921"){ - done(); - return; - } - } - //try again - - setTimeout(function(){ - request - .post('/o/apps/all?api_key=' + API_KEY_ADMIN) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - else { - res = JSON.parse(res.text); - res = res["admin_of"] - for(var k in res){ - if(k === "5f589b9e8df39d7b85474921"){ - done(); - return; - } - } - - done("App missing"); - } - }); - - },10000); - } - }); - }); - - it("delete import request ", function(done) { - request - .post('/i/datamigration/delete_import?exportid=f9b35d90be5f2240eafced7c6bfdf130856cd0a7' + '&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - if (ob.result && ob.result == "ok") { - setTimeout(done,10000); - } - else { - done('invalid response. No token provided.' + res.text); - } - - }); - }); - }); - - describe("Exporting same app", function() { - it("Run bigger export", function(done) { - request - .post('/i/datamigration/export?only_export=1&apps=5f589b9e8df39d7b85474921&api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("f9b35d90be5f2240eafced7c6bfdf130856cd0a7"); - done(); - }); - }); - - after(function(done) { - //checking statuss and seeing if it moves to end - counter = 0; - this.timeout(0); - setTimeout(function() { - validate_result(done, 200, "finished", "failed",{test_export_id:"f9b35d90be5f2240eafced7c6bfdf130856cd0a7"}); - }, TIMEOUT_FOR_DATA_MIGRATION_TEST); - }); - }); - - describe("Comparing if folder contents - if no data missing", function() { - it("Check for archive", function(done) { - - var dir = path.resolve(__dirname, './../export/' + "f9b35d90be5f2240eafced7c6bfdf130856cd0a7" + '.tar.gz'); - var logdir = path.resolve(__dirname, './../../../log/dm-export_' + "f9b35d90be5f2240eafced7c6bfdf130856cd0a7" + '.log'); - if (fs.existsSync(dir)) { - if (fs.existsSync(logdir)) { - validate_log("f9b35d90be5f2240eafced7c6bfdf130856cd0a7",function(err){ - if(err) { - done(err); - } - else { - validate_files("f9b35d90be5f2240eafced7c6bfdf130856cd0a7", ["5f589b9e8df39d7b85474921"], null, done) - } - }); - - } - else { - done("Log file not created"); - } - } - else { - done("Archive not created"); - } - }); - - - it("Get contents", function(done) { - var exportid = "f9b35d90be5f2240eafced7c6bfdf130856cd0a7"; - var export_path = export_path || path.resolve(__dirname, './../export/'); - var missing_files = []; - var apps = ["5f589b9e8df39d7b85474921"]; - for (var i = 0; i < apps.length; i++) { - var pp = path.resolve(__dirname, './compare_export'+'/' + exportid + '/' + apps[i] + '/countly'); - var files = fs.readdirSync(path.resolve(__dirname, "./"+"f9b35d90be5f2240eafced7c6bfdf130856cd0a7"+"/" + apps[i] + "/countly")); - for (var j = 0; j < files.length; j++) { - var dir = path.resolve(pp,"./"+files[j]); - if (!fs.existsSync(dir)) { - missing_files.push(dir); - } - } - } - if (missing_files.length > 0) { - done("File(s) missing: " + missing_files.join('/n')); - } - else { - done(); - } - }); - it("Remove test app", function(done) { - request - .post('/i/apps/delete?api_key=' + API_KEY_ADMIN + '&args={"app_id":"5f589b9e8df39d7b85474921"}') - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - done(); - }); - }); - });*/ - - describe("cleanup", function() { - - - it('delete data', function(done) { - request - .post('/i/datamigration/delete_all?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("ok"); - done(); - }); - }); - - it('delete collection', function(done) { - testUtils.db.collection("data_migrations").drop(function(err, res) { - if (err) { - return done(err); - } - if (res) { - done(); - } - else { - done("ERROR cleaning up database"); - } - }); - }); - it("clenup test dir", function(done) { - fse.remove(path.resolve(__dirname, './compare_export'), err => { - if (err) { - done(Error('Unable to remove directory')); - } - else { - done(); - } - - }); - }); - it("Get export list", function(done) { - request - .post('/o/datamigration/getmyexports?api_key=' + API_KEY_ADMIN + '&app_id=' + APP_ID) - .expect(200) - .end(function(err, res) { - if (err) { - return done(err); - } - var ob = JSON.parse(res.text); - (ob.result).should.be.exactly("data-migration.no-exports"); - done(); - }); - }); - }); -}); diff --git a/plugins/data_migration/uninstall.js b/plugins/data_migration/uninstall.js deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/plugins/dbviewer/api/api.js b/plugins/dbviewer/api/api.js index 7af108d49b8..8ac9d1c731b 100644 --- a/plugins/dbviewer/api/api.js +++ b/plugins/dbviewer/api/api.js @@ -12,55 +12,14 @@ const { MongoInvalidArgumentError } = require('mongodb'); const { EJSON } = require('bson'); const FEATURE_NAME = 'dbviewer'; -const whiteListedAggregationStages = { - "$addFields": true, - "$bucket": true, - "$bucketAuto": true, - //"$changeStream": false, - //"$changeStreamSplitLargeEvents": false, - //"$collStats": false, - "$count": true, - //"$currentOp": false, - "$densify": true, - //"$documents": false - "$facet": true, - "$fill": true, - "$geoNear": true, - // "$graphLookup": false — removed: lets attacker pull joined documents from any collection in the same DB, - // bypassing the per-collection access check. Use $lookup instead if cross-collection - // joins are ever needed (currently also disallowed). - "$group": true, - //"$indexStats": false, - "$limit": true, - //"$listLocalSessions": false - //"$listSampledQueries": false - //"$listSearchIndexes": false - //"$listSessions": false - //"$lookup": false - "$match": true, - //"$merge": false - //"$mergeCursors": false - //"$out": false - //"$planCacheStats": false, - "$project": true, - "$querySettings": true, - "$redact": true, - "$replaceRoot": true, - "$replaceWith": true, - "$sample": true, - "$search": true, - "$searchMeta": true, - "$set": true, - "$setWindowFields": true, - //"$sharedDataDistribution": false, - "$skip": true, - "$sort": true, - "$sortByCount": true, - //"$unionWith": false, - "$unset": true, - "$unwind": true, - "$vectorSearch": true //atlas specific -}; +// upper bound on rows returned per find()/aggregation page, to keep a crafted +// limit/iDisplayLength from requesting an unbounded result set +const MAX_DBVIEWER_LIMIT = 10000; +// Aggregation-stage allow-list and the recursive sanitizer that strips blocked +// stages at every depth (including inside $facet sub-pipelines). Kept in a +// dedicated module so it can be unit-tested in isolation. +const { sanitizeAggregation, ALLOWED_STAGES_USER, ALLOWED_STAGES_GLOBAL_ADMIN } = require('./parts/aggregation_guard.js'); +const { sanitizeProjection, escapeRegExp } = require('./parts/query_guard.js'); var spawn = require('child_process').spawn, child; @@ -68,27 +27,6 @@ var spawn = require('child_process').spawn, plugins.register("/permissions/features", function(ob) { ob.features.push(FEATURE_NAME); }); - /** - * Function removes not allowed aggregation stages from the pipeline - * @param {array} aggregation - current aggregation pipeline - * @returns {object} changes - object with information which operations were removed - */ - function escapeNotAllowedAggregationStages(aggregation) { - var changes = {}; - for (var z = 0; z < aggregation.length; z++) { - for (var key in aggregation[z]) { - if (!whiteListedAggregationStages[key]) { - changes[key] = true; - delete aggregation[z][key]; - } - } - if (Object.keys(aggregation[z]).length === 0) { - aggregation.splice(z, 1); - z--; - } - } - return changes; - } /** * @api {get} /o/db Access database @@ -189,11 +127,22 @@ var spawn = require('child_process').spawn, params.qstring.document = common.db.ObjectID(params.qstring.document); } if (dbs[dbNameOnParam]) { - dbs[dbNameOnParam].collection(params.qstring.collection).findOne({ _id: params.qstring.document }, function(err, results) { + // Scope the lookup to the member's apps the same way the + // collection listing does, so a document cannot be fetched + // outside the caller's app scope by supplying its _id. + var docFilter = { _id: params.qstring.document }; + if (!params.member.global_admin) { + var docBaseFilter = getBaseAppFilter(params.member, dbNameOnParam, params.qstring.collection); + if (docBaseFilter && Object.keys(docBaseFilter).length > 0) { + docFilter = { $and: [docBaseFilter, docFilter] }; + } + } + dbs[dbNameOnParam].collection(params.qstring.collection).findOne(docFilter, function(err, results) { if (!err) { if (params.qstring.collection === 'members' && results) { delete results.password; delete results.api_key; + delete results.two_factor_auth; } else if (params.qstring.collection === 'auth_tokens' && results) { if (results._id) { @@ -203,7 +152,8 @@ var spawn = require('child_process').spawn, common.returnOutput(params, objectIdCheck(results) || {}); } else { - common.returnOutput(params, 500, err); + log.e(err); + common.returnMessage(params, 500, "An unexpected error occurred."); } }); } @@ -216,8 +166,19 @@ var spawn = require('child_process').spawn, * Get collection data from db **/ async function dbGetCollection() { - var limit = parseInt(params.qstring.limit || 20); - var skip = parseInt(params.qstring.skip || 0); + // cap page size and guard against NaN so a crafted limit/skip can't + // request an unbounded result set + var limit = parseInt(params.qstring.limit, 10); + if (isNaN(limit) || limit <= 0) { + limit = 20; + } + if (limit > MAX_DBVIEWER_LIMIT) { + limit = MAX_DBVIEWER_LIMIT; + } + var skip = parseInt(params.qstring.skip, 10); + if (isNaN(skip) || skip < 0) { + skip = 0; + } var filter = params.qstring.filter || params.qstring.query || "{}"; var sSearch = params.qstring.sSearch || ""; var projection = params.qstring.project || params.qstring.projection || "{}"; @@ -229,6 +190,11 @@ var spawn = require('child_process').spawn, catch (SyntaxError) { sort = {}; } + //EJSON.parse("null") yields null (typeof null === "object"), so + //normalize to a plain object before any property access / query use + if (!sort || typeof sort !== 'object' || Array.isArray(sort)) { + sort = {}; + } try { filter = EJSON.parse(filter); } @@ -236,11 +202,16 @@ var spawn = require('child_process').spawn, common.returnMessage(params, 400, "Failed to parse query. " + SyntaxError.message); return false; } + if (!filter || typeof filter !== 'object' || Array.isArray(filter)) { + filter = {}; + } if (filter._id && isObjectId(filter._id)) { filter._id = common.db.ObjectID(filter._id); } if (sSearch) { - filter._id = new RegExp(sSearch); + // treat the search term as a literal so a crafted pattern cannot + // cause catastrophic regex backtracking (ReDoS) + filter._id = new RegExp(escapeRegExp(sSearch)); } try { projection = EJSON.parse(projection); @@ -248,10 +219,30 @@ var spawn = require('child_process').spawn, catch (SyntaxError) { projection = {}; } + //EJSON.parse("null") yields null and an array is also typeof + //"object"; normalize anything that isn't a plain object to {} so an + //invalid projection can't reach find() + if (!projection || typeof projection !== 'object' || Array.isArray(projection)) { + projection = {}; + } if (typeof filter !== 'object' || Array.isArray(filter)) { filter = {}; } + //reject server-side-JS Mongo operators ($where/$function/ + //$accumulator) in the user-supplied filter and sort so the db + //viewer query cannot be abused to execute code on the server + var badOp = common.findUnsafeMongoOperator(filter) || common.findUnsafeMongoOperator(sort); + if (badOp) { + log.d("Rejected user query" + common.reqInfo(params) + ": " + common.unsafeQueryError(badOp)); + common.returnMessage(params, 400, common.unsafeQueryError(badOp)); + return false; + } + //restrict the projection to plain field include/exclude — drop any + //expression / field-path alias (e.g. {x:"$password"}) that could + //compute or rename fields the viewer otherwise removes + sanitizeProjection(projection); + var base_filter = {}; if (!params.member.global_admin) { base_filter = getBaseAppFilter(params.member, dbNameOnParam, params.qstring.collection); @@ -296,6 +287,7 @@ var spawn = require('child_process').spawn, if (params.qstring.collection === 'members' && doc) { delete doc.password; delete doc.api_key; + delete doc.two_factor_auth; } else if (params.qstring.collection === 'auth_tokens' && doc) { if (doc._id) { @@ -343,7 +335,8 @@ var spawn = require('child_process').spawn, } } catch (err) { - common.returnMessage(params, 500, err); + log.e(err); + common.returnMessage(params, 500, "An unexpected error occurred."); } } } @@ -414,24 +407,27 @@ var spawn = require('child_process').spawn, * @param {object} changes - object referencing removed stages from pipeline * */ function aggregate(collection, aggregation, changes) { - if (params.qstring.iDisplayLength) { - aggregation.push({ "$limit": parseInt(params.qstring.iDisplayLength) }); - } if (!Array.isArray(aggregation)) { - common.returnMessage(params, 500, "The aggregation pipeline must be of the type array"); + common.returnMessage(params, 400, "The aggregation pipeline must be of the type array"); + return; } else { - var addProjectionAt = 0; - if (aggregation[0] && aggregation[0].$match) { - while (aggregation.length > addProjectionAt && aggregation[addProjectionAt].$match) { - addProjectionAt++; + if (params.qstring.iDisplayLength) { + var iDisplayLength = parseInt(params.qstring.iDisplayLength, 10); + if (!isNaN(iDisplayLength) && iDisplayLength > 0) { + aggregation.push({ "$limit": Math.min(iDisplayLength, MAX_DBVIEWER_LIMIT) }); } } if (collection === 'members') { - aggregation.splice(addProjectionAt, 0, {"$project": {"password": 0, "api_key": 0}}); + // Insert the redaction as the very first stage so no + // user-supplied stage — including a leading $match using + // $expr, or a $project/$group that aliases or references the + // field — can read the raw credential fields before they are + // removed. + aggregation.splice(0, 0, {"$project": {"password": 0, "api_key": 0, "two_factor_auth": 0}}); } else if (collection === 'auth_tokens') { - aggregation.splice(addProjectionAt, 0, {"$addFields": {"_id": "***redacted***"}}); + aggregation.splice(0, 0, {"$addFields": {"_id": "***redacted***"}}); } else if ((collection === "events_data" || collection === "drill_events") && !params.member.global_admin) { var base_filter = getBaseAppFilter(params.member, dbNameOnParam, params.qstring.collection); @@ -476,7 +472,8 @@ var spawn = require('child_process').spawn, common.returnOutput(params, { sEcho: params.qstring.sEcho, iTotalRecords: 0, iTotalDisplayRecords: 0, "aaData": result, "removed": (changes || {}) }); } else { - common.returnMessage(params, 500, aggregationErr); + log.e(aggregationErr); + common.returnMessage(params, 500, "An unexpected error occurred."); } } }); @@ -576,31 +573,39 @@ var spawn = require('child_process').spawn, } // handle aggregation request else if (isContainDb && params.qstring.aggregation) { - if (params.member.global_admin) { + // Validate the pipeline against the caller's allow-list and run + // it. Global admins get the broader list (may join/union, but + // still no writes and never into a redacted collection); other + // users get the restricted list. Disallowed stages are stripped; + // server-side-JS operators and joins into members/auth_tokens + // reject the request. + var runAggregation = function(allowedStages) { try { let aggregation = EJSON.parse(params.qstring.aggregation); - aggregate(params.qstring.collection, aggregation); + var guard = sanitizeAggregation(aggregation, allowedStages); + if (guard.error) { + var msg = guard.error.type === "join" + ? 'Aggregation may not join the "' + guard.error.name + '" collection' + : 'Aggregation may not use the "' + guard.error.name + '" operator'; + common.returnMessage(params, 400, msg); + return; + } + if (guard.changes && Object.keys(guard.changes).length > 0) { + log.d("Removed stages from pipeline: ", JSON.stringify(guard.changes)); + } + aggregate(params.qstring.collection, aggregation, guard.changes); } catch (e) { common.returnMessage(params, 500, 'Aggregation object is not valid.'); - return true; } + }; + if (params.member.global_admin) { + runAggregation(ALLOWED_STAGES_GLOBAL_ADMIN); } else { userHasAccess(params, params.qstring.collection, function(hasAccess) { if (hasAccess || params.qstring.collection === "events_data" || params.qstring.collection === "drill_events") { - try { - let aggregation = EJSON.parse(params.qstring.aggregation); - var changes = escapeNotAllowedAggregationStages(aggregation); - if (changes && Object.keys(changes).length > 0) { - log.d("Removed stages from pipeline: ", JSON.stringify(changes)); - } - aggregate(params.qstring.collection, aggregation, changes); - } - catch (e) { - common.returnMessage(params, 500, 'Aggregation object is not valid.'); - return true; - } + runAggregation(ALLOWED_STAGES_USER); } else { common.returnMessage(params, 401, 'User does not have right tot view this colleciton'); @@ -746,7 +751,12 @@ var spawn = require('child_process').spawn, } if (isView) { - pretty = "app_viewdata" + getCollectionName(name); + var viewEntry = getCollectionName(name); + //getCollectionName returns the input unchanged when there is no mapping, + //prepending it then would double the prefix (app_viewdataapp_viewdata...) + if (viewEntry !== name) { + pretty = "app_viewdata" + viewEntry; + } } else if (!isEvent) { for (let i in apps) { diff --git a/plugins/dbviewer/api/parts/aggregation_guard.js b/plugins/dbviewer/api/parts/aggregation_guard.js new file mode 100644 index 00000000000..0f83b196f2d --- /dev/null +++ b/plugins/dbviewer/api/parts/aggregation_guard.js @@ -0,0 +1,322 @@ +/** + * @module plugins/dbviewer/api/parts/aggregation_guard + * @description Validates a DB Viewer aggregation pipeline against a role-specific + * allow-list of stages, and rejects pipelines that use server-side-JavaScript + * operators or join/union into a redacted (credential) collection. + * + * One routine, two lists: + * - non-global users get ALLOWED_STAGES_USER (no joins, no writes); + * - global admins get ALLOWED_STAGES_GLOBAL_ADMIN (the same plus join/union + * stages). + * Anything not in the applicable list is stripped from the pipeline at every + * depth. Two hard rules apply to everyone, at any depth, and reject the request: + * - no $function / $accumulator / $where (server-side JavaScript); + * - no join/union into members / auth_tokens (their field redaction only + * applies to the top-level source collection, so a join would return raw + * credentials — denied even for global admins). + */ + +'use strict'; + +// Stages a non-global user may run. No joins/unions (they read a second +// collection, bypassing the per-collection access check) and no writes. +const ALLOWED_STAGES_USER = { + "$addFields": true, + "$bucket": true, + "$bucketAuto": true, + "$count": true, + "$densify": true, + "$facet": true, + "$fill": true, + "$geoNear": true, + "$group": true, + "$limit": true, + "$match": true, + "$project": true, + "$querySettings": true, + "$redact": true, + "$replaceRoot": true, + "$replaceWith": true, + "$sample": true, + "$search": true, + "$searchMeta": true, + "$set": true, + "$setWindowFields": true, + "$skip": true, + "$sort": true, + "$sortByCount": true, + "$unset": true, + "$unwind": true, + "$vectorSearch": true //atlas specific +}; + +// Global admins may additionally use the join/union stages. Still no write +// stages, and still never into a protected collection (see findHardViolation). +const ALLOWED_STAGES_GLOBAL_ADMIN = Object.assign({}, ALLOWED_STAGES_USER, { + "$lookup": true, + "$graphLookup": true, + "$unionWith": true +}); + +// Expression operators that run server-side JavaScript. Never allowed for +// anyone, at any depth — they live inside otherwise-allowed stages. +const DENIED_OPERATORS = { + "$function": true, + "$accumulator": true, + "$where": true +}; + +// Collections whose contents DB Viewer redacts. A join/union into them would +// return the raw documents (redaction only applies to the top-level source), +// so such joins are rejected for everyone — including global admins. +const PROTECTED_JOIN_COLLECTIONS = { + "members": true, + "auth_tokens": true +}; + +// All recognized aggregation STAGE operators (any role's allow-list plus the +// known blocked stages), used to recognize a nested array as a sub-pipeline +// (array of stage objects) and tell it apart from an ordinary expression array. +const KNOWN_STAGE_OPERATORS = Object.assign({ + "$out": true, + "$merge": true, + "$documents": true, + "$collStats": true, + "$indexStats": true, + "$currentOp": true, + "$listSessions": true, + "$listLocalSessions": true, + "$listSampledQueries": true, + "$listSearchIndexes": true, + "$planCacheStats": true, + "$changeStream": true, + "$changeStreamSplitLargeEvents": true, + "$mergeCursors": true, + "$sharedDataDistribution": true +}, ALLOWED_STAGES_GLOBAL_ADMIN); + +/** + * Extract the collection name from a join "from"/"coll" reference, which may be + * a plain string ("members") or the cross-database object form + * ({ db, coll }). Returns [] when no collection name can be determined. + * @param {*} from - a $lookup.from / $graphLookup.from / $unionWith.coll value + * @returns {string[]} the referenced collection name(s) + */ +function collectionOf(from) { + if (typeof from === "string") { + return [from]; + } + if (from && typeof from === "object" && typeof from.coll === "string") { + return [from.coll]; + } + return []; +} + +/** + * Collection names a single stage joins / unions from. + * @param {object} stage - one aggregation stage + * @returns {string[]} target collection names referenced by join/union operators + */ +function joinTargetsOf(stage) { + var targets = []; + if (!stage || typeof stage !== "object") { + return targets; + } + if (stage.$lookup && typeof stage.$lookup === "object") { + targets = targets.concat(collectionOf(stage.$lookup.from)); + } + if (stage.$graphLookup && typeof stage.$graphLookup === "object") { + targets = targets.concat(collectionOf(stage.$graphLookup.from)); + } + if (stage.$unionWith) { + if (typeof stage.$unionWith === "string") { + targets.push(stage.$unionWith); + } + else if (typeof stage.$unionWith === "object") { + targets = targets.concat(collectionOf(stage.$unionWith.coll)); + } + } + return targets; +} + +/** + * Does this array look like an aggregation sub-pipeline — a non-empty array + * whose every element is a stage object (an object carrying a recognized stage + * operator key)? Expression arrays (e.g. $concat operands) do not match. + * @param {*} arr - candidate value + * @returns {boolean} true if it is a sub-pipeline + */ +function isSubPipeline(arr) { + if (!Array.isArray(arr) || arr.length === 0) { + return false; + } + for (var i = 0; i < arr.length; i++) { + var el = arr[i]; + if (!el || typeof el !== "object" || Array.isArray(el)) { + return false; + } + var isStage = false; + for (var k in el) { + if (Object.prototype.hasOwnProperty.call(el, k) && KNOWN_STAGE_OPERATORS[k] === true) { + isStage = true; + break; + } + } + if (!isStage) { + return false; + } + } + return true; +} + +/** + * Deep-walk a node for a hard-rule violation that must reject the whole request, + * regardless of role: a server-side-JS operator, or a join/union into a + * protected (redacted) collection. Detection-only (no mutation), so a blanket + * deep walk is safe and stays correct for any (incl. future) nested shape. + * @param {*} node - pipeline / stage / expression node + * @returns {{type: string, name: string}|null} the violation, or null + */ +function findHardViolation(node) { + if (Array.isArray(node)) { + for (var i = 0; i < node.length; i++) { + var inArr = findHardViolation(node[i]); + if (inArr) { + return inArr; + } + } + return null; + } + if (node && typeof node === "object") { + var targets = joinTargetsOf(node); + for (var t = 0; t < targets.length; t++) { + if (PROTECTED_JOIN_COLLECTIONS[targets[t]] === true) { + return { type: "join", name: targets[t] }; + } + } + for (var key in node) { + if (!Object.prototype.hasOwnProperty.call(node, key)) { + continue; + } + if (DENIED_OPERATORS[key] === true) { + return { type: "operator", name: key }; + } + var inVal = findHardViolation(node[key]); + if (inVal) { + return inVal; + } + } + } + return null; +} + +/** + * Walk a kept stage's value and strip disallowed stages from any sub-pipeline + * nested anywhere within it (structural — $facet branches, a .pipeline field, + * or any other nested-pipeline shape). A sub-pipeline emptied by stripping is + * removed from its parent object (Mongo rejects an empty $facet branch). + * @param {*} value - the stage value (or any nested node) + * @param {object} allowedStages - the role's allow-list + * @param {object} changes - accumulator: keys are removed stage names + * @returns {void} + */ +function stripNested(value, allowedStages, changes) { + if (Array.isArray(value)) { + if (isSubPipeline(value)) { + stripStages(value, allowedStages, changes); + } + else { + for (var i = 0; i < value.length; i++) { + stripNested(value[i], allowedStages, changes); + } + } + return; + } + if (value && typeof value === "object") { + for (var k in value) { + if (!Object.prototype.hasOwnProperty.call(value, k)) { + continue; + } + var child = value[k]; + if (Array.isArray(child) && isSubPipeline(child)) { + stripStages(child, allowedStages, changes); + if (child.length === 0) { + delete value[k]; + } + } + else { + stripNested(child, allowedStages, changes); + } + } + } +} + +/** + * Recursively remove stages not in `allowedStages` from a pipeline, at every + * depth. Mutates the pipeline in place. + * @param {Array} pipeline - aggregation pipeline + * @param {object} allowedStages - the role's allow-list (values must be === true) + * @param {object} changes - accumulator: keys are removed stage names + * @returns {void} + */ +function stripStages(pipeline, allowedStages, changes) { + if (!Array.isArray(pipeline)) { + return; + } + for (var z = 0; z < pipeline.length; z++) { + var stage = pipeline[z]; + if (!stage || typeof stage !== "object") { + continue; + } + for (var key in stage) { + if (!Object.prototype.hasOwnProperty.call(stage, key)) { + continue; + } + // require an explicit `true` so inherited Object.prototype keys + // (constructor, __proto__, …) are never treated as allow-listed + if (allowedStages[key] !== true) { + changes[key] = true; + delete stage[key]; + } + else { + stripNested(stage[key], allowedStages, changes); + var v = stage[key]; + if (v && typeof v === "object" && !Array.isArray(v) && Object.keys(v).length === 0) { + delete stage[key]; + } + } + } + if (Object.keys(stage).length === 0) { + pipeline.splice(z, 1); + z--; + } + } +} + +/** + * Validate and sanitize an aggregation pipeline for a given role's allow-list. + * Rejects (without mutating) when a hard rule is violated; otherwise strips any + * stage not in the allow-list and returns what was removed. + * @param {Array} pipeline - aggregation pipeline (mutated in place when valid) + * @param {object} allowedStages - ALLOWED_STAGES_USER or ALLOWED_STAGES_GLOBAL_ADMIN + * @returns {{changes: object, error: ({type: string, name: string}|null)}} + * When error is set the caller must reject the request and not run the + * pipeline. Otherwise changes lists the removed stage names. + */ +function sanitizeAggregation(pipeline, allowedStages) { + var violation = findHardViolation(pipeline); + if (violation) { + return { changes: {}, error: violation }; + } + var changes = {}; + stripStages(pipeline, allowedStages, changes); + return { changes: changes, error: null }; +} + +module.exports = { + ALLOWED_STAGES_USER, + ALLOWED_STAGES_GLOBAL_ADMIN, + DENIED_OPERATORS, + PROTECTED_JOIN_COLLECTIONS, + sanitizeAggregation +}; diff --git a/plugins/dbviewer/api/parts/query_guard.js b/plugins/dbviewer/api/parts/query_guard.js new file mode 100644 index 00000000000..db0bbd85dd8 --- /dev/null +++ b/plugins/dbviewer/api/parts/query_guard.js @@ -0,0 +1,57 @@ +/** + * @module plugins/dbviewer/api/parts/query_guard + * @description Helpers that harden the user-supplied parts of a DB Viewer + * find() query (projection and the _id search term). + */ + +'use strict'; + +/** + * Restrict a find() projection to plain field inclusion / exclusion. + * + * A projection value is only allowed to be 0, 1 or a boolean (strict + * include/exclude). Any other value is dropped: + * - expressions and field-path aliases — e.g. { leak: "$password" } or + * { x: { $function: ... } } — would compute new fields from, or rename, + * fields the viewer otherwise removes from the response (MongoDB 4.4+ find() + * projections accept expressions); + * - other numbers (2, NaN, …) are not valid include/exclude values and can + * make the query throw. + * Keeping projections to strict include/exclude removes that whole avenue. + * + * @param {object} projection - parsed projection object (mutated in place) + * @returns {object} changes - keys are the projection fields that were dropped + */ +function sanitizeProjection(projection) { + var changes = {}; + if (!projection || typeof projection !== "object" || Array.isArray(projection)) { + return changes; + } + for (var key in projection) { + if (Object.prototype.hasOwnProperty.call(projection, key)) { + var value = projection[key]; + if (value !== 0 && value !== 1 && value !== true && value !== false) { + changes[key] = true; + delete projection[key]; + } + } + } + return changes; +} + +/** + * Escape a string for safe use as a literal inside a RegExp, so a user-supplied + * search term cannot introduce a pathological pattern (catastrophic + * backtracking / ReDoS). + * + * @param {string} str - raw search term + * @returns {string} the term with all RegExp metacharacters escaped + */ +function escapeRegExp(str) { + return String(str).replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +module.exports = { + sanitizeProjection, + escapeRegExp +}; diff --git a/plugins/guides/frontend/public/javascripts/countly.models.js b/plugins/guides/frontend/public/javascripts/countly.models.js index 7211b7cc840..b0af9eb00b5 100755 --- a/plugins/guides/frontend/public/javascripts/countly.models.js +++ b/plugins/guides/frontend/public/javascripts/countly.models.js @@ -16,7 +16,7 @@ countlyGuides.memberViewedGuides = function(user_id) { CV.$.ajax({ type: "POST", - url: "/guides/viewed", + url: (countlyGlobal.path || "") + "/guides/viewed", dataType: "json", data: { "user_id": user_id, diff --git a/plugins/hooks/api/api.js b/plugins/hooks/api/api.js index 5febf0926f8..fb5a206acdc 100644 --- a/plugins/hooks/api/api.js +++ b/plugins/hooks/api/api.js @@ -1,4 +1,5 @@ const Triggers = require('./parts/triggers/index.js'); +const InternalEventTrigger = require('./parts/triggers/internal_event.js'); const Effects = require('./parts/effects/index.js'); const asyncLib = require('async'); const EventEmitter = require('events'); @@ -13,6 +14,25 @@ const ssrfProtection = require('../../../api/utils/ssrf-protection'); const FEATURE_NAME = 'hooks'; +/** + * Check that the member holds the given right for every app a hook targets. + * Hook write endpoints authorize against params.qstring.app_id only, so the + * apps actually affected by the operation must be re-checked to prevent a + * user with hooks rights on one app from touching hooks of another app. + * @param {function} rightFn - rights.hasCreateRight/hasUpdateRight/hasDeleteRight + * @param {object} member - request member object + * @param {Array} apps - app ids the hook targets + * @returns {boolean} true only if member has the right for every app + */ +function memberHasRightForAllApps(rightFn, member, apps) { + if (!Array.isArray(apps) || apps.length === 0) { + return false; + } + return apps.every(function(appId) { + return rightFn(FEATURE_NAME, appId + "", member); + }); +} + plugins.setConfigs("hooks", { batchActionSize: 0, // size for processing actions each time refreshRulesPeriod: 3000, // miliseconds to fetch hook records @@ -291,6 +311,18 @@ plugins.register("/i/hook/save", function(ob) { return true; } + // Only global admins may create or update hooks that subscribe + // to global (non app-scoped) internal events such as + // /i/users/* or /systemlogs — these carry instance-wide data. + if (!params.member.global_admin + && hookConfig.trigger + && hookConfig.trigger.type === "InternalEventTrigger" + && hookConfig.trigger.configuration + && InternalEventTrigger.GLOBAL_EVENT_TYPES[hookConfig.trigger.configuration.eventType]) { + common.returnMessage(params, 403, "User does not have right to subscribe to this event"); + return true; + } + if (hookConfig.effects) { const effectValidation = await validateEffects(hookConfig.effects); if (!effectValidation.valid) { @@ -302,6 +334,19 @@ plugins.register("/i/hook/save", function(ob) { if (hookConfig._id) { const id = hookConfig._id; delete hookConfig._id; + const existingHook = await common.db.collection("hooks").findOne({ _id: common.db.ObjectID(id) }); + if (!existingHook) { + common.returnMessage(params, 404, "Hook not found"); + return true; + } + // must be allowed to update the hook's current apps, and any + // new set of apps it is being retargeted to + if (!params.member.global_admin && + (!memberHasRightForAllApps(rights.hasUpdateRight, params.member, existingHook.apps) || + (hookConfig.apps && !memberHasRightForAllApps(rights.hasUpdateRight, params.member, hookConfig.apps)))) { + common.returnMessage(params, 403, "User does not have right"); + return true; + } return common.db.collection("hooks").findAndModify( { _id: common.db.ObjectID(id) }, {}, @@ -338,6 +383,11 @@ plugins.register("/i/hook/save", function(ob) { hookConfig.createdBy = params.member._id; // Accessing property now with proper check hookConfig.created_at = new Date().getTime(); } + // a new hook may only target apps the caller can create hooks on + if (!params.member.global_admin && !memberHasRightForAllApps(rights.hasCreateRight, params.member, hookConfig.apps)) { + common.returnMessage(params, 403, "User does not have right"); + return true; + } return common.db.collection("hooks").insert( hookConfig, function(err, result) { @@ -544,34 +594,66 @@ plugins.register("/i/hook/status", function(ob) { try { statusList = JSON.parse(params.qstring.status); } - catch (err) { - log.e('Parse status list failed', params.qstring.status); - common.returnMessage(params, 400, "Invalid status list"); + catch (e) { + common.returnMessage(params, 400, "Invalid status payload"); return; } - const batch = []; - for (const appID in statusList) { - batch.push( - common.db.collection("hooks").findAndModify( - { _id: common.db.ObjectID(appID) }, - {}, - { $set: { enabled: statusList[appID] } }, - { new: false, upsert: false } - ) - ); + if (!statusList || typeof statusList !== "object" || Array.isArray(statusList)) { + common.returnMessage(params, 400, "Invalid status payload"); + return; } - Promise.all(batch).then(function() { - log.d("hooks all updated."); - // Audit log: Hook status updated - plugins.dispatch("/systemlogs", { - params: params, - action: "hook_status_updated", - data: { updatedHooksCount: Object.keys(statusList).length, requestedBy: params.member._id } + const hookIds = Object.keys(statusList); + let objectIds; + try { + objectIds = hookIds.map(function(id) { + return common.db.ObjectID(id); + }); + } + catch (e) { + common.returnMessage(params, 400, "Invalid hook id"); + return; + } + //statusList is keyed by hook _id; verify the caller may update every + //affected hook (across its apps) before toggling any of them + common.db.collection("hooks").find({ _id: { $in: objectIds } }, { projection: { apps: 1 } }).toArray(function(findErr, hooksFound) { + if (findErr) { + log.e('Failed to load hooks for status update: ', findErr); + common.returnMessage(params, 500, "Failed to update hook statuses"); + return; + } + var allowed = !params.member.global_admin + ? (hooksFound.length === hookIds.length && hooksFound.every(function(h) { + return memberHasRightForAllApps(rights.hasUpdateRight, params.member, h.apps); + })) + : true; + if (!allowed) { + common.returnMessage(params, 403, "User does not have right"); + return; + } + const batch = []; + for (const hookId in statusList) { + batch.push( + common.db.collection("hooks").findAndModify( + { _id: common.db.ObjectID(hookId) }, + {}, + { $set: { enabled: statusList[hookId] } }, + { new: false, upsert: false } + ) + ); + } + Promise.all(batch).then(function() { + log.d("hooks all updated."); + // Audit log: Hook status updated + plugins.dispatch("/systemlogs", { + params: params, + action: "hook_status_updated", + data: { updatedHooksCount: Object.keys(statusList).length, requestedBy: params.member._id } + }); + common.returnOutput(params, true); + }).catch(function(err) { + log.e('Failed to update hook statuses: ', err); + common.returnMessage(params, 500, "Failed to update hook statuses: " + err.message); }); - common.returnOutput(params, true); - }).catch(function(err) { - log.e('Failed to update hook statuses: ', err); - common.returnMessage(params, 500, "Failed to update hook statuses: " + err.message); }); }, paramsInstance); return true; @@ -599,9 +681,32 @@ plugins.register("/i/hook/delete", function(ob) { validateDelete(paramsInstance, FEATURE_NAME, function(params) { let hookID = params.qstring.hookID; + let objId; try { + objId = common.db.ObjectID(hookID); + } + catch (err) { + common.returnMessage(params, 400, "Invalid hook id"); + return; + } + //load the hook first so its apps can be authorized; the request was + //only validated against params.qstring.app_id, not the hook's apps + common.db.collection("hooks").findOne({ "_id": objId }, function(findErr, hook) { + if (findErr) { + log.e('delete hook failed', hookID, findErr); + common.returnMessage(params, 500, "Failed to delete an hook"); + return; + } + if (!hook) { + common.returnMessage(params, 404, "Hook not found"); + return; + } + if (!params.member.global_admin && !memberHasRightForAllApps(rights.hasDeleteRight, params.member, hook.apps)) { + common.returnMessage(params, 403, "User does not have right"); + return; + } common.db.collection("hooks").remove( - { "_id": common.db.ObjectID(hookID) }, + { "_id": objId }, function(err, result) { log.d(err, result, "delete an hook"); if (!err) { @@ -616,13 +721,12 @@ plugins.register("/i/hook/delete", function(ob) { }); common.returnMessage(params, 200, "Deleted an hook"); } + else { + common.returnMessage(params, 500, "Failed to delete a hook: " + err.message); + } } ); - } - catch (err) { - log.e('delete hook failed', hookID, err); - common.returnMessage(params, 500, "Failed to delete an hook" + err.message); - } + }); }, paramsInstance); return true; }); @@ -661,6 +765,15 @@ plugins.register("/i/hook/test", function(ob) { } } + // a user may only test a hook for apps they could actually manage; + // validateCreate above only checked the request app_id, so re-check + // every app the hook targets to keep test from running effects (or + // probing data) against apps the member has no hooks rights on + if (!params.member.global_admin && !memberHasRightForAllApps(rights.hasCreateRight, params.member, hookConfig.apps)) { + common.returnMessage(params, 403, "User does not have right"); + return; + } + // trigger process log.d(JSON.stringify(hookConfig), "[hook test config]"); diff --git a/plugins/hooks/api/parts/effects/custom_code.js b/plugins/hooks/api/parts/effects/custom_code.js index dbd10ac92cf..ac775a5e7a6 100644 --- a/plugins/hooks/api/parts/effects/custom_code.js +++ b/plugins/hooks/api/parts/effects/custom_code.js @@ -41,7 +41,14 @@ class CustomCodeEffect { ${code} setResult({ value: params }); `; - const sandbox = new Sandbox(); + // Disable the sandbox's built-in httpRequest helper. v8-sandbox + // enables it by default, which would let custom code make + // arbitrary server-side requests (to loopback, link-local, + // cloud-metadata and other internal targets) completely + // bypassing the SSRF validation applied to the HTTPEffect path. + // Hooks that need outbound HTTP must use the HTTPEffect, whose + // URL is checked with ssrfProtection.isUrlSafe(). + const sandbox = new Sandbox({ httpEnabled: false }); (async() => { const { error, value } = await sandbox.execute({ code: genCode, timeout: 3000, globals: { params } }); diff --git a/plugins/hooks/api/parts/effects/email.js b/plugins/hooks/api/parts/effects/email.js index 804f1c8ab53..b61b8c2093a 100644 --- a/plugins/hooks/api/parts/effects/email.js +++ b/plugins/hooks/api/parts/effects/email.js @@ -49,10 +49,12 @@ class EmailEffect { }; const sendTasks = []; await emailAddress.forEach(address => { - let formatedEmailContent = "
" + JSON.stringify(params, null, 2) + "
"; + let formatedEmailContent = "
" + mail.escapedHTMLString(JSON.stringify(params, null, 2)) + "
"; if (emailTemplate && emailTemplate.length > 0) { try { - formatedEmailContent = utils.parseStringTemplate(emailTemplate, params); + //escape substituted trigger values; the admin-authored + //template markup itself is preserved + formatedEmailContent = utils.parseStringTemplate(emailTemplate, params, null, true); formatedEmailContent = formatedEmailContent.replace(/\n/g, "
"); } catch (e) { diff --git a/plugins/hooks/api/parts/triggers/internal_event.js b/plugins/hooks/api/parts/triggers/internal_event.js index dfce6d18a2d..77bcf8fcf46 100644 --- a/plugins/hooks/api/parts/triggers/internal_event.js +++ b/plugins/hooks/api/parts/triggers/internal_event.js @@ -2,6 +2,54 @@ const plugins = require('../../../../pluginManager.js'); const common = require('../../../../../api/utils/common.js'); const utils = require('../../utils.js'); const log = common.log('hooks:internalEventTrigger'); + +// Event types that are global (not scoped to a single app): new-member events, +// the master tick, and the system-log stream. These carry instance-wide data +// and must only be delivered to hooks owned by a global admin. +const GLOBAL_EVENT_TYPES = { + "/i/users/create": true, + "/i/users/update": true, + "/i/users/delete": true, + "/master": true, + "/systemlogs": true +}; + +/** + * Whether the hook's owner (createdBy) is a global admin. Used to gate the + * global, non app-scoped event types so an app-scoped hook created by a + * non-global member cannot receive instance-wide data (e.g. new-member objects + * or the system-log stream). A hook with no resolvable owner is treated as not + * authorized. + * @param {object} rule - hook rule + * @param {Map} [cache] - optional owner-id -> boolean cache, scoped to one + * event dispatch, so each distinct owner is resolved only once + * @returns {Promise} true if the owner is a global admin + */ +async function isRuleOwnerGlobalAdmin(rule, cache) { + if (!rule || !rule.createdBy) { + return false; + } + const ownerId = rule.createdBy + ""; + // memoize per process() call so each distinct owner is resolved once per + // event dispatch (avoids an N+1 lookup when many hooks share owners) + if (cache && cache.has(ownerId)) { + return cache.get(ownerId); + } + let result = false; + try { + const owner = await common.db.collection("members").findOne({_id: common.db.ObjectID(ownerId)}, {projection: {global_admin: 1}}); + result = !!(owner && owner.global_admin); + } + catch (e) { + log.e("Failed to resolve hook owner for global-event scope check (hook " + (rule._id || "?") + ", createdBy " + ownerId + ")", e); + result = false; + } + if (cache) { + cache.set(ownerId, result); + } + return result; +} + /** * Internal event trigger */ @@ -75,7 +123,16 @@ class InternalEventTrigger { if (!rules.length) { return; } - rules.forEach((rule) => { + // cache of owner-id -> isGlobalAdmin, scoped to this dispatch, so a + // global event reaching many hooks resolves each owner only once + const ownerGlobalAdminCache = new Map(); + for (const rule of rules) { + // global (non app-scoped) events must only reach hooks owned by a + // global admin: an app-scoped hook from a non-global member must + // not receive instance-wide member/system data. + if (GLOBAL_EVENT_TYPES[eventType] && !await isRuleOwnerGlobalAdmin(rule, ownerGlobalAdminCache)) { + continue; + } switch (eventType) { case "/cohort/enter": case "/cohort/exit": { @@ -234,7 +291,7 @@ class InternalEventTrigger { break; } } - }); + } } /** @@ -249,6 +306,10 @@ class InternalEventTrigger { } } +// exposed so the save handler can reject non-global-admins creating/updating +// hooks that subscribe to these global event types +InternalEventTrigger.GLOBAL_EVENT_TYPES = GLOBAL_EVENT_TYPES; + module.exports = InternalEventTrigger; const InternalEvents = [ "/i/apps/create", diff --git a/plugins/hooks/api/utils.js b/plugins/hooks/api/utils.js index a0ad2a5ef4f..6cf7ed4dd3b 100755 --- a/plugins/hooks/api/utils.js +++ b/plugins/hooks/api/utils.js @@ -32,7 +32,7 @@ utils.addErrorRecord = function addErrorRecord(hookId, error, params, effectStep }; -utils.parseStringTemplate = function(str, data, httpMethod) { +utils.parseStringTemplate = function(str, data, httpMethod, escapeHtml) { const parseData = function(obj) { let d = ""; if (typeof obj === 'object') { @@ -49,6 +49,11 @@ utils.parseStringTemplate = function(str, data, httpMethod) { if (httpMethod === 'get') { return encodeURIComponent(d); } + //when the result is rendered as HTML (e.g. an email body) escape the + //substituted values so trigger data cannot inject markup/scripts + if (escapeHtml) { + return ("" + d).replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"').replace(/'/g, '''); + } return d; }; diff --git a/plugins/hooks/frontend/public/javascripts/countly.views.js b/plugins/hooks/frontend/public/javascripts/countly.views.js index 14af71fe402..e6e71ffc3ee 100644 --- a/plugins/hooks/frontend/public/javascripts/countly.views.js +++ b/plugins/hooks/frontend/public/javascripts/countly.views.js @@ -485,6 +485,15 @@ } }, mounted: function() { + // global (non app-scoped) internal events carry instance-wide data + // and may only be subscribed to by global admins; hide them for + // everyone else (the server also rejects them on save). + if (!countlyGlobal.member.global_admin) { + var globalEventTypes = {"/i/users/create": true, "/i/users/update": true, "/i/users/delete": true, "/master": true, "/systemlogs": true}; + this.internalEventOptions = this.internalEventOptions.filter(function(option) { + return !globalEventTypes[option.value]; + }); + } this.getCohortOptioins(); this.getHookOptions(); this.getAlertOptions(); @@ -661,7 +670,7 @@ }, computed: { url: function() { - return window.location.protocol + "//" + window.location.host + "/o/hooks/" + this.value.path; + return window.location.protocol + "//" + window.location.host + (countlyGlobal.path || "") + "/o/hooks/" + this.value.path; }, valuePath: function() { return this.value.path; diff --git a/plugins/hooks/package-lock.json b/plugins/hooks/package-lock.json index 89e10b77749..6ee602603e3 100644 --- a/plugins/hooks/package-lock.json +++ b/plugins/hooks/package-lock.json @@ -459,16 +459,16 @@ } }, "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.6.tgz", + "integrity": "sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==", "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" + "hasown": "^2.0.4", + "mime-types": "^2.1.35" }, "engines": { "node": ">= 6" @@ -598,9 +598,9 @@ } }, "node_modules/hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.4.tgz", + "integrity": "sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==", "license": "MIT", "dependencies": { "function-bind": "^1.1.2" diff --git a/plugins/hooks/tests/authz.js b/plugins/hooks/tests/authz.js new file mode 100644 index 00000000000..6bb7177a2ef --- /dev/null +++ b/plugins/hooks/tests/authz.js @@ -0,0 +1,163 @@ +var request = require('supertest'); +var should = require('should'); +var testUtils = require('../../../test/testUtils'); +request = request(testUtils.url); + +// Regression tests for cross-app authorization on hook write endpoints. +// A user holding the `hooks` permission on app A must not be able to edit, +// toggle, or delete a hook that belongs to app B. + +var baseHookConfig = { + "name": "authz-test", + "description": "desc", + "apps": [], + "trigger": {"type": "APIEndPointTrigger", "configuration": {"path": "11111111-ea4e-420d-bb7e-b3210e5d8b33", "method": "get"}}, + "effects": [{"type": "CustomCodeEffect", "configuration": {"code": "params.a=1"}}], + "enabled": true +}; + +describe('Testing Hooks cross-app authorization', function() { + var VICTIM_APP_ID = ""; + var ATTACKER_APP_ID = ""; + var ATTACKER_API_KEY = ""; + var victimHookId = ""; + + it('should create a victim app with a hook (as admin)', function(done) { + var API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); + request.get('/i/apps/create?api_key=' + API_KEY_ADMIN + '&args=' + JSON.stringify({name: "Hooks Victim App"})) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + VICTIM_APP_ID = res.body._id; + var hookConfig = Object.assign({}, baseHookConfig, {apps: [VICTIM_APP_ID]}); + request.post('/i/hook/save?api_key=' + API_KEY_ADMIN + '&app_id=' + VICTIM_APP_ID) + .send({hook_config: JSON.stringify(hookConfig)}) + .expect(200) + .end(function(err2, res2) { + if (err2) { + return done(err2); + } + victimHookId = res2.body && (res2.body._id || res2.body); + should.exist(victimHookId); + done(); + }); + }); + }); + + it('should create an attacker app and a user with hooks rights on it only', function(done) { + var API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); + request.get('/i/apps/create?api_key=' + API_KEY_ADMIN + '&args=' + JSON.stringify({name: "Hooks Attacker App"})) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + ATTACKER_APP_ID = res.body._id; + var perm = {}; + ["c", "r", "u", "d"].forEach(function(t) { + perm[t] = {}; + perm[t][ATTACKER_APP_ID] = {all: false, allowed: {hooks: true}}; + }); + perm._ = {a: [], u: [ATTACKER_APP_ID]}; + var userParams = { + full_name: 'hooksattacker', + username: 'hooksattacker', + password: 'p4ssw0rD!', + email: 'hooksattacker@mail.test', + permission: perm + }; + request.get('/i/users/create?api_key=' + API_KEY_ADMIN + '&args=' + JSON.stringify(userParams)) + .expect(200) + .end(function(err2, res2) { + if (err2) { + return done(err2); + } + ATTACKER_API_KEY = res2.body && res2.body.api_key; + should.exist(ATTACKER_API_KEY); + done(); + }); + }); + }); + + it('should reject deleting another app\'s hook', function(done) { + request.post('/i/hook/delete?api_key=' + ATTACKER_API_KEY + '&app_id=' + ATTACKER_APP_ID) + .send({hookID: victimHookId}) + .expect(403) + .end(function(err) { + return done(err); + }); + }); + + it('should reject toggling another app\'s hook status', function(done) { + var status = {}; + status[victimHookId] = false; + request.post('/i/hook/status?api_key=' + ATTACKER_API_KEY + '&app_id=' + ATTACKER_APP_ID) + .send({status: JSON.stringify(status)}) + .expect(403) + .end(function(err) { + return done(err); + }); + }); + + it('should reject editing another app\'s hook', function(done) { + var edit = Object.assign({}, baseHookConfig, {apps: [ATTACKER_APP_ID], _id: victimHookId, name: "hijacked"}); + request.post('/i/hook/save?api_key=' + ATTACKER_API_KEY + '&app_id=' + ATTACKER_APP_ID) + .send({hook_config: JSON.stringify(edit)}) + .expect(403) + .end(function(err) { + return done(err); + }); + }); + + it('should reject creating a hook targeting another app', function(done) { + var hookConfig = Object.assign({}, baseHookConfig, {apps: [VICTIM_APP_ID]}); + request.post('/i/hook/save?api_key=' + ATTACKER_API_KEY + '&app_id=' + ATTACKER_APP_ID) + .send({hook_config: JSON.stringify(hookConfig)}) + .expect(403) + .end(function(err) { + return done(err); + }); + }); + + it('should reject testing a hook targeting another app', function(done) { + var hookConfig = Object.assign({}, baseHookConfig, {apps: [VICTIM_APP_ID]}); + request.get('/i/hook/test?api_key=' + ATTACKER_API_KEY + '&app_id=' + ATTACKER_APP_ID + '&hook_config=' + encodeURIComponent(JSON.stringify(hookConfig)) + '&mock_data=' + encodeURIComponent(JSON.stringify({}))) + .expect(403) + .end(function(err) { + return done(err); + }); + }); + + it('should confirm the victim hook still exists and is unchanged', function(done) { + var API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); + request.get('/o/hook/list?api_key=' + API_KEY_ADMIN + '&app_id=' + VICTIM_APP_ID + '&id=' + victimHookId) + .expect(200) + .end(function(err, res) { + if (err) { + return done(err); + } + var list = res.body && res.body.hooksList ? res.body.hooksList : res.body; + var hook = Array.isArray(list) ? list.find(function(h) { + return String(h._id) === String(victimHookId); + }) : null; + should.exist(hook); + hook.name.should.eql(baseHookConfig.name); + done(); + }); + }); + + it('should clean up created apps', function(done) { + var API_KEY_ADMIN = testUtils.get("API_KEY_ADMIN"); + request.get('/i/apps/delete?api_key=' + API_KEY_ADMIN + '&args=' + JSON.stringify({app_id: VICTIM_APP_ID})) + .expect(200) + .end(function() { + request.get('/i/apps/delete?api_key=' + API_KEY_ADMIN + '&args=' + JSON.stringify({app_id: ATTACKER_APP_ID})) + .expect(200) + .end(function() { + done(); + }); + }); + }); +}); diff --git a/plugins/hooks/tests/email.js b/plugins/hooks/tests/email.js new file mode 100644 index 00000000000..83245fb5b76 --- /dev/null +++ b/plugins/hooks/tests/email.js @@ -0,0 +1,27 @@ +require('should'); +var utils = require('../api/utils'); + +// Regression tests for HTML escaping of trigger values substituted into the +// hook email effect template (prevents HTML/script injection into the email +// body via attacker-controlled trigger data). + +describe('Testing hook email template escaping', function() { + it('escapes substituted values when escapeHtml is set', function(done) { + var out = utils.parseStringTemplate("Hello {{name}}", {name: ''}, null, true); + out.should.containEql('<script>'); + out.indexOf('