diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index e650cdacc1d..ba02babecb0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -37,11 +37,11 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v5 # Initializes the CodeQL tools for scanning. - name: Initialize CodeQL - uses: github/codeql-action/init@v3 + uses: github/codeql-action/init@v4 with: languages: ${{ matrix.language }} # If you wish to specify custom queries, you can do so here or in a config file. @@ -52,7 +52,7 @@ jobs: # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). # If this step fails, then you should remove it and run the build manually (see below) - name: Autobuild - uses: github/codeql-action/autobuild@v3 + uses: github/codeql-action/autobuild@v4 # ℹ️ Command-line programs to run using the OS shell. # 📚 https://git.io/JvXDl @@ -66,4 +66,4 @@ jobs: # make release - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@v3 + uses: github/codeql-action/analyze@v4 diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 0069d3fedd2..2b3ad70be08 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@v4 + - uses: actions/checkout@v5 - name: Enable command line shell: bash @@ -45,10 +45,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Log in to Docker Hub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml index 584a16014fb..b0187e7eec6 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@v4 + uses: actions/checkout@v5 - name: Set output id: vars @@ -26,7 +26,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -43,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set output id: vars @@ -57,7 +57,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -74,7 +74,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set output id: vars @@ -88,7 +88,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} @@ -105,7 +105,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Check out the repo - uses: actions/checkout@v4 + uses: actions/checkout@v5 - name: Set output id: vars @@ -119,7 +119,7 @@ jobs: echo ${{ steps.vars.outputs.tag }} - name: Log in to Docker Hub - uses: docker/login-action@74a5d142397b4f367a81961eba4e8cd7edddf772 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef with: username: ${{ secrets.DOCKERHUB_USERNAME }} password: ${{ secrets.DOCKERHUB_TOKEN }} diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 47d0e067092..e21a6099b11 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@v4 + - uses: actions/checkout@v5 - name: Copy code shell: bash @@ -96,7 +96,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Copy code shell: bash @@ -153,7 +153,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Prepare tests shell: bash @@ -205,7 +205,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Copy code shell: bash @@ -268,7 +268,7 @@ jobs: COUNTLY_CONFIG_API_PREVENT_JOBS: true steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v5 - name: Install Chrome shell: bash @@ -278,6 +278,13 @@ jobs: wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb -O /tmp/chrome.deb apt install -y /tmp/chrome.deb + - name: Install Sharp dependencies for image processing + shell: bash + run: | + export DEBIAN_FRONTEND=noninteractive + apt-get update -y + apt-get install -y libvips-dev + - name: Copy code shell: bash run: cp -rf ./* /opt/countly @@ -331,6 +338,6 @@ jobs: working-directory: /opt/countly/ui-tests/cypress run: | ARTIFACT_ARCHIVE_NAME="$(date '+%Y%m%d-%H.%M')_${GITHUB_REPOSITORY#*/}_CI#${{ github.run_number }}_${{ matrix.test_type }}.tar.gz" - mkdir -p screenshots videos - tar zcvf "$ARTIFACT_ARCHIVE_NAME" screenshots videos + mkdir -p screenshots videos downloads + tar zcvf "$ARTIFACT_ARCHIVE_NAME" screenshots videos downloads curl -o /tmp/uploader.log -u "${{ secrets.BOX_UPLOAD_AUTH }}" ${{ secrets.BOX_UPLOAD_PATH }} -T "$ARTIFACT_ARCHIVE_NAME" diff --git a/.github/workflows/release_notice.yml b/.github/workflows/release_notice.yml index 45b00ba67e8..75009d182db 100644 --- a/.github/workflows/release_notice.yml +++ b/.github/workflows/release_notice.yml @@ -17,6 +17,8 @@ jobs: uses: slackapi/slack-github-action@v2.1.1 with: # This data can be any valid JSON from a previous step in the GitHub Action + webhook: ${{ secrets.SLACK_RELEASE }} + webhook-type: incoming-webhook payload: | { "repository": "${{ github.repository }}", @@ -28,7 +30,7 @@ jobs: env: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_RELEASE }} - name: Send custom JSON data to Discord - uses: sarisia/actions-status-discord@v1.15.3 + uses: sarisia/actions-status-discord@v1.15.4 with: webhook: ${{ secrets.DISCORD_WEBHOOK_URL }} nodetail: true diff --git a/.github/workflows/stable-je-deploy.yml b/.github/workflows/stable-je-deploy.yml index e7fbe5ab92e..c98242826a2 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@v4 + - uses: actions/checkout@v5 - name: Deploy server shell: bash diff --git a/CHANGELOG.md b/CHANGELOG.md index 29e619106c3..ddf3b4e6d06 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,211 @@ -## Version 25.03.XX +## Version 25.03.X +Enterprise Fixes: +- [concurrent_users] Fix email check for alert + + +## Version 25.03.27 +Fixes: +- [core-vis] Fix chart legend click event +- [push] Fixed the options of the request being made during mime detection +- [views] Fix view name that is displayed in view table +- [data-manager] Fix last modified data for event and segment + +Enterprise Fixes: +- [concurrent_users] Fix alert threshold comparison +- [dashboards] Add setting to disable public dashboards +- [surveys] Handle multiple survey submission from same user based on survey visibility +- [users] Display user property limits in user profiles when exceeded +- [users] Set correct users widget table rows amount according to selected setting + +## Version 25.03.26 +Fixes: +- [push] Fixed timeout setting +- [security] Fixed injection possibility on res.expose + +Enterprise Fixes: +- [data-manager] Fixed bug when merging events with ampersand symbol in the name +- [groups] Add logs for user updates +- [nps] Sort widgets by internal name and search by name or internal name +- [surveys] Change question map log to debug log +- [surveys] Sort widgets by internal name and search by name or internal name + +Dependencies: +- Bump axios from 1.12.2 to 1.13.1 in /plugins/cognito +- Bump csvtojson from 1.1.12 to 2.0.14 +- Bump eslint-plugin-vue from 10.5.0 to 10.5.1 +- Bump express-rate-limit from 8.1.0 to 8.2.0 +- Bump get-random-values from 4.0.0 to 4.1.0 +- Bump lint-staged from 16.2.4 to 16.2.6 +- Bump mockttp from 4.2.0 to 4.2.1 in /plugins/crash_symbolication +- Bump nodemailer from 7.0.9 to 7.0.10 +- Bump puppeteer from 24.25.0 to 24.27.0 +- Bump vite from 7.1.10 to 7.1.12 + +## Version 25.03.25 +Fixes: +- [crashes] Fixed resolving audit log recording +- [location] Fixed updating none gps coordinate location after gps location was used + +Enterprise Fixes: +- [ab-testing] Add script for fixing variant cohort +- [groups] Fix user permission update after updating user group permission +- [funnels] Fixed delete confirmation using correct button copy + +## Version 25.03.24 +Fixes: +- [jobs] Fix condition for scheduling alert job + +Enterprise Fixes: +- [compliance-hub] Fixed query patterns +- [data-manager] Fixed bug preventing transformation of events ending in a dot +- [data-manager] Fixed segment data deletion +- [license] Stop sending metric after license expired +- [users] Fix add/remove user to profile group +- [users] Remove link to profile group page after removing user from group + +Dependencies +- Bump @faker-js/faker from 10.0.0 to 10.1.0 in /ui-tests +- Bump countly-sdk-nodejs from 24.10.2 to 24.10.3 +- Bump lint-staged from 16.2.3 to 16.2.4 +- Bump puppeteer from 24.23.0 to 24.24.1 + +## Version 25.03.23 +Fixes: +- [events] Entries in the event list are now sorted alphabetically +- [mail] Add smtp debug option for mail module + +Enterprise Fixes: +- [block] Fixed filter for consent events +- [drill] [survey] Fix survey answer in drill +- [funnels] Show notification if funnel results are from cache/task manager +- [revenue] Card in revenue page are now correctly indentified +- [users] Add survey section to user feedback page +- [users] Fixed uploading user profile pictures + + +## Version 25.03.22 +Fixes: +- [alerts] Fix: Migrate alerts to the new events model +- [applications] Persist newly created app after page reload +- [dashboard] Allow users to select text inside the widget without dragging it +- [oidc] Fix for session state storage + +Enterprise Fixes: +- [cohorts] Fix query transformation for profile group +- [drill] [survey] Display survey answer value instead of id in drill result +- [drill] [survey] Fix duplicate question id +- [users] Fix condition for custom property update +- [users] Update user custom field number formatting + +Dependencies: +- Bump axios from 1.8.2 to 1.12.2 in /plugins/hooks +- Bump tar-fs from 3.1.0 to 3.1.1 +- Bump nodemailer from 7.0.6 to 7.0.9 +- Bump fs-extra from 11.3.1 to 11.3.2 +- Bump sharp from 0.34.3 to 0.34.4 +- Bump eslint-plugin-vue from 10.4.0 to 10.5.0 +- Bump sass from 1.92.1 to 1.93.2 +- Bump lint-staged from 16.1.6 to 16.2.3 +- Bump puppeteer from 24.20.0 to 24.23.0 +- Bump mongodb from 6.19.0 to 6.20.0 +- Bump typescript from 5.9.2 to 5.9.3 +- Bump semver from 7.7.2 to 7.7.3 + +## Version 25.03.21 +Fixes: +- [feedback] Prevent showing the application when switching between the NPS and Survey pages +- [populator] Fix NPS generator +- [surveys] Fix survey details results summary tiles display when journeys plugin in not enabled + +## Version 25.03.20 +Fixes: +- [push] Fix: Migrate push to the new events model + +Enterprise Fixes: +- [oidc] fix for sending state in oidc login + +## Version 25.03.19 +Enterprise Fixes +- [home] Fix home download render issue +- [journeys] Fix for handling the skip threshold value when saving the journey +- [journeys] Performance improvement on journey stat user list & UI bugfixes + +Dependencies: +- Bump express-rate-limit from 8.0.1 to 8.1.0 +- Bump mongodb from 6.18.0 to 6.19.0 +- Bump nodemailer from 7.0.5 to 7.0.6 +- Bump puppeteer from 24.17.0 to 24.19.0 +- Bump sass from 1.90.0 to 1.92.1 + +## Version 25.03.18 +Fixes: +- [server-stats] Add new events to breakdown +- [server-stats] Fix breakdown event calculation + +Enterprise Fixes: +- [journeys] Fix for clearing content queue when journey is paused +- [journeys] Fix for content shown event handling +- [journeys] Fix for performance issues when huge number of journey instances created +- [journeys] Update skip threshold when journeys are paused + +Dependencies: +- Bump get-random-values from 3.0.0 to 4.0.0 +- Bump puppeteer from 24.16.1 to 24.16.2 + +## Version 25.03.17 +Enterprise Fixes: +- [ldap] Recursive user search in ldap added +- [license] Update metric endpoint permission + +Dependencies: +- Bump puppeteer from 24.16.2 to 24.17.0 + + +## Version 25.03.16 +Enterprise Fixes: +- [journeys] Fix for skip threshold check in concurrent requests +- [journeys] Prevent showing journey builder when viewing journey list page + +Dependencies: +- Bump get-random-values from 3.0.0 to 4.0.0 +- Bump puppeteer from 24.16.1 to 24.16.2 + +## Version 25.03.15 +Enterprise Fixes: +- [cohorts] Unescape segmentation properties options to prevent duplicated values +- [ldap] Connection timeout values are added to LDAP config + +## Version 25.03.14 +Fixes: +- [dashboard] Localized missing string in the dashboard +- [localization] Added French translations + +Enterprise Fixes: +- [active_directory] Fix for body parser empty request body issue + +Dependencies: +- Bump eslint-plugin-vue from 10.3.0 to 10.4.0 +- Bump fs-extra from 11.3.0 to 11.3.1 +- Bump lint-staged from 16.1.2 to 16.1.4 +- Bump puppeteer from 24.15.0 to 24.16.1 +- Bump sass from 1.89.2 to 1.90.0 +- Bump typescript from 5.8.3 to 5.9.2 + + +## Version 25.03.13 +Features: +- [remote-config] Enable comparing newer/older app version in conditions + +Fixes: +- [remote-config] Fix condition matching with compound conditions + +Enterprise Fixes: +- [flows] Showing correct state for disabled flows +- [surveys] Move "not likely" label next to 0 on mobile screens + + +## Version 25.03.12 Features: - [plugins] Add configuration warning tags to settings UI - [white-labeling] Add sidebar footer label setting to white labeling @@ -7,9 +213,9 @@ Features: Fixes: - [core] Use correct rights validation for loyality - [crashes] Fix free session for home widget -- [crashes] Use na for free session and free user when there's no data - [crashes] Fix trend and change calculation for crash stats -- [push] Show segmentation, geo and cohorts related components in push drawer on editing draft. +- [crashes] Use na for free session and free user when there's no data +- [push] Show segmentation, geo and cohorts related components in push drawer on editing draft. Enterprise Fixes: - [ldap] Error handling in ldap plugin on search error @@ -17,8 +223,8 @@ Enterprise Fixes: - [users] Load table data from report if user table calculation goes to report manager Dependencies: -- Bump puppeteer from 24.14.0 to 24.15.0 - Bump mongodb from 6.17.0 to 6.18.0 +- Bump puppeteer from 24.14.0 to 24.15.0 - Bump supertest from 7.1.3 to 7.1.4 @@ -99,8 +305,8 @@ Dependencies: - Bump sass-embedded in /plugins/content from 1.89.1 to 1.89.2 - Bump @vue-flow/node-resizer in /plugins/content from 1.4.0 to 1.5.0 - Bump @vue-flow/core in /plugins/content from 1.44.0 to 1.45.0 -- Bump vue-i18n in /plugins/content from 11.1.5 to 11.1.6 -- Bump terser in /plugins/content from 5.42.0 to 5.43.0 +- Bump vue-i18n in /plugins/content from 11.1.5 to 11.1.6 +- Bump terser in /plugins/content from 5.42.0 to 5.43.0 - Bump mockttp in /plugins/crash_symbolication from 3.17.1 to 4.0.0 ## Version 25.03.6 @@ -156,7 +362,7 @@ Fixes: Enterprise Fixes: - [heatmaps] Get heatmap data from new drill events collection -- [retention] Fixed report loading +- [retention] Fixed report loading Dependencies: - Bump countly-sdk-web from 25.1.0 to 25.4.0 @@ -315,7 +521,7 @@ Enterprise fixes: - [drill] [license] Update license loader to enable supplying db client - [users] Format data points displayed in user sidebar - [cohorts] Unescape drill texts in cohort component - + Dependencies: - Bump fs-extra from 11.2.0 to 11.3.0 - Bump nodemailer from 6.9.16 to 6.10.0 @@ -330,7 +536,7 @@ Fixes: Security: - Bump puppeteer from 17.1.3 to 23.8.0 -- Bump express from 4.21.0 to 4.21.1 +- Bump express from 4.21.0 to 4.21.1 - Bump sass from 1.79.4 to 1.81.0 - Bump express-session from 1.18.0 to 1.18.1 - Bump cross-spawn from 7.0.3 to 7.0.6 in /ui-tests @@ -453,7 +659,7 @@ Fixes: Security: - Bump puppeteer from 17.1.3 to 23.8.0 -- Bump express from 4.21.0 to 4.21.1 +- Bump express from 4.21.0 to 4.21.1 - Bump sass from 1.79.4 to 1.81.0 - Bump express-session from 1.18.0 to 1.18.1 - Bump cross-spawn from 7.0.3 to 7.0.6 in /ui-tests @@ -584,7 +790,7 @@ Fixes: Enterprise fixes: - [data-manager] changes to allow skipping query rewriting using passed property - [os] fix for changing default repository of CentOS 8 to vault because OS reached EOL - + ## Version 24.05.7 Fixes: - [countly-request]Fix countly-request get and post methods @@ -669,10 +875,10 @@ Enterprise fixes: Fixes: - [populator] Bugfix for - Can't edit populator template - [surveys] fix nps/survey background color -- [alerts] fix for old data remains when the alerts metric is updated -- [star-rating] tooltips updates +- [alerts] fix for old data remains when the alerts metric is updated +- [star-rating] tooltips updates - [populator] fix for correctly checking if different plugins enabled - + Enterprise fixes: - [formulas] null checks - [surveys] survey/nps tooltips updates @@ -714,21 +920,21 @@ Enterprise Features: - [surveys] Responsiveness for survey/NPS/rating widget contents - [users] Profile Groups - [users] Purging user profiles in bulk -- [users] Record hinge as user property +- [users] Record hinge as user property - [users] Select/deselect users - [users] User Profiles widget in dashboards ## Version 23.11.22 Features: - [views] Added deselect All button - + Fixes: - [push] Fix for p12 file not being parsed correctly Enterprise Features: - [ab-testing] Improved query to match experiments to users - [cohorts] Optimization to not calculate full period buckets when loading cohort meta data. Speeds up meta-loading and reduces memory usage. - + Enterprise Fixes: - [cohorts] Correctly preprocess drill query for cohort values. - [drill] Correct time interval for drill meta job. @@ -749,7 +955,7 @@ Enterprise Fixes: Fixes: - [core] Nullcheck in data exports - [events] changed formatSecond to show decimals - + Enterprise Fixes: - [core] missing proxy setting added for enterprise features - [drill] fix for select users correctly when navigating from drill to user @@ -760,24 +966,24 @@ Features: - [reports] log subscribe/unsubscribe to audit logs - [scripts] Additions to checking data per cd in drill - [scripts] Additional crash deletion scripts - + Fixes: - [core] Date picker fixes - [data-points] Correct counting data points to do not include events that are discarded from recording or only update user properties. - + Enterprise Features: - [drill] Outputing extra columns in drill byval download with unformatted values. - + Enterprise Fixes: - [block] Check for valid rule in block - [drill] Added missing columns in byval table download - [funnels] Localization value fixes -- [users] Fixed bug - when the session is selected, the event timeline dropdown is not working. - +- [users] Fixed bug - when the session is selected, the event timeline dropdown is not working. + ## Version 23.11.18 Fixes: - [scripts] Create script for deleting old crashgroups -- [scripts] Script to validate based on cd field for which dates data was recorded +- [scripts] Script to validate based on cd field for which dates data was recorded - [reports] Use localhost only in case of dashboard - [push] Added a chart to show number of sent and acted events - [push] Added the ability to set content-available by default into push settings @@ -813,7 +1019,7 @@ Fixes: - [scripts] custom indexes for customers - [report-manager] Fix warning on adding report manager submenu - [push] Add content-available to apns request payload - + ## Version 23.11.15 Fixes: - [views] Updated views processing logic to prevent data mismatch in cases where there are multiple records in same request decribing same view event @@ -868,7 +1074,7 @@ Fixes: Enterprise Fixes: - [geo] UI for geolocations - [drill] Fixed drill events when applied new filter -- [drill] Fix toggle for drill segmentation +- [drill] Fix toggle for drill segmentation - [formulas] Fix for formulas graph becomes unresponsive - [data-manager] CSV export breaks if there is big_list or array type @@ -913,7 +1119,7 @@ Fixes: - [account-settings] allow generating new api key - [reports] Increase timeouts for report generation - [flows] Fixed bug with incorrectly seletcing date range when calculating flows - + Enterprise Fixes: - [heatmaps] Display a warning on heatmaps if domain is not setup - [license] Fix license metrics dp endpoint calculation @@ -979,7 +1185,7 @@ Enterprise Fixes: - [flows] Recheck logic for date regex for event timeline - [data-manager] Do not show 'all-time' in regeneration drawer in data manager - [drill] typo fixes in drill localization - + ## Version 23.11.5 Fixes: - [core] add required excludes to scripts @@ -995,19 +1201,19 @@ Fixes: - [push] Updated push settings to be able to parameterize message timeout - [hooks] Fixed hook settings texts - [core] udated ingress to support latest api version - + Enterprise Fixes: - [formulas] text change in formulas - [core] Remove tests from packages - + ## Version 23.11.4 Enterprise Fixes: - [drill] Fix for drill snapshot query - + ## Version 23.11.3 Fixes: - [core] Script for fixing drill properties - + Enterprise Fixes: - [drill] Update custom properties only on session end/begin and duration. (Do not process on user_details ) - [drill] Fixed query for generating snapshot to do not error on unexpected object values @@ -1022,7 +1228,7 @@ Fixes: - [core] Always Calculate city and country and store in user object based on passed coordinates, not only on session start - [cms] Moved data fetching for guides/startup/walkthroughs to frontend -[core] Added script to export anonymized drill data - + Enterprise Fixes: - [drill] Rewriting drill query before running it on the database to look also for numeric values for a passed list of values (["1"] => ["1",1]) (Helps to better search for data if values are saved in both ways - as numbers and also as strings) - [drill] Make sure duration is parsed before sending it for database update. @@ -1035,7 +1241,7 @@ Fixes: - [cms] Stop cms calls after failure - [cms] Calculate timedifference against meta entry - [heatmaps] Update deprecated methods in heatmaps.js - + Enterprise Fixes: - [drill] Fix the correct type prefix for widget names - [surveys] Fix the survey api to include previous month @@ -1059,7 +1265,7 @@ Features: - [sdks] Add request metrics - [server-stats] Record breakdown of internal events of data points - [settings] Trim incoming data based on API setting -- [star-rating] Make comments table serverside +- [star-rating] Make comments table serverside - [UI] Loading state fixes to distinguish from no data state - [views] Make table column widths adjustable @@ -1121,7 +1327,7 @@ Fixes: - [star-rating] Fix for targeting reset on toggle - [members] Fix full Name updates in db -Enterprise fixes: +Enterprise fixes: - [crash_symbolication] Fix for symbol file upload - [data-manager] Fix for disabled input in view transformations - [data-manager] Fix for duplicate events being created in event transformation @@ -1149,7 +1355,7 @@ Fixes: - [dashboard] Fix user widget x axis in visualisation - [hooks] Fix hook request json payload -Enterprise Fixes: +Enterprise Fixes: - [attribution] Rename campaign properties to Campaign Platform and Campaign Browser in the drill and user profile filters - [active-directory] Add postinstall for active directory plugin - [okta] Add postinstall for okta plugin @@ -1176,12 +1382,12 @@ Enterprise fixes: - [ab-testing] add fetch_experiments api - [cognito] post install script added to congito install.js - [retention] Fixed Cohort breakdown query on retention - + ## Version 23.06.11 Fixes: -- [crashes] Fix crash visibility filter +- [crashes] Fix crash visibility filter - [push] Fixing wrong timeout handling for APN - + ## Version 23.06.10 Fixes: - [core] Remove trust proxy @@ -1269,7 +1475,7 @@ Fixes: - [report-manager] Fixes for plugin filter selector. - [security] Dependency updates -Enterprise fixes: +Enterprise fixes: - [cohorts] fix ui bug when adding User Behavior Segmentation, the items don't fit the box. - [flows] null check in flows job @@ -1282,7 +1488,7 @@ Fixes: - [hooks] trigger hooks, if multiple hooks are listening to the same trigger - [whitelabeling] localization fixes -Enterprise fixes: +Enterprise fixes: - [drill] Fix empty Drill query with empty result produces none empty table - [drill] Fix save visualisations - [drill] Prevent error with trying set empty string for values upon recording meta biglist data @@ -1345,7 +1551,7 @@ Enterprise fixes: New Features: - [app_versions] display time series data - [dashboards] new time series type for Technology section -- [dashboards] SDK statistics widgets +- [dashboards] SDK statistics widgets - [events] added event comparison by average duration - [events] added search in available segments - [events] show omitted segments @@ -1452,7 +1658,7 @@ Fixes: - [core] Update app details response to check permission object when listing app admins and users - [core] Sum showing up in Events breakdown that has only Count - [push] Fixes regarding push delivery in users’ timezones -- [push] Drill filter for push action event +- [push] Drill filter for push action event Enterprise fixes: - [surveys] Fix nps/ias popups not working in firefox @@ -1628,7 +1834,7 @@ Enterprise fixes: - [data-manager] Auto enable/disable global masking setting on enabling/disabling masking. - [data-manager] Fix drawer opening issue - [groups] Showing correct user count in each group. -- [users] Showing in users profile only those cohorts user is currently in. +- [users] Showing in users profile only those cohorts user is currently in. ## Version 22.09.16 Fixes: @@ -1692,7 +1898,7 @@ Enterprise fixes: ## Version 22.09.14 Fixes: - [core] Always use random initialization vector if not provided for encryption -- [core] Fix incorrect changing of platform to Windows Phone 10 for Windows 10 +- [core] Fix incorrect changing of platform to Windows Phone 10 for Windows 10 - [dashboards] Fix incorrect data & fluctations of visualisation in analytics widgets Enterprise fixes: @@ -1735,7 +1941,7 @@ Enterprise fixes: ## Version 22.09.11 Fixes: - [compliance-hub] Fixes for table export. -- [core] Local table export improvements to allow sorting. +- [core] Local table export improvements to allow sorting. - [data-manager] Fixes for event transformation drawer. - [dbviewer] Storing aggregation pipeline results in reports if they take long to calculate. - [plugins] Update internal-events endpoint access right @@ -1749,7 +1955,7 @@ Fixes: Enterprise fixes: - [ab-testing] AB testing bayesian models compilation fixed - [attribution] Fixed issues with invalid url after edit -- [attribution] Fixes for platform recording +- [attribution] Fixes for platform recording - [cohorts] Bugfix for cohort data merging on user merge. - [cohorts] Fixed issues for realtime cohort update on requests with only user properties - [concurrent-users] Number visualization widget @@ -1772,7 +1978,7 @@ Fixes: - [ui] table export column titles are not user friendly Enterprise fixes: -- [attribution] added typo control for platform when parsing user-agent parameters. +- [attribution] added typo control for platform when parsing user-agent parameters. - [data-manager] invalid values on opening form when editing transformation with regexp in data-manager - [oidc] add same site cookie fallback - [oidc] generate password moved to common @@ -1802,7 +2008,7 @@ Fixes: Enterprise fixes: - [cohorts] improved speed for loading cohort widgets in dashboards - [data-manager] fixed for missing data type in user props -- [data-manager] fixed user properties sort +- [data-manager] fixed user properties sort - [drill] adding stringified Drill query to the export file name - [funnels] fixed for false error ouptut in logs if funnels dashboard widget does not have filter query - [push_approver] correct members query @@ -2009,7 +2215,7 @@ Fixes: - [apps] app management sidebar app icon margin alignment - [crashes] fixes new crash dispatch - [crashes] use common styles for the tab element -- [dashboards] note in dashboards escaping fixed +- [dashboards] note in dashboards escaping fixed - [device-list] add missing Apple devices - [events] check for existience of map key in overview - [hooks] fix app filter for new crashes @@ -2094,7 +2300,7 @@ Fixes: - [core] env variables in tests - [dbviewer] fixes in dbviewer aggregation pipeline - [events] fixed . usage in events for some cases -- [logger] change display name of Request Logs to Incoming Data Logs +- [logger] change display name of Request Logs to Incoming Data Logs - [push] allowing editing created messages - [push] correct formatting for sent number in messages table - [push] fixes for user merging @@ -2542,7 +2748,7 @@ Fixes: - [crashes] fixed chart color - [crashes] fixed crashes stats - [populator] added control when input has a comma -- [push] Revert "delete unsued preview push images" +- [push] Revert "delete unsued preview push images" - [push] fixes in upgrade script - [push] making sure audience pusher works if there's no token - [push] proxy support, better configs & lots of fixes @@ -2625,7 +2831,7 @@ Eneterprise: ## Version 20.11.2.14 Fixes: -- [cmd] use new hash for user management +- [cmd] use new hash for user management - [frontend] Discard the first week in ticks if it is the 7th day - [populator] styling fix for CE - [push] no personalisation in CE fix @@ -2654,7 +2860,7 @@ Enterprise fixes: Fixes: - [prelogin] do not use double params in templates -- [scripts] epel repo installing only for centos 7 +- [scripts] epel repo installing only for centos 7 Enterprise Improvements: - [active_directory] azure ad latest client & fixing group pagination @@ -3066,7 +3272,7 @@ Enterprise Improvements: * [performance-monitoring] network response latency overall percentages and breakdown by country * [populator] add more template based views with heatmap data for web app type * [remote-config] add support for does not contain -* [reportmanager] display errors in the report manager table +* [reportmanager] display errors in the report manager table **Enterprise Improvements** * [block] do not require segmentation for blocking events @@ -3107,7 +3313,7 @@ Enterprise Improvements: * [api] fixed api side aggregated data user correction in some cases * [api] fixed escaping filenames in headers * [api] reset period object before getting query time ranges -* [api] respect city settings when it comes to user +* [api] respect city settings when it comes to user * [core] fixes running countly in sub directory * [core] improve countly user password change experience * [crashes] double dots on y axis for crash/session ratio @@ -4055,11 +4261,11 @@ Enterprise Improvements: * [cohorts] Remove deleted cohorts from selection * [dashboards] Fix widget drawer reset * [dashboards] Hide sidebar toggle in dashboards view -* [drill] Fixed limited connection pool size +* [drill] Fixed limited connection pool size * [drill][block][cohorts] convert numeric values to number only for custom properties * [funnels] Calculation of total users in a perriod changed to get sessions from drill database. * [funnels] Fixed last row data problem. -* [funnels] Funnels bars length issue has been fixed. +* [funnels] Funnels bars length issue has been fixed. * [live] Prevent realtime bar content wrapping * [live] Responsive UI modifications * [revenue] Responsive UI modifications @@ -4274,7 +4480,7 @@ Enterprise Improvements: * New topbar that holds many action buttons. This replaces bottom bar and adds app selector, dashboard selector and configuration options (e.g password change, settings, language switcher etc). * Rich push support for iOS and Android, which includes ability to send images and videos to devices. * Interactive push support for both platforms, with ability to add up to 3 buttons in a push notification. -* New Assistant plugin can be used to get more insights from devices and keeps you up to date with data. +* New Assistant plugin can be used to get more insights from devices and keeps you up to date with data. * Inside Crash Analytics, now it's possible to make multi crash selection for actions like resolve, unresolve, hide, show, delete, etc * Event logs plugin can record whole request data * Record metrics and some default values even without sessions @@ -4371,141 +4577,141 @@ This is a bugfix release. **Improvements** -- New user interface: 16.12 release includes the biggest visual overhaul to the entire Countly user interface, greatly improving not only the UI but also the user experience. Dashboard is now faster than ever with simplified graphs, icons, less CSS and markup. -- New languages: Countly is translated into Hungarian and Vietnamese, and now [supports more than 10 languages](https://www.transifex.com/projects/p/countly/). -- 5 new plugins - - [Compare:](http://count.ly/plugins/compare) All custom event and application data can easily be compared on a time series chart. - - [Star rating:](http://count.ly/plugins/star-rating) A simple plugin in order to understand end user’s ratings about your application. This plugin shows a popup when called on the SDK side (inside the mobile app) prompted the user to submit send rating information. - - [Slipping Away Users:](https://count.ly/plugins/slipping-away-users) This plugin displays a list (and count) of users who haven’t used the application for a particular period, e.g for 7, 14, 30, 60 and 90 days. - - [Server stats:](https://count.ly/plugins/data-points/) This plugin displays how many data points (sum of sessions, custom events, pageviews, crashes and push data) a Countly server has collected for the last 3 months. - - Desktop analytics: Countly now has support for Windows and Mac OS X desktop application types. User interface changes accordingly to provide relevant information for desktop application types. -- Security - - A new extensive login security plugin makes sure brute force login attempts are identified and eliminated by limiting number of wrong login attempts. - - System administrators can specify how strong passwords need to be. Minimum password requirements such as length, uppercase or special characters can be set. - - There are several additional HTTP response headers for a more secure infrastructure. - - There is a password expiration mechanism, editable from Configurations. - - Proper HTML escaping has been added to prevent HTML injections, editable from Configurations. - - Javascript errors are hidden from the browser console. - - MongoDB password in configuration file can be set in an encrypted way. - - Countly can be configured to use a salt (from Management → Applications and inside the SDK) to add checksum to SDK requests in order to prevent parameter tampering. - - System administrator can lock users in order to prevent them accessing the dashboard or API. -- Push notifications - - Push overview is redesigned, to show only meaningful and important metrics. Instead of unreliable numbers that change from platform to platform (eg. delivery rate), we simplified metric page to show most important numbers and past performance, based on weekly and monthly deliveries. - - It’s possible to view how a push message will look on Android and iOS prior to sending it. - - You can send a push notification to a user based on her/his local timezone. - - Geolocation definition is greatly improved, using OpenStreetMap and Leaflet JS. - - Geolocations can now be app specific or global. - -- Crash analytics - - Error logs are now syntax highlighted for easier readability. - - Fix displaying crashes for web apps externally. - - Do not allow bots to index shared crashes which can be read by 3rd parties. - -- System logs - - There are now more than 30 system logs stored for audit purposes (Management → System Logs). - - Filter system logs based on specific user and user actions. - - System logs display before and after values for update operations. - - Actions of a specific user can be accessed by clicking “View User Actions” button under Management → Users. - -- General - - New horizontal bar chart visualisation is added and is used for Analytics → Platforms and Analytics → Densities. - - iOS density and web pages pixel ratio has been added to Density plugin, and values are now segmentable by platform. - - Added “Show details” link to Management → Applications which displays information about the application including creation, edit, last data recorded time and all users who have access to that application. - - Added a configuration option to prevent crash list from growing too long. +- New user interface: 16.12 release includes the biggest visual overhaul to the entire Countly user interface, greatly improving not only the UI but also the user experience. Dashboard is now faster than ever with simplified graphs, icons, less CSS and markup. +- New languages: Countly is translated into Hungarian and Vietnamese, and now [supports more than 10 languages](https://www.transifex.com/projects/p/countly/). +- 5 new plugins + - [Compare:](http://count.ly/plugins/compare) All custom event and application data can easily be compared on a time series chart. + - [Star rating:](http://count.ly/plugins/star-rating) A simple plugin in order to understand end user’s ratings about your application. This plugin shows a popup when called on the SDK side (inside the mobile app) prompted the user to submit send rating information. + - [Slipping Away Users:](https://count.ly/plugins/slipping-away-users) This plugin displays a list (and count) of users who haven’t used the application for a particular period, e.g for 7, 14, 30, 60 and 90 days. + - [Server stats:](https://count.ly/plugins/data-points/) This plugin displays how many data points (sum of sessions, custom events, pageviews, crashes and push data) a Countly server has collected for the last 3 months. + - Desktop analytics: Countly now has support for Windows and Mac OS X desktop application types. User interface changes accordingly to provide relevant information for desktop application types. +- Security + - A new extensive login security plugin makes sure brute force login attempts are identified and eliminated by limiting number of wrong login attempts. + - System administrators can specify how strong passwords need to be. Minimum password requirements such as length, uppercase or special characters can be set. + - There are several additional HTTP response headers for a more secure infrastructure. + - There is a password expiration mechanism, editable from Configurations. + - Proper HTML escaping has been added to prevent HTML injections, editable from Configurations. + - Javascript errors are hidden from the browser console. + - MongoDB password in configuration file can be set in an encrypted way. + - Countly can be configured to use a salt (from Management → Applications and inside the SDK) to add checksum to SDK requests in order to prevent parameter tampering. + - System administrator can lock users in order to prevent them accessing the dashboard or API. +- Push notifications + - Push overview is redesigned, to show only meaningful and important metrics. Instead of unreliable numbers that change from platform to platform (eg. delivery rate), we simplified metric page to show most important numbers and past performance, based on weekly and monthly deliveries. + - It’s possible to view how a push message will look on Android and iOS prior to sending it. + - You can send a push notification to a user based on her/his local timezone. + - Geolocation definition is greatly improved, using OpenStreetMap and Leaflet JS. + - Geolocations can now be app specific or global. + +- Crash analytics + - Error logs are now syntax highlighted for easier readability. + - Fix displaying crashes for web apps externally. + - Do not allow bots to index shared crashes which can be read by 3rd parties. + +- System logs + - There are now more than 30 system logs stored for audit purposes (Management → System Logs). + - Filter system logs based on specific user and user actions. + - System logs display before and after values for update operations. + - Actions of a specific user can be accessed by clicking “View User Actions” button under Management → Users. + +- General + - New horizontal bar chart visualisation is added and is used for Analytics → Platforms and Analytics → Densities. + - iOS density and web pages pixel ratio has been added to Density plugin, and values are now segmentable by platform. + - Added “Show details” link to Management → Applications which displays information about the application including creation, edit, last data recorded time and all users who have access to that application. + - Added a configuration option to prevent crash list from growing too long. - Better logging for uncaught/database errors & crashes. - Error logs (Management → Error logs) now output shorter logs, eliminating potential page slow-downs when viewing this page. - - Single install script (countly.install.sh) which auto-detects which install procedure to execute based on OS and OS version (e.g Ubuntu, Red Hat or CentOS). - - OS based MongoDB version is installed and configured automatically. - - Accept all timestamps in second, millisecond or float format from the SDKs. - - There is no need to call begin_session to create a user on server. This way, a user is created for any request with a new device_id. - - Allow changing number of items displayed in server side paginated tables (e.g 50, 100 or 200). - - Management → Users displays the last login time. - - Clearing an application now only clears analytical data and leaves all other data (e.g configuration in push notifications or attribution analytics). - - Time ago now displays actual time on hover tooltip. - - Separate export and display data for some tables. - - Export file name now changes based on where data is exported from, to eliminate file mixups. - - Each user can now maintain their own app sort list. Previously when a change was made, it affected all users. - - Instead of mobile device and model names (e.g SM-G930F), now we use marketing names of corresponding models (e.g Galaxy S7) under Devices and filtering dropdowns, using Google’s Android device mapping list. - - Carriers are filtered out and converted to names using MCC and MNC codes. - - For image resizing, jimp library is used instead of sharp for less OS specific library dependencies. - - Application administrator can change App key and all users can change their API key. - - Countly command line has autocomplete capabilities. Also new commands are added, namely countly reindex (reapply all Countly database indexes), countly encrypt (to encrypt a value), countly decrypt (to decrypt a value), countly task (to run grunt tasks in a more convenient way) and countly config (to allow unsetting configuration values). - - Sources plugin uses preprocessed data for faster loading and can extract keywords from referral data and display it under Analytics → Search Terms. - - DBViewer plugin - - Allow users with read permission to access data for specific application(s) she/he has access to. - - Enhanced API to provide filtering capabilities based on MongoDB query mechanism. - - - Populator plugin can generate more realistic and platform dependent data, to onboard end users easier. - - IDFA fix plugin is introduced to ignore opted out iOS users until new app version upgrade. + - Single install script (countly.install.sh) which auto-detects which install procedure to execute based on OS and OS version (e.g Ubuntu, Red Hat or CentOS). + - OS based MongoDB version is installed and configured automatically. + - Accept all timestamps in second, millisecond or float format from the SDKs. + - There is no need to call begin_session to create a user on server. This way, a user is created for any request with a new device_id. + - Allow changing number of items displayed in server side paginated tables (e.g 50, 100 or 200). + - Management → Users displays the last login time. + - Clearing an application now only clears analytical data and leaves all other data (e.g configuration in push notifications or attribution analytics). + - Time ago now displays actual time on hover tooltip. + - Separate export and display data for some tables. + - Export file name now changes based on where data is exported from, to eliminate file mixups. + - Each user can now maintain their own app sort list. Previously when a change was made, it affected all users. + - Instead of mobile device and model names (e.g SM-G930F), now we use marketing names of corresponding models (e.g Galaxy S7) under Devices and filtering dropdowns, using Google’s Android device mapping list. + - Carriers are filtered out and converted to names using MCC and MNC codes. + - For image resizing, jimp library is used instead of sharp for less OS specific library dependencies. + - Application administrator can change App key and all users can change their API key. + - Countly command line has autocomplete capabilities. Also new commands are added, namely countly reindex (reapply all Countly database indexes), countly encrypt (to encrypt a value), countly decrypt (to decrypt a value), countly task (to run grunt tasks in a more convenient way) and countly config (to allow unsetting configuration values). + - Sources plugin uses preprocessed data for faster loading and can extract keywords from referral data and display it under Analytics → Search Terms. + - DBViewer plugin + - Allow users with read permission to access data for specific application(s) she/he has access to. + - Enhanced API to provide filtering capabilities based on MongoDB query mechanism. + + - Populator plugin can generate more realistic and platform dependent data, to onboard end users easier. + - IDFA fix plugin is introduced to ignore opted out iOS users until new app version upgrade. **Performance** -- Countly now uses a data splitting algorithm on all metrics, events and users collections. This results in better performance for high traffic deployments and takes better advantage of MongoDB sharding mechanism. -- Added better handling of capped collections, indexing and reindexing options. -- Now there is a single point for updating users collection, resulting in less read and writes from SDK requests. -- Using objects instead of MongoDB arrays for meta data. -- Optimize health check (ping) request. - +- Countly now uses a data splitting algorithm on all metrics, events and users collections. This results in better performance for high traffic deployments and takes better advantage of MongoDB sharding mechanism. +- Added better handling of capped collections, indexing and reindexing options. +- Now there is a single point for updating users collection, resulting in less read and writes from SDK requests. +- Using objects instead of MongoDB arrays for meta data. +- Optimize health check (ping) request. + ### Changelog specific to Enterprise Edition (available to Enterprise Edition customers only) -- User Profiles, Drill, Funnel and several other plugins are simpler and more easier to work with, using a modern and up to date user interface. -- Retention plugin, Funnels and User Profiles have a visual overhaul to show even smallest details, all introducing new designs. -- Drill plugin - - Checking property limits correctly, also for user properties. - - Better labels and data sorting for time buckets with periods larger than a year. - - More precise event timeline ordering based on unique millisecond timestamps from the SDKs. - - Big list dropdown implementation for large amounts of list values (e.g for sources and views). +- User Profiles, Drill, Funnel and several other plugins are simpler and more easier to work with, using a modern and up to date user interface. +- Retention plugin, Funnels and User Profiles have a visual overhaul to show even smallest details, all introducing new designs. +- Drill plugin + - Checking property limits correctly, also for user properties. + - Better labels and data sorting for time buckets with periods larger than a year. + - More precise event timeline ordering based on unique millisecond timestamps from the SDKs. + - Big list dropdown implementation for large amounts of list values (e.g for sources and views). -- Restrict plugin - - Improved restriction UI. - - Restrict API access for blocked users. +- Restrict plugin + - Improved restriction UI. + - Restrict API access for blocked users. -- User Profiles plugin - - Custom properties are shown in alphabetical order. - - Correctly display user's funnel progress and device names. +- User Profiles plugin + - Custom properties are shown in alphabetical order. + - Correctly display user's funnel progress and device names. -- Attribution Analytics: - - Correctly record unique clicks for some time periods. - - When there is a long list of campaigns, it’s possible to hide them so they don’t clutter user interface. - - Greatly improved user experience and user interface. - - Re-designed campaign popups. +- Attribution Analytics: + - Correctly record unique clicks for some time periods. + - When there is a long list of campaigns, it’s possible to hide them so they don’t clutter user interface. + - Greatly improved user experience and user interface. + - Re-designed campaign popups. -- When recaptcha is enabled from Configurations, it asks for recaptcha confirmation on login. +- When recaptcha is enabled from Configurations, it asks for recaptcha confirmation on login. ## Version 16.06 -This version provides several features and bugfixes to both server and SDKs. There are a lot of improvements in Countly core, and you are advised to upgrade. Below you can find notable changes for both Community Edition and Enterprise Edition. +This version provides several features and bugfixes to both server and SDKs. There are a lot of improvements in Countly core, and you are advised to upgrade. Below you can find notable changes for both Community Edition and Enterprise Edition. ### Changelog for both Community Edition and Enterprise Edition -* Feature: We developed Countly Code Generator (http://code.count.ly) to help developers integrate their SDKs easily. +* Feature: We developed Countly Code Generator (http://code.count.ly) to help developers integrate their SDKs easily. * Feature: We provided several one liner explanations in Countly configuration options (under dashboard > Management > Configuration) -* Feature: Charts now full day for today's chart, instead of capping to current time. -* Switched from MongoDB 3.0 to 3.2. +* Feature: Charts now full day for today's chart, instead of capping to current time. +* Switched from MongoDB 3.0 to 3.2. * Feature: Command line now checks whether user is root and displays meaningful message for root needed commands * Feature: App key of application and API key of user can be changed from dashboard. This is nice in circumstances where keys should be modified in SDK but this is not a viable method. -* Feature: Previously it wasn't possible to rename events with key names containing dots. As you may have guessed, this is not the case any more. +* Feature: Previously it wasn't possible to rename events with key names containing dots. As you may have guessed, this is not the case any more. * Feature: Countly now uses bulk report sending through jobs, rather than cronjob for each separate report. -* Feature: Reports now also display overall events data and also benefit from datatables library when managing reports. -* Feature: We dropped using Imagemagick, and started using Sharp node module instead. +* Feature: Reports now also display overall events data and also benefit from datatables library when managing reports. +* Feature: We dropped using Imagemagick, and started using Sharp node module instead. * Feature: Add IPv6 listen directive to Nginx config to make sure we are ready to use IPv6 in the future. -* Bugfix: There is a fix in bulk API that now helps API run smoothly in certain conditions. +* Bugfix: There is a fix in bulk API that now helps API run smoothly in certain conditions. * Bugfix: All known issues with push notifications have been fixed. * Bugfix: Date selector issue inside push notifications have been fixed. * Bugfix: Corrected user estimation with active users data. * Bugfix: Fixed unique click reporting in attribution. * User experience: App management is visually improved with hints and value order. * User experience: All apps data fetching is greatly optimized. -* User experience: User management table is redesigned with datatables. -* User experience: Data populator has been revamped so it generates less random and more meaningful data with less overhead for browser. +* User experience: User management table is redesigned with datatables. +* User experience: Data populator has been revamped so it generates less random and more meaningful data with less overhead for browser. * User experience: We removed unused fields for web analytics. -* User experience: When Google services are disabled (mainly for servers in China), switching between cities and countries and displaying simple table of countries on dashboard is now possible. +* User experience: When Google services are disabled (mainly for servers in China), switching between cities and countries and displaying simple table of countries on dashboard is now possible. * User experience: Improved plugin state syncing between two Countly servers, with option to disable it. -* User interface: There is now a new and improved side bar UI which looks and behaves a lot better than the old, one-level navigation bar. +* User interface: There is now a new and improved side bar UI which looks and behaves a lot better than the old, one-level navigation bar. * User interface: There is a new, shiny pre-login page design that you'll probably love. -* User interface: Main dashboard has been redesigned and better graph tooltips have been added to graphs. +* User interface: Main dashboard has been redesigned and better graph tooltips have been added to graphs. * User interface: Removed app category from app creation since we think your time is valuable. -* User interface: Whole UI is now more modern - lots of small & lovely retouches everywhere +* User interface: Whole UI is now more modern - lots of small & lovely retouches everywhere * User interface: Loading bar has been renewed with a modern one. * User interface: When clicked on cog, user settings are now displayed in full screen instead of popup. @@ -4523,10 +4729,10 @@ This version provides several features and bugfixes to both server and SDKs. The * Attribution Analytics: User can hide a campaign when completed, to declutter user interface. * Attribution Analytics: Optimization of loading data by separating campaign properties and analytical data. * New plugin: Retention with segments, where retention table can be drilled down into segmentation values. -* New plugin: Restrict access, where admin can define who can see what part of the dashboard. +* New plugin: Restrict access, where admin can define who can see what part of the dashboard. * New plugin: Block requests, where admin can block certain type of requests coming from devices or web apps. -### Updated SDKs +### Updated SDKs * Windows Phone SDK updated, with 60 seconds intervals instead of 20. * iPhone SDK updated, with many features and bugfixes. @@ -4543,11 +4749,11 @@ This version provides several features and bugfixes to both server and SDKs. The * Fixed DB settings for replica sets * Fixed problem with data on Sharded servers (documents coming in different order) * Fixed MongoDB configuration on Ubuntu Willy - * Fixed GCM sent count for tokens replaced by GCM + * Fixed GCM sent count for tokens replaced by GCM * Use NodeJS 5.5 for compatibility with push functionality * Fixed inconsistencies with MongoDB 3 findAndModify * Fixed help localization for app version - * Added remote installer script to install Countly + * Added remote installer script to install Countly wget -qO- http://c.ly/install | bash @@ -4562,7 +4768,7 @@ This version provides several features and bugfixes to both server and SDKs. The ### Changelog for both Community Edition and Enterprise Edition * Web Analytics feature, one of the major app types Countly now supports - * Support for different app types, where each app type can have different views and dashboards + * Support for different app types, where each app type can have different views and dashboards * HTTP/2 transport for Apple Push Notifications service, single certificate for both: development & production environments * Sources plugin, showing sources of your Web visitors or Android app installations (replacing stores plugin) * Support for themeing, and switching between different themes @@ -4584,7 +4790,7 @@ This version provides several features and bugfixes to both server and SDKs. The * User Profiles - store custom properties as arrays, for multiple values, as well as provide atomic on server operations, like increase, max, min, etc. * More drillable properties and Drill property categorization * Allow tracking custom segments with Attribution Analytics - + ## Version 15.08 @@ -4608,10 +4814,10 @@ This version provides several features and bugfixes to both server and SDKs. The * Languages should show long language names instead of language codes ([Issue #140](https://github.com/Countly/countly-server/issues/140)) ### Changelog specific to Enterprise Edition (available for Enterprise Edition customers) - + * Enhanced Drill (Query Builder) with new metrics, user properties, custom properties, attribution campaigns and crashes - * Funnel segment applied to all steps ([Issue #173](https://github.com/Countly/countly-server/issues/173)) - * Added Organic data to Referal Analytics ([Issue #153](https://github.com/Countly/countly-server/issues/153)) + * Funnel segment applied to all steps ([Issue #173](https://github.com/Countly/countly-server/issues/173)) + * Added Organic data to Referal Analytics ([Issue #153](https://github.com/Countly/countly-server/issues/153)) * New metrics added to User Profiles, e.g crashes, attribution and other ([Issue #170] (https://github.com/Countly/countly-server/issues/170)) @@ -4640,7 +4846,7 @@ This version provides several features and bugfixes to both server and SDKs. The ## Version 15.03.02 -### Changelog for both Community Edition and Enterprise Edition +### Changelog for both Community Edition and Enterprise Edition * Fixed get_events api method * Fixed Docker support @@ -4659,17 +4865,17 @@ This version provides several features and bugfixes to both server and SDKs. The ## Version 15.03 - -### Changelog specific to Community Edition + +### Changelog specific to Community Edition * Introducing Plugins system, allowing other developers to write plugins which would extend Countly functionality without changing/breaking the core. For more information on how to write a plugin, see [Countly resources](http://resources.count.ly) - + * Lots of plugins come with new functionality in this release, including: * Data Populator * Event Logger * Database Viewer * System Logger - + * New core and scalable data structure, dividing data into years and months to prevent reaching MongoDB document size limit. * Upgraded to using latest Mongoskin version and newer MongoDB driver which supports new connection string format (https://github.com/Countly/countly-server/issues/124) * Countly now ensures that it uses latest MongoDB version @@ -4691,10 +4897,10 @@ This version provides several features and bugfixes to both server and SDKs. The ### Changelog specific to Enterprise Edition (available for Enterprise Edition customers) - + * Fixed displaying long funnel names (https://github.com/Countly/countly-server/issues/107) * Numerous other minor fixes and improvements - + ## Version 14.08 @@ -4735,8 +4941,8 @@ This version provides several features and bugfixes to both server and SDKs. The ## Version 13.06 * Added session durations view that shows users categorized into predefined - session duration buckets. User is categorized into one of 0-10 seconds, - 11-30 seconds, 31-60 seconds, 1-3 minutes, 3-10 minutes, 10-30 minutes, + session duration buckets. User is categorized into one of 0-10 seconds, + 11-30 seconds, 31-60 seconds, 1-3 minutes, 3-10 minutes, 10-30 minutes, 30-60 minutes or > 1 hour according to this session duration (accessible from Engagement > Session durations) * Added resolutions view that shows detailed device resolution data @@ -4749,16 +4955,16 @@ This version provides several features and bugfixes to both server and SDKs. The that returns ready-to-use metrics for today, 7 days and 30 days. This API is used by Countly Mobile Apps. * Added individual event key deletion to event configuration modal. - * Improved and optimized update mechanism during dashboard navigation. + * Improved and optimized update mechanism during dashboard navigation. Navigation is now much more smoother. - * Added a script (bin/geoip-updater.sh) to fetch and update geoip data. + * Added a script (bin/geoip-updater.sh) to fetch and update geoip data. Running this script will update country and city database from Maxmind database. * Various performance and visual improvements to Events view. * Added switch to turn off or change session_duration limit of 120 seconds in api/config.js (session_duration_limit). * Added host configuration to both app.js and api.js configuration files - (/frontend/express/config.js and /api/config.js) to make it possible to + (/frontend/express/config.js and /api/config.js) to make it possible to run dashboard and application on different servers (defaults to localhost) ## Version 12.12 @@ -4780,61 +4986,61 @@ This version provides several features and bugfixes to both server and SDKs. The ## Version 12.09 - * Added localization support. All the pages have translations in the - following languages: Chinese, Dutch, French, German, Italian, Japanese, + * Added localization support. All the pages have translations in the + following languages: Chinese, Dutch, French, German, Italian, Japanese, Spanish and Turkish (https://www.transifex.com/projects/p/countly/). - * Added city level location information to countries view. City level - location information is available only for the country selected in - timezone configuration of an application. - * Added ghost graphs for all the 6 time graphs on the dashboard view. A - light gray graph will represent the previous period. For instance if "30 - days" is selected, ghost graph will show the stats for the previous 30 + * Added city level location information to countries view. City level + location information is available only for the country selected in + timezone configuration of an application. + * Added ghost graphs for all the 6 time graphs on the dashboard view. A + light gray graph will represent the previous period. For instance if "30 + days" is selected, ghost graph will show the stats for the previous 30 days. * Added current month to the available time buckets. - * Optimized total user calculation for date ranges other than current + * Optimized total user calculation for date ranges other than current year, month and day which already show the absolute number. ## Version 12.08 - * Added custom event support. Each event has a key as well as a count and - an optional sum property. There can be unlimited number of segmentation + * Added custom event support. Each event has a key as well as a count and + an optional sum property. There can be unlimited number of segmentation keys for an event. - * Added help mode. After activated from the sidebar under Management > - Help, certain items in the interface show a small descriptive text when + * Added help mode. After activated from the sidebar under Management > + Help, certain items in the interface show a small descriptive text when hovered on. * Added option to re-order applications listed in the sidebar. - * Added option to select a single day from the date picker. When a single + * Added option to select a single day from the date picker. When a single day is selected hourly data for that day is displayed. - * Optimized dashboard refresh process. While refreshing the dashboard, only - the data for the current day is requested from the read API. Current day - data is merged into the existing data which is fetched the first time + * Optimized dashboard refresh process. While refreshing the dashboard, only + the data for the current day is requested from the read API. Current day + data is merged into the existing data which is fetched the first time user logs in to the dashboard. - * Fixed active application and selected date reset problem after a hard - page reload. Active application and selected date are stored in + * Fixed active application and selected date reset problem after a hard + page reload. Active application and selected date are stored in localStorage until user logs out. ## Version 12.07 * Added platforms view under analytics section. - * Added app versions view under analytics section and API is modified + * Added app versions view under analytics section and API is modified accordingly to handle _app_version metric. - * Added summary bars to device view to show top platform, top platform + * Added summary bars to device view to show top platform, top platform version and top resolution. - * Added reset data option to manage apps screen. Global admin can reset + * Added reset data option to manage apps screen. Global admin can reset the data stored for any application. - * Added timestamp (UTC UNIX timestamp) parameter to the write API. If - provided, the event is recorded with the given time instead of current + * Added timestamp (UTC UNIX timestamp) parameter to the write API. If + provided, the event is recorded with the given time instead of current time. - * Fixed application delete bug that prevented app_users collection to be + * Fixed application delete bug that prevented app_users collection to be cleared. app_id field is added to app_users collection. - * Fixed JSON escape issue for the read API when device name, carrier name + * Fixed JSON escape issue for the read API when device name, carrier name etc. contained a single quote. ## Version 12.06 - * Added user management support. A user can be created as a global admin to + * Added user management support. A user can be created as a global admin to manage & view all apps or can be assigned to any application as an - admin or user. An admin of an application can edit application settings. - A user of an application can only view analytics for that application + admin or user. An admin of an application can edit application settings. + A user of an application can only view analytics for that application and cannot edit its settings. * Added csfr protection to all methods provided through app.js. diff --git a/LICENSE b/LICENSE.md similarity index 92% rename from LICENSE rename to LICENSE.md index 1901e4fc150..0bf57dc9a67 100644 --- a/LICENSE +++ b/LICENSE.md @@ -3,7 +3,7 @@ Countly Product Analytics - Countly Lite License © Countly, https://count.ly -Countly is provided under AGPL v3 with modified Section 7. In accordance +Countly is provided under AGPL-3.0 with modified Section 7. In accordance with Section 7 of the AGPL, the Works included in this package or repository (excluding 3rd party Software), are subject to the following additional terms: diff --git a/README.md b/README.md index 3f4ad6978b4..0ea8dc3416a 100644 --- a/README.md +++ b/README.md @@ -116,3 +116,11 @@ If you like Countly, why not use one of our badges and give a link back to us? Countly - Product Analytics Countly - Product Analytics + + +## License +This project is licensed under **AGPL-3.0** with modified Section 7., see the [LICENSE](LICENSE) file for more details. + +## 💚 Thanks + +This project is tested with BrowserStack. diff --git a/api/api.js b/api/api.js index 843deb94f1b..efb4f061c65 100644 --- a/api/api.js +++ b/api/api.js @@ -16,6 +16,7 @@ const {WriteBatcher, ReadBatcher, InsertBatcher} = require('./parts/data/batcher const pack = require('../package.json'); const versionInfo = require('../frontend/express/version.info.js'); const moment = require("moment"); +const tracker = require('./parts/mgmt/tracker.js'); var t = ["countly:", "api"]; common.processRequest = processRequest; @@ -38,6 +39,9 @@ else { process.title = t.join(' '); plugins.connectToAllDatabases().then(function() { + plugins.loadConfigs(common.db, function() { + tracker.enable(); + }); common.writeBatcher = new WriteBatcher(common.db); common.readBatcher = new ReadBatcher(common.db); common.insertBatcher = new InsertBatcher(common.db); @@ -142,6 +146,41 @@ plugins.connectToAllDatabases().then(function() { require('./utils/log.js').ipcHandler(msg); }); + /** + * Set tracking config + */ + plugins.setConfigs("tracking", { + self_tracking_app: "", + self_tracking_url: "", + self_tracking_app_key: "", + self_tracking_id_policy: "_id", + self_tracking_sessions: true, + self_tracking_events: true, + self_tracking_views: true, + self_tracking_feedback: true, + self_tracking_user_details: true, + server_sessions: true, + server_events: true, + server_crashes: true, + server_views: true, + server_feedback: true, + server_user_details: true, + /*user_sessions: true, + user_events: true, + user_crashes: true, + user_views: true, + user_feedback: true, + user_details: true*/ + }); + + /*plugins.setUserConfigs("tracking", { + user_sessions: false, + user_events: false, + user_crashes: false, + user_views: false, + user_feedback: false + });*/ + /** * Initialize Plugins */ @@ -309,7 +348,7 @@ plugins.connectToAllDatabases().then(function() { // Allow configs to load & scanner to find all jobs classes setTimeout(() => { jobs.job('api:topEvents').replace().schedule('at 00:01 am ' + 'every 1 day'); - jobs.job('api:ping').replace().schedule('every 1 day'); + jobs.job('api:ping').replace().schedule('at 00:01 am ' + 'every 1 day'); jobs.job('api:clear').replace().schedule('every 1 day'); jobs.job('api:clearTokens').replace().schedule('every 1 day'); jobs.job('api:clearAutoTasks').replace().schedule('every 1 day'); diff --git a/api/jobs/ping.js b/api/jobs/ping.js index 984e82c566c..efc4e78ceea 100644 --- a/api/jobs/ping.js +++ b/api/jobs/ping.js @@ -1,12 +1,8 @@ 'use strict'; const job = require('../parts/jobs/job.js'), - log = require('../utils/log.js')('job:ping'), - countlyConfig = require("../../frontend/express/config.js"), - versionInfo = require('../../frontend/express/version.info'), plugins = require('../../plugins/pluginManager.js'), - request = require('countly-request')(plugins.getConfig("security")); - + tracker = require('../parts/mgmt/tracker.js'); /** Class for the job of pinging servers **/ class PingJob extends job.Job { @@ -16,86 +12,114 @@ class PingJob extends job.Job { * @param {done} done callback */ run(db, done) { - request({strictSSL: false, uri: (process.env.COUNTLY_CONFIG_PROTOCOL || "http") + "://" + (process.env.COUNTLY_CONFIG_HOSTNAME || "localhost") + (countlyConfig.path || "") + "/configs"}, function() {}); - var countlyConfigOrig = JSON.parse(JSON.stringify(countlyConfig)); - var url = "https://count.ly/configurations/ce/tracking"; - if (versionInfo.type !== "777a2bf527a18e0fffe22fb5b3e322e68d9c07a6") { - url = "https://count.ly/configurations/ee/tracking"; - } - plugins.loadConfigs(db, function() { + plugins.loadConfigs(db, async function() { const offlineMode = plugins.getConfig("api").offline_mode; - const { countly_tracking } = plugins.getConfig('frontend'); if (!offlineMode) { - request(url, function(err, response, body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } - catch (ex) { - body = null; - } + var server = tracker.getBulkServer(); + var user = tracker.getBulkUser(server); + if (!user) { + return done(); + } + + try { + var custom = await tracker.getAllData(); + if (Object.keys(custom).length) { + user.user_details({"custom": custom }); } - if (body) { - if (countlyConfigOrig.web.use_intercom && typeof body.intercom !== "undefined") { - countlyConfig.web.use_intercom = body.intercom; - } - if (typeof countlyConfigOrig.web.track === "undefined" && typeof body.stats !== "undefined") { - if (body.stats) { - countlyConfig.web.track = null; - } - else { - countlyConfig.web.track = "none"; - } - } + } + catch (ex) { + console.log("Error collecting server data:", ex); + } + var days = 90; + var current_sync = Date.now(); + + // Atomically retrieve old last_sync value and set new one + var syncResult = await db.collection("plugins").findOneAndUpdate( + {_id: "version"}, + {$set: {last_sync: current_sync}}, + { + upsert: true, + returnDocument: 'before', + projection: {last_sync: 1} } - log.d(err, body, countlyConfigOrig, countlyConfig); - if (countly_tracking) { - db.collection("members").findOne({global_admin: true}, function(err2, member) { - if (!err2 && member) { - var date = new Date(); - let domain = plugins.getConfig('api').domain; + ); - try { - // try to extract hostname from full domain url - const urlObj = new URL(domain); - domain = urlObj.hostname; - } - catch (_) { - // do nothing, domain from config will be used as is - } + var last_sync = syncResult.value ? syncResult.value.last_sync : null; + if (last_sync) { + days = Math.floor((new Date().getTime() - last_sync) / (1000 * 60 * 60 * 24)); + } - request({ - uri: "https://stats.count.ly/i", - method: "GET", - timeout: 4E3, - qs: { - device_id: domain, - app_key: "e70ec21cbe19e799472dfaee0adb9223516d238f", - timestamp: Math.floor(date.getTime() / 1000), - hour: date.getHours(), - dow: date.getDay(), - no_meta: true, - events: JSON.stringify([ - { - key: "PING", - count: 1 - } - ]) + if (days > 0) { + //calculate seconds timestamp of days before today + var startTs = Math.round((new Date().getTime() - (30 * 24 * 60 * 60 * 1000)) / 1000); + + //sync server events - use aggregation pipeline to group by day and action on MongoDB side + var aggregationPipeline = [ + // Match documents with timestamp greater than startTs and valid action + { + $match: { + ts: { $gt: startTs } + } + }, + // Add calculated fields for day grouping + { + $addFields: { + // Convert timestamp to date and set to noon (12:00:00) + dayDate: { + $dateFromParts: { + year: { $year: { $toDate: { $multiply: ["$ts", 1000] } } }, + month: { $month: { $toDate: { $multiply: ["$ts", 1000] } } }, + day: { $dayOfMonth: { $toDate: { $multiply: ["$ts", 1000] } } }, + hour: 12, + minute: 0, + second: 0 } - }, function(a/*, c, b*/) { - log.d('Done running ping job: %j', a); - done(); - }); + } + } + }, + // Convert back to timestamp in seconds + { + $addFields: { + noonTimestamp: { + $divide: [{ $toLong: "$dayDate" }, 1000] + } } - else { - done(); + }, + // Group by day and action + { + $group: { + _id: { + day: "$noonTimestamp", + action: "$a" + }, + count: { $sum: 1 } } - }); + }, + // Project to final format + { + $project: { + _id: 0, + action: "$_id.action", + timestamp: "$_id.day", + count: 1 + } + } + ]; + + var cursor = db.collection("systemlogs").aggregate(aggregationPipeline); + + while (cursor && await cursor.hasNext()) { + let eventData = await cursor.next(); + user.add_event({key: eventData.action, count: eventData.count, timestamp: eventData.timestamp}); } - else { + server.start(function() { + server.stop(); done(); - } - }); + }); + } + else { + done(); + } } else { done(); @@ -104,4 +128,4 @@ class PingJob extends job.Job { } } -module.exports = PingJob; +module.exports = PingJob; \ No newline at end of file diff --git a/api/parts/data/usage.js b/api/parts/data/usage.js index 0b03e0599de..93fc8b978eb 100644 --- a/api/parts/data/usage.js +++ b/api/parts/data/usage.js @@ -1030,7 +1030,7 @@ plugins.register("/sdk/user_properties", async function(ob) { if (plugins.getConfig('api', params.app && params.app.plugins, true).city_data === true && !userProps.loc && typeof data.lat !== "undefined" && typeof data.lon !== "undefined") { // only override lat/lon if no recent gps location exists in user document - if (!params.app_user.loc || (params.app_user.loc.gps && params.time.mstimestamp - params.app_user.loc.date > 7 * 24 * 3600)) { + if (!params.app_user.loc || !params.app_user.loc.gps || params.time.mstimestamp - params.app_user.loc.date > 7 * 24 * 3600) { userProps.loc = { gps: false, geo: { @@ -1061,7 +1061,7 @@ plugins.register("/sdk/user_properties", async function(ob) { if (plugins.getConfig('api', params.app && params.app.plugins, true).city_data === true && !userProps.loc && data.ll && typeof data.ll[0] !== "undefined" && typeof data.ll[1] !== "undefined") { // only override lat/lon if no recent gps location exists in user document - if (!params.app_user.loc || (params.app_user.loc.gps && params.time.mstimestamp - params.app_user.loc.date > 7 * 24 * 3600)) { + if (!params.app_user.loc || !params.app_user.loc.gps || params.time.mstimestamp - params.app_user.loc.date > 7 * 24 * 3600) { userProps.loc = { gps: false, geo: { @@ -1125,7 +1125,7 @@ plugins.register("/sdk/user_properties", async function(ob) { userProps.av_major = null; userProps.av_minor = null; userProps.av_patch = null; - userProps.av_rel = null; + userProps.av_prerel = null; userProps.av_build = null; } } diff --git a/api/parts/jobs/job.js b/api/parts/jobs/job.js index 1bf39c2e6a1..46577397cc8 100644 --- a/api/parts/jobs/job.js +++ b/api/parts/jobs/job.js @@ -254,32 +254,33 @@ class Job extends EventEmitter { this._json.next = next.getTime(); } - if (this.name !== "alerts:monitor") { - //check if any job already scheduled or running - let query = { - status: {"$in": [STATUS.SCHEDULED, STATUS.RUNNING]}, - name: this.name, - }; - if (this._id) { - query._id = {$ne: this._id}; - } - var self = this; - return new Promise((resolve, reject) => { - Job.findMany(this.db(), query).then(existing => { - if (existing && existing.length) { - log.d('Job already scheduled or running: %j', existing); - this._json.status = STATUS.CANCELLED; //set this as cancelled now as we have other scheduled - } - else { - self._save().then(resolve, reject); - } - }); - }); + //check if any job already scheduled or running + let query = { + status: {"$in": [STATUS.SCHEDULED, STATUS.RUNNING]}, + name: this.name, + }; + + if (this.name === 'alerts:monitor' && this.data && Object.keys(this.data).length) { + query.data = this.data; } - else { - return this._save(); + + if (this._id) { + query._id = {$ne: this._id}; } + + var self = this; + return new Promise((resolve, reject) => { + Job.findMany(this.db(), query).then(existing => { + if (existing && existing.length) { + log.d('Job already scheduled or running: %j', existing); + this._json.status = STATUS.CANCELLED; //set this as cancelled now as we have other scheduled + } + else { + self._save().then(resolve, reject); + } + }); + }); } /** @@ -1142,4 +1143,4 @@ module.exports = { STATUS: STATUS, STATUS_MAP: STATUS_MAP, debounce: debounce -}; \ No newline at end of file +}; diff --git a/api/parts/mgmt/mail.js b/api/parts/mgmt/mail.js index c5330bcfc0b..19e01d517fd 100644 --- a/api/parts/mgmt/mail.js +++ b/api/parts/mgmt/mail.js @@ -11,8 +11,38 @@ var mail = {}, versionInfo = require('../../../frontend/express/version.info'), authorize = require('../../utils/authorizer'), config = require('../../config'), + log = require('../../utils/log.js')('mail'), + util = require('node:util'), ip = require('./ip.js'); +const smtpLogger = {}; + +// Set up logger wrapper +for (let level of ['trace', 'debug', 'info', 'warn', 'error', 'fatal']) { + smtpLogger[level] = (data, message, ...args) => { + if (args && args.length) { + message = util.format(message, ...args); + } + if (level === 'error' || level === 'fatal') { + log.e(message, data || ''); + } + else if (level === 'warn') { + log.w(message, data || ''); + } + else if (level === 'info') { + log.i(message, data || ''); + } + else { + log.d(message, data || ''); + } + }; +} + +if (config.mail && config.mail.config) { + config.mail.config.logger = smtpLogger; + config.mail.config.debug = true; +} + if (config.mail && config.mail.transport && config.mail.transport !== "nodemailer-smtp-transport") { mail.smtpTransport = nodemailer.createTransport(require(config.mail.transport)(config.mail.config)); } @@ -23,7 +53,8 @@ else { mail.smtpTransport = nodemailer.createTransport({ sendmail: true, newline: 'unix', - path: '/usr/sbin/sendmail' + path: '/usr/sbin/sendmail', + logger: smtpLogger }); } diff --git a/api/parts/mgmt/tracker.js b/api/parts/mgmt/tracker.js index d878ab7188f..d75dce31e1a 100644 --- a/api/parts/mgmt/tracker.js +++ b/api/parts/mgmt/tracker.js @@ -8,176 +8,158 @@ var tracker = {}, stats = require('../data/stats.js'), common = require('../../utils/common.js'), logger = require('../../utils/log.js'), + countlyFs = require('../../utils/countlyFs.js'), //log = logger("tracker:server"), Countly = require('countly-sdk-nodejs'), - countlyConfig = require("../../../frontend/express/config.js"), - versionInfo = require('../../../frontend/express/version.info'), - ip = require('./ip.js'), - cluster = require('cluster'), - os = require('os'), fs = require('fs'), - asyncjs = require('async'), - trial = "79199134e635edb05fc137e8cd202bb8640fb0eb", - app = "e70ec21cbe19e799472dfaee0adb9223516d238f", - server = "e0693b48a5513cb60c112c21aede3cab809d52d0", + path = require('path'), + https = require('https'), + http = require('http'), + FormData = require('form-data'), + { Readable } = require('node:stream'), + versionInfo = require('../../../frontend/express/version.info'), + server = "9c28c347849f2c03caf1b091ec7be8def435e85e", + user = "fa6e9ae7b410cb6d756e8088c5f3936bf1fab5f3", url = "https://stats.count.ly", - plugins = require('../../../plugins/pluginManager.js'), - request = require('countly-request')(plugins.getConfig("security")), - offlineMode = plugins.getConfig("api").offline_mode, - domain = plugins.getConfig("api").domain; - + plugins = require('../../../plugins/pluginManager.js'); -//update configs -var cache = {}; -if (countlyConfig.web && countlyConfig.web.track === "all") { - countlyConfig.web.track = null; -} -var countlyConfigOrig = JSON.parse(JSON.stringify(countlyConfig)); -var url_check = "https://count.ly/configurations/ce/tracking"; -if (versionInfo.type !== "777a2bf527a18e0fffe22fb5b3e322e68d9c07a6") { - url_check = "https://count.ly/configurations/ee/tracking"; -} +var IS_FLEX = false; -if (!offlineMode) { - request(url_check, function(err, response, body) { - if (typeof body === "string") { - try { - body = JSON.parse(body); - } - catch (ex) { - body = null; - } +if (fs.existsSync(path.resolve('/opt/deployment_env.json'))) { + var deploymentConf = fs.readFileSync('/opt/deployment_env.json', 'utf8'); + try { + if (JSON.parse(deploymentConf).DEPLOYMENT_ID) { + IS_FLEX = true; } - if (body) { - if (countlyConfigOrig.web.use_intercom && typeof body.intercom !== "undefined") { - countlyConfig.web.use_intercom = body.intercom; - } - if (typeof countlyConfigOrig.web.track === "undefined" && typeof body.stats !== "undefined") { - if (body.stats) { - countlyConfig.web.track = null; - } - else { - countlyConfig.web.track = "none"; - } - } - if (typeof countlyConfigOrig.web.server_track === "undefined" && typeof body.server !== "undefined") { - if (body.server) { - countlyConfig.web.server_track = null; - } - else { - countlyConfig.web.server_track = "none"; - } - } - } - }); + } + catch (e) { + IS_FLEX = false; + } } +//update configs var isEnabled = false; /** * Enable tracking for this server **/ tracker.enable = function() { + if (isEnabled) { + return; + } + var config = { - app_key: (versionInfo.trial) ? trial : server, + app_key: server, url: url, app_version: versionInfo.version, storage_path: "../../../.sdk/", interval: 10000, fail_timeout: 600, - session_update: 120, + session_update: 60 * 60 * 12, remote_config: true, debug: (logger.getLevel("tracker:server") === "debug") }; + + var domain = plugins.getConfig("api").domain; + //set static device id if domain is defined if (domain) { config.device_id = stripTrailingSlash((domain + "").split("://").pop()); } - Countly.init(config); - //change device id if is it not domain - if (domain && Countly.get_device_id() !== domain) { - Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); - } - else if (!domain) { - checkDomain(); - } + if (config.device_id && config.device_id !== "localhost") { + Countly.init(config); - isEnabled = true; - if (countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { - Countly.track_errors(); - } - if (cluster.isMaster) { - setTimeout(function() { - if (countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { - Countly.begin_session(true); - setTimeout(function() { - collectServerStats(); - collectServerData(); - }, 20000); - } - }, 1000); - //report app start trace - if (Countly.report_app_start) { - Countly.report_app_start(); + //change device id if is it not domain + if (Countly.get_device_id() !== domain) { + Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); + } + + isEnabled = true; + Countly.user_details({"name": config.device_id }); + if (plugins.getConfig("white-labeling") && (plugins.getConfig("white-labeling").favicon || plugins.getConfig("white-labeling").stopleftlogo || plugins.getConfig("white-labeling").prelogo)) { + var id = plugins.getConfig("white-labeling").favicon || plugins.getConfig("white-labeling").stopleftlogo || plugins.getConfig("white-labeling").prelogo; + countlyFs.gridfs.getDataById("white-labeling", id, function(errWhitelabel, data) { + if (!errWhitelabel && data) { + tracker.uploadBase64FileFromGridFS(data).catch(() => {}); + } + }); } + else { + Countly.user_details({"picture": "./images/favicon.png" }); + } + if (plugins.getConfig("tracking").server_sessions) { + Countly.begin_session(true); + } + if (plugins.getConfig("tracking").server_crashes) { + Countly.track_errors(); + } + setTimeout(function() { + tracker.getAllData().then((custom) => { + if (Object.keys(custom).length) { + Countly.user_details({"custom": custom }); + } + }); + }, 20000); } }; /** -* Enable tracking for dashboard process -**/ -tracker.enableDashboard = function() { - var config = { - app_key: (versionInfo.trial) ? trial : server, - url: url, - app_version: versionInfo.version, - storage_path: "../../../.sdk/", - interval: 60000, - fail_timeout: 600, - session_update: 120, - debug: (logger.getLevel("tracker:server") === "debug") - }; - //set static device id if domain is defined - if (domain) { - config.device_id = stripTrailingSlash((domain + "").split("://").pop()); - } - Countly.init(config); + * Get bulk server instance + * @returns {Object} Countly Bulk instance + */ +tracker.getBulkServer = function() { + return new Countly.Bulk({ + app_key: server, + url: url + }); +}; - //change device id if is it not domain - if (domain && Countly.get_device_id() !== domain) { - Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); - } - else if (!domain) { - checkDomain(); - } - isEnabled = true; - if (countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { - Countly.track_errors(); +/** + * Get bulk user instance + * @param {Object} serverInstance - Countly Bulk server instance + * @returns {Object} Countly Bulk User instance + */ +tracker.getBulkUser = function(serverInstance) { + var domain = stripTrailingSlash((plugins.getConfig("api").domain + "").split("://").pop()); + if (domain && domain !== "localhost") { + return serverInstance.add_user({device_id: domain}); } }; - /** * Report server level event * @param {object} event - event object **/ tracker.reportEvent = function(event) { - if (isEnabled && countlyConfig.web.track !== "none" && countlyConfig.web.server_track !== "none") { + if (isEnabled && plugins.getConfig("tracking").server_events) { Countly.add_event(event); } }; +/** +* Report server level event in bulk +* @param {Array} events - array of event objects +**/ +tracker.reportEventBulk = function(events) { + if (isEnabled && plugins.getConfig("tracking").server_events) { + Countly.request({ + app_key: server, + device_id: Countly.get_device_id(), + events: JSON.stringify(events) + }); + } +}; + /** * Report user level event * @param {string} id - id of the device * @param {object} event - event object -* @param {string} level - tracking level **/ -tracker.reportUserEvent = function(id, event, level) { - if (isEnabled && countlyConfig.web.track !== "none" && (!level || countlyConfig.web.track === level) && countlyConfig.web.server_track !== "none") { +tracker.reportUserEvent = function(id, event) { + if (isEnabled && plugins.getConfig("tracking").user_events) { Countly.request({ - app_key: app, + app_key: user, device_id: id, events: JSON.stringify([event]) }); @@ -186,11 +168,10 @@ tracker.reportUserEvent = function(id, event, level) { /** * Check if tracking enabled -* @param {boolean|string} level - level of tracking * @returns {boolean} if enabled **/ -tracker.isEnabled = function(level) { - return (isEnabled && countlyConfig.web.track !== "none" && (!level || countlyConfig.web.track === level) && countlyConfig.web.server_track !== "none"); +tracker.isEnabled = function() { + return isEnabled; }; /** @@ -203,164 +184,403 @@ tracker.getSDK = function() { /** * Get server stats +* @returns {Promise} server stats **/ -function collectServerStats() { // eslint-disable-line no-unused-vars - stats.getServer(common.db, function(data) { - common.db.collection("apps").aggregate([{$project: {last_data: 1}}, {$sort: {"last_data": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errApps, resApps) { - common.db.collection("members").aggregate([{$project: {last_login: 1}}, {$sort: {"last_login": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errLogin, resLogin) { - if (resApps && resApps[0]) { - Countly.userData.set("last_data", resApps[0].last_data || 0); - } - if (resLogin && resLogin[0]) { - Countly.userData.set("last_login", resLogin[0].last_login || 0); - } - if (data) { - if (data.app_users) { - Countly.userData.set("app_users", data.app_users); - } - if (data.apps) { - Countly.userData.set("apps", data.apps); - } - if (data.users) { - Countly.userData.set("users", data.users); - } - } - Countly.userData.save(); +tracker.collectServerStats = function() { + var props = {}; + return new Promise((resolve) => { + stats.getServer(common.db, function(data) { + common.db.collection("apps").aggregate([{$project: {last_data: 1}}, {$sort: {"last_data": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errApps, resApps) { + common.db.collection("members").aggregate([{$project: {last_login: 1}}, {$sort: {"last_login": -1}}, {$limit: 1}], {allowDiskUse: true}, function(errLogin, resLogin) { + // Aggregate total list lengths across all documents in events collection + common.db.collection("events").aggregate([ + { + $group: { + _id: null, + totalListLength: { $sum: { $size: "$list" } } + } + } + ], {allowDiskUse: true}, function(errEvents, resEvents) { + + if (resApps && resApps[0]) { + props.last_data = resApps[0].last_data || 0; + } + if (resLogin && resLogin[0]) { + props.last_login = resLogin[0].last_login || 0; + } + if (resEvents && resEvents[0]) { + props.events = resEvents[0].totalListLength || 0; + } + if (data) { + if (data.app_users) { + props.app_users = data.app_users; + } + if (data.apps) { + props.apps = data.apps; + } + if (data.users) { + props.users = data.users; + } + } + resolve(props); + }); + }); }); }); }); -} +}; /** * Get server data +* @returns {Object} server data **/ -function collectServerData() { - Countly.userData.set("plugins", plugins.getPlugins()); - var cpus = os.cpus(); - if (cpus && cpus.length) { - Countly.userData.set("cores", cpus.length); +tracker.collectServerData = async function() { + var props = {}; + props.trial = versionInfo.trial ? true : false; + props.plugins = plugins.getPlugins(); + props.nodejs = process.version; + props.countly = versionInfo.version; + props.docker = hasDockerEnv() || hasDockerCGroup() || hasDockerMountInfo(); + var edition = "Lite"; + if (IS_FLEX) { + edition = "Flex"; + } + else if (versionInfo.type !== "777a2bf527a18e0fffe22fb5b3e322e68d9c07a6") { + edition = "Enterprise"; } - Countly.userData.set("nodejs_version", process.version); + props.edition = edition; if (common.db.build && common.db.build.version) { - Countly.userData.set("db_version", common.db.build.version); + props.mongodb = common.db.build.version; } - common.db.command({ serverStatus: 1 }, function(errCmd, res) { - if (res && res.storageEngine && res.storageEngine.name) { - Countly.userData.set("db_engine", res.storageEngine.name); + const sdkData = await tracker.getSDKData(); + if (sdkData && sdkData.sdk_versions && Object.keys(sdkData.sdk_versions).length) { + props.sdks = Object.keys(sdkData.sdk_versions); + for (const [key, value] of Object.entries(sdkData.sdk_versions)) { + props[key] = value; } - getDomain(function(err, domainname) { - if (!err) { - Countly.userData.set("domain", domainname); - Countly.user_details({"name": stripTrailingSlash((domainname + "").split("://").pop())}); + } + + return props; +}; + +/** + * Get all eligible data + * @returns {Object} all eligible data + */ +tracker.getAllData = async function() { + var props = {}; + if (plugins.getConfig("tracking").server_user_details) { + Object.assign(props, await tracker.collectServerStats()); + } + Object.assign(props, await tracker.collectServerData()); + return props; +}; + +/** + * Query sdks collection for current and previous year (month 0) and combine meta_v2 data + * @returns {Promise} Combined meta_v2 data from all matching documents + */ +tracker.getSDKData = async function() { + var currentYear = new Date().getFullYear(); + var previousYear = currentYear - 1; + + // Build regex pattern to match: appid_YYYY:0_shard + // Matches any app ID, year (current or previous), month 0, and any shard number + var yearPattern = `(${currentYear}|${previousYear})`; + var pattern = new RegExp(`^[a-f0-9]{24}_${yearPattern}:0_\\d+$`); + + try { + // Use aggregation pipeline to combine meta_v2 data on MongoDB side + var pipeline = [ + // Match documents for current and previous year, month 0, any shard + { + $match: { + _id: pattern + } + }, + // Project only meta_v2 field and convert to array of key-value pairs + { + $project: { + meta_v2: { $objectToArray: "$meta_v2" } + } + }, + // Unwind meta_v2 array to process each meta key separately + { + $unwind: "$meta_v2" + }, + // Convert nested objects to arrays for merging + { + $project: { + metaKey: "$meta_v2.k", + metaValue: { $objectToArray: "$meta_v2.v" } + } + }, + // Unwind nested values + { + $unwind: "$metaValue" + }, + // Group by meta key and inner key to collect all unique combinations + { + $group: { + _id: { + metaKey: "$metaKey", + innerKey: "$metaValue.k" + }, + value: { $first: "$metaValue.v" } + } + }, + // Group by meta key to rebuild nested structure + { + $group: { + _id: "$_id.metaKey", + values: { + $push: { + k: "$_id.innerKey", + v: "$value" + } + } + } + }, + // Convert arrays back to objects + { + $project: { + _id: 0, + k: "$_id", + v: { $arrayToObject: "$values" } + } + }, + // Group all into single document + { + $group: { + _id: null, + meta_v2: { + $push: { + k: "$k", + v: "$v" + } + } + } + }, + // Convert final array to object + { + $project: { + _id: 0, + meta_v2: { $arrayToObject: "$meta_v2" } + } } - getDistro(function(err2, distro) { - if (!err2) { - Countly.userData.set("distro", distro); + ]; + + var result = await common.db.collection("sdks").aggregate(pipeline).toArray(); + + // Extract combined meta_v2 or return empty object if no results + var combinedMeta = (result && result[0] && result[0].meta_v2) ? result[0].meta_v2 : {}; + + // Process sdk_version to extract highest version per SDK + var sdkVersions = {}; + if (combinedMeta.sdk_version) { + for (var versionKey in combinedMeta.sdk_version) { + // Parse SDK version format: [sdk_name]_major:minor:patch + var match = versionKey.match(/^\[([^\]]+)\]_(\d+):(\d+):(\d+)$/); + if (match) { + var sdkName = match[1]; + var major = parseInt(match[2], 10); + var minor = parseInt(match[3], 10); + var patch = parseInt(match[4], 10); + + // Check if this SDK exists and compare versions + if (!sdkVersions[sdkName]) { + sdkVersions[sdkName] = { + version: `${major}.${minor}.${patch}`, + major: major, + minor: minor, + patch: patch + }; + } + else { + var current = sdkVersions[sdkName]; + // Compare versions (major.minor.patch) + if (major > current.major || + (major === current.major && minor > current.minor) || + (major === current.major && minor === current.minor && patch > current.patch)) { + sdkVersions[sdkName] = { + version: `${major}.${minor}.${patch}`, + major: major, + minor: minor, + patch: patch + }; + } + } } - getHosting(function(err3, hosting) { - if (!err3) { - Countly.userData.set("hosting", hosting); + } + } + + // Convert to simple object with just SDK name -> version string + var simpleSdkVersions = {}; + for (var sdk in sdkVersions) { + simpleSdkVersions[`sdk_${sdk}`] = sdkVersions[sdk].version; + } + + return { + meta_v2: combinedMeta, + sdk_versions: simpleSdkVersions, + years: [previousYear, currentYear], + month: 0 + }; + } + catch (error) { + logger("tracker:server").error("Error querying SDK data:", error); + return { + meta_v2: {}, + error: error.message + }; + } +}; + +/** + * Upload a base64-encoded file from GridFS to the stats server + * This function handles files stored in GridFS as base64 strings (e.g., data URIs) + * and decodes them before uploading + * + * @param {Object} base64String - Picture data + * @returns {Promise} Upload result + */ +tracker.uploadBase64FileFromGridFS = function(base64String) { + return new Promise((resolve, reject) => { + var domain = stripTrailingSlash((plugins.getConfig("api").domain + "").split("://").pop()); + if (domain && domain !== "localhost") { + try { + let mimeType = "image/png"; + // Strip data URI prefix if present and stripDataURI is true + if (base64String.includes('base64,')) { + // Extract MIME type from data URI if not provided + const dataURIMatch = base64String.match(/^data:([^;]+);base64,/); + if (dataURIMatch) { + mimeType = dataURIMatch[1]; } - Countly.userData.save(); + // Remove data URI prefix + base64String = base64String.split('base64,')[1]; + } + + // Decode base64 to binary buffer + const binaryBuffer = Buffer.from(base64String, 'base64'); + + // Create a readable stream from the decoded buffer + const decodedStream = Readable.from(binaryBuffer); + + // Parse the URL + const statsUrl = new URL(url); + const protocol = statsUrl.protocol === 'https:' ? https : http; + + // Build query parameters + const queryParams = new URLSearchParams({ + device_id: domain, + app_key: server, + user_details: "" }); - }); - }); + + // Create form data + const form = new FormData(); + + // Prepare form options with MIME type if available + const formOptions = { filename: "profile" }; + if (mimeType) { + formOptions.contentType = mimeType; + } + + form.append('file', decodedStream, formOptions); + + // Prepare request options + const requestOptions = { + hostname: statsUrl.hostname, + port: statsUrl.port || (statsUrl.protocol === 'https:' ? 443 : 80), + path: `/i?${queryParams.toString()}`, + method: 'POST', + headers: form.getHeaders() + }; + + // Make the request + const req = protocol.request(requestOptions, (res) => { + let data = ''; + + res.on('data', (chunk) => { + data += chunk; + }); + + res.on('end', () => { + if (res.statusCode >= 200 && res.statusCode < 300) { + try { + const result = JSON.parse(data); + resolve({ + success: true, + statusCode: res.statusCode, + data: result + }); + } + catch (e) { + resolve({ + success: true, + statusCode: res.statusCode, + data: data + }); + } + } + else { + reject(new Error(`Upload failed with status ${res.statusCode}: ${data}`)); + } + }); + }); + + req.on('error', (error) => { + reject(error); + }); + + // Pipe the form data to the request + form.pipe(req); + } + catch (error) { + reject(error); + } + } }); -} +}; /** -* Get server domain or ip -* @param {function} callback - callback to get results -**/ -function getDomain(callback) { - if (cache.domain) { - callback(false, cache.domain); + * Check if running in Docker environment + * @returns {boolean} if running in docker + */ +function hasDockerEnv() { + try { + fs.statSync('/.dockerenv'); + return true; } - else { - ip.getHost(function(err, host) { - cache.domain = host; - callback(err, host); - }); + catch { + return false; } } -/** -* Get server hosting provider -* @param {function} callback - callback to get results -**/ -function getHosting(callback) { - if (cache.hosting) { - callback(cache.hosting.length === 0, cache.hosting); +/** + * Check if running in Docker by inspecting cgroup info + * @returns {boolean} if running in docker + */ +function hasDockerCGroup() { + try { + return fs.readFileSync('/proc/self/cgroup', 'utf8').includes('docker'); } - else { - var hostings = { - "Digital Ocean": "http://169.254.169.254/metadata/v1/hostname", - "Google Cloud": "http://metadata.google.internal", - "AWS": "http://169.254.169.254/latest/dynamic/instance-identity/" - }; - asyncjs.eachSeries(Object.keys(hostings), function(host, done) { - request(hostings[host], function(err, response) { - if (response && response.statusCode >= 200 && response.statusCode < 300) { - cache.hosting = host; - callback(false, cache.hosting); - done(true); - } - else { - done(); - } - }); - }, function() { - if (!cache.hosting) { - callback(true, cache.hosting); - } - }); + catch { + return false; } } -/** -* Get OS distro -* @param {function} callback - callback to get results -**/ -function getDistro(callback) { - if (cache.distro) { - callback(cache.distro.length === 0, cache.distro); +/** + * Check if running in Docker by inspecting mountinfo + * @returns {boolean} if running in docker + */ +function hasDockerMountInfo() { + try { + return fs.readFileSync('/proc/self/mountinfo', 'utf8').includes('/docker/containers/'); } - else { - var oses = {"win32": "Windows", "darwin": "macOS"}; - var osName = os.platform(); - // Linux is a special case. - if (osName !== 'linux') { - cache.distro = oses[osName] ? oses[osName] : osName; - cache.distro += " " + os.release(); - callback(false, cache.distro); - } - else { - var distros = { - "/etc/lsb-release": {name: "Ubuntu", regex: /distrib_release=(.*)/i}, - "/etc/redhat-release": {name: "RHEL/Centos", regex: /release ([^ ]+)/i} - }; - asyncjs.eachSeries(Object.keys(distros), function(distro, done) { - //check ubuntu - fs.readFile(distro, 'utf8', (err, data) => { - if (!err && data) { - cache.distro = distros[distro].name; - var match = data.match(distros[distro].regex); - if (match[1]) { - cache.distro += " " + match[1]; - } - callback(false, cache.distro); - done(true); - } - else { - done(null); - } - }); - }, function() { - if (!cache.distro) { - callback(true, cache.distro); - } - }); - } + catch { + return false; } } @@ -376,20 +596,4 @@ function stripTrailingSlash(str) { return str; } -//check every hour if domain was provided -var checkDomain = function() { - if (!domain && domain !== plugins.getConfig("api").domain) { - domain = plugins.getConfig("api").domain; - if (Countly && isEnabled) { - Countly.change_id(stripTrailingSlash((domain + "").split("://").pop()), true); - Countly.userData.set("domain", domain); - Countly.user_details({"name": stripTrailingSlash((domain + "").split("://").pop())}); - Countly.userData.save(); - } - } - else if (!domain) { - setTimeout(checkDomain, 3600000); - } -}; - module.exports = tracker; \ No newline at end of file diff --git a/api/utils/countly-request/package-lock.json b/api/utils/countly-request/package-lock.json index db195a845d9..77a6410ba14 100644 --- a/api/utils/countly-request/package-lock.json +++ b/api/utils/countly-request/package-lock.json @@ -646,6 +646,16 @@ "node": ">=8" } }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/is-plain-obj": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz", @@ -796,9 +806,9 @@ } }, "node_modules/mocha": { - "version": "11.7.1", - "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.1.tgz", - "integrity": "sha512-5EK+Cty6KheMS/YLPPMJC64g5V61gIR25KsRItHw6x4hEKT6Njp1n9LOlH4gpevuwMVS66SXaBBpg+RWZkza4A==", + "version": "11.7.4", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-11.7.4.tgz", + "integrity": "sha512-1jYAaY8x0kAZ0XszLWu14pzsf4KV740Gld4HXkhNTXwcHx4AUEDkPzgEHg9CM5dVcW+zv036tjpsEbLraPJj4w==", "dev": true, "license": "MIT", "dependencies": { @@ -810,6 +820,7 @@ "find-up": "^5.0.0", "glob": "^10.4.5", "he": "^1.2.0", + "is-path-inside": "^3.0.3", "js-yaml": "^4.1.0", "log-symbols": "^4.1.0", "minimatch": "^9.0.5", diff --git a/api/utils/pdf.js b/api/utils/pdf.js index 4e2f1159352..6ae800c3a4b 100644 --- a/api/utils/pdf.js +++ b/api/utils/pdf.js @@ -42,7 +42,7 @@ exports.renderPDF = async function(html, callback, options = null, puppeteerArgs } const updatedTimeout = 240000; const page = await browser.newPage(); - + await page.setBypassCSP(true); page.on('console', (msg) => { log.d("Headless chrome page log", msg.text()); }); @@ -74,6 +74,16 @@ exports.renderPDF = async function(html, callback, options = null, puppeteerArgs await page.setContent(html); } + const contentHeight = await page.evaluate(() => { + /*global document*/ + return document.body.scrollHeight; + }); + + options.width = '210mm'; // A4 width, for example + options.height = `${contentHeight}px`; // full content height + options.printBackground = true; + options.preferCSSPageSize = true; + await page.pdf(options).then(callback, function(error) { log.d('pdf generation error', error); }); diff --git a/api/utils/render.js b/api/utils/render.js index e117df24556..f062802a4c3 100644 --- a/api/utils/render.js +++ b/api/utils/render.js @@ -68,7 +68,7 @@ exports.renderView = function(options, cb) { XDG_CONFIG_HOME: pathModule.resolve(__dirname, "../../.cache/chrome/tmp/.chromium"), XDG_CACHE_HOME: pathModule.resolve(__dirname, "../../.cache/chrome/tmp/.chromium") }, - args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors'], + args: ['--no-sandbox', '--disable-setuid-sandbox', '--ignore-certificate-errors', '--disable-web-security'], ignoreHTTPSErrors: true, userDataDir: pathModule.resolve(__dirname, "../../dump/chrome/" + Date.now()) }; @@ -82,6 +82,7 @@ exports.renderView = function(options, cb) { try { log.d('Started rendering images'); var page = await browser.newPage(); + await page.setBypassCSP(true); page.on('console', (msg) => { log.d("Headless chrome page log", msg.text()); diff --git a/api/utils/requestProcessor.js b/api/utils/requestProcessor.js index 707eca00467..d891af82d74 100644 --- a/api/utils/requestProcessor.js +++ b/api/utils/requestProcessor.js @@ -29,6 +29,7 @@ const validateUserForGlobalAdmin = validateGlobalAdmin; const validateUserForMgmtReadAPI = validateUser; const request = require('countly-request')(plugins.getConfig("security")); const Handle = require('../../api/parts/jobs/index.js'); +const render = require('../../api/utils/render.js'); var loaded_configs_time = 0; @@ -362,6 +363,45 @@ const processRequest = (params) => { } break; } + case '/o/render': { + validateUserForRead(params, function() { + var options = {}; + var view = params.qstring.view || ""; + var route = params.qstring.route || ""; + var id = params.qstring.id || ""; + + options.view = view + "#" + route; + options.id = id ? "#" + id : ""; + + var imageName = "screenshot_" + common.crypto.randomBytes(16).toString("hex") + ".png"; + + options.savePath = path.resolve(__dirname, "../../frontend/express/public/images/screenshots/" + imageName); + options.source = "core"; + + authorize.save({ + db: common.db, + multi: false, + owner: params.member._id, + ttl: 300, + purpose: "LoginAuthToken", + callback: function(err2, token) { + if (err2) { + common.returnMessage(params, 400, 'Error creating token: ' + err2); + return false; + } + options.token = token; + render.renderView(options, function(err3) { + if (err3) { + common.returnMessage(params, 400, 'Error creating screenshot: ' + err3); + return false; + } + common.returnOutput(params, {path: common.config.path + "/images/screenshots/" + imageName}); + }); + } + }); + }); + break; + } case '/i/app_users': { switch (paths[3]) { case 'create': { diff --git a/api/utils/taskmanager.js b/api/utils/taskmanager.js index 696932373d1..67b3820a090 100644 --- a/api/utils/taskmanager.js +++ b/api/utils/taskmanager.js @@ -848,6 +848,11 @@ taskmanager.deleteResult = function(options, callback) { if (task.gridfs) { countlyFs.gridfs.deleteFile("task_results", options.id, {id: options.id}, function() {}); } + + // Delete additional results specific to task type + taskmanager.deleteAdditionalResults(task, options, function() { + // Continue with normal task deletion + }); options.db.collection("long_tasks").remove({_id: options.id}, function() { callback(null, task); }); @@ -863,6 +868,45 @@ taskmanager.deleteResult = function(options, callback) { }); }; +/** +* Delete additional results specific to task type +* @param {object} task - the task object +* @param {object} options - options for the task +* @param {function} callback - callback for the result +*/ +taskmanager.deleteAdditionalResults = function(task, options, callback) { + if (task.type === "journey_engine") { + const collectionName = "journey_task_data_" + options.id; + options.db.collection(collectionName).drop(function(dropErr) { + if (dropErr && dropErr.code !== 26) { // 26 = namespace not found, which is fine + log.w("Failed to drop journey task data collection:", collectionName, dropErr); + } + else { + log.d("Successfully dropped journey task data collection:", collectionName); + } + }); + + // Also try to clean up the default collection if it exists and is empty + // This is a safety measure for cases where the default collection might have been used + options.db.collection("journey_task_data").countDocuments({}, function(countErr, count) { + if (!countErr && count === 0) { + options.db.collection("journey_task_data").drop(function(defaultDropErr) { + if (defaultDropErr && defaultDropErr.code !== 26) { + log.w("Failed to drop default journey task data collection:", defaultDropErr); + } + else if (!defaultDropErr) { + log.d("Successfully dropped empty default journey task data collection"); + } + }); + } + callback(); + }); + } + else { + callback(); + } +}; + /** * Mark all running or rerunning tasks as errored * @param {object} options - options for the task diff --git a/bin/config/nginx.server.ssl.conf b/bin/config/nginx.server.ssl.conf index 16089456631..b5645f3ad05 100644 --- a/bin/config/nginx.server.ssl.conf +++ b/bin/config/nginx.server.ssl.conf @@ -11,14 +11,12 @@ server { # HTTPS configuration server { - listen 443; - listen [::]:443 ipv6only=on; + listen 443 ssl; + listen [::]:443 ipv6only=on; server_name localhost; access_log off; - ssl on; - # support only known-secure cryptographic protocols # SSLv3 is broken by POODLE as of October 2014 ssl_protocols TLSv1.2 TLSv1.3; diff --git a/bin/scripts/device_list/package-lock.json b/bin/scripts/device_list/package-lock.json index 0449733bbec..deb16bfedf5 100644 --- a/bin/scripts/device_list/package-lock.json +++ b/bin/scripts/device_list/package-lock.json @@ -9,73 +9,42 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "csvtojson": "^1.1.9" + "csvtojson": "^2.0.13" } }, "node_modules/csvtojson": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-1.1.12.tgz", - "integrity": "sha512-gJg1II2uXh8H6XP7L1YzX/6H8rrUbSTiEg4SaI6/pHOcnZovwAR3Rmm9TizSsGQBBXl1pb/JJPlaImV2YIuMrg==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.14.tgz", + "integrity": "sha512-F7NNvhhDyob7OsuEGRsH0FM1aqLs/WYITyza3l+hTEEmOK9sGPBlYQZwlVG0ezCojXYpE17lhS5qL6BCOZSPyA==", "dependencies": { - "lodash": "^4.17.3", - "strip-bom": "^2.0.0" + "lodash": "^4.17.21" }, "bin": { "csvtojson": "bin/csvtojson" }, "engines": { - "node": ">=0.10" + "node": ">=8.0.0" } }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" - }, "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } } }, "dependencies": { "csvtojson": { - "version": "1.1.12", - "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-1.1.12.tgz", - "integrity": "sha512-gJg1II2uXh8H6XP7L1YzX/6H8rrUbSTiEg4SaI6/pHOcnZovwAR3Rmm9TizSsGQBBXl1pb/JJPlaImV2YIuMrg==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.14.tgz", + "integrity": "sha512-F7NNvhhDyob7OsuEGRsH0FM1aqLs/WYITyza3l+hTEEmOK9sGPBlYQZwlVG0ezCojXYpE17lhS5qL6BCOZSPyA==", "requires": { - "lodash": "^4.17.3", - "strip-bom": "^2.0.0" + "lodash": "^4.17.21" } }, - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==" - }, "lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" - }, - "strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "requires": { - "is-utf8": "^0.2.0" - } } } } diff --git a/bin/scripts/device_list/package.json b/bin/scripts/device_list/package.json index 8767d654354..04c67ef5e95 100644 --- a/bin/scripts/device_list/package.json +++ b/bin/scripts/device_list/package.json @@ -23,6 +23,6 @@ }, "homepage": "https://count.ly/", "dependencies": { - "csvtojson": "^1.1.9" + "csvtojson": "^2.0.13" } } diff --git a/bin/scripts/expire-data/delete_custom_events.js b/bin/scripts/expire-data/delete_custom_events.js index 88161bed835..addb9b4d26c 100644 --- a/bin/scripts/expire-data/delete_custom_events.js +++ b/bin/scripts/expire-data/delete_custom_events.js @@ -22,7 +22,7 @@ Promise.all([pluginManager.dbConnection("countly"), pluginManager.dbConnection(" common.drillDb = drillDb; //GET APP try { - const app = await countlyDb.collection("apps").findOne({_id: ObjectId(APP_ID)}, {_id: 1, name: 1}); + const app = await countlyDb.collection("apps").findOne({_id: new ObjectId(APP_ID)}, {_id: 1, name: 1}); console.log("App:", app.name); //GET EVENTS let events = EVENTS; diff --git a/bin/scripts/export-data/setting_limits_and_real_values.js b/bin/scripts/export-data/setting_limits_and_real_values.js index 7cbb0e0bfa3..482c25578f8 100644 --- a/bin/scripts/export-data/setting_limits_and_real_values.js +++ b/bin/scripts/export-data/setting_limits_and_real_values.js @@ -19,8 +19,8 @@ const DEFAULT_LIMITS = { view_name_limit: 128, view_segment_limit: 100, view_segment_value_limit: 10, - //custom_prop_limit: 20, - custom_property_limit: 20, + custom_prop_limit: 20, + //custom_property_limit: 20, custom_prop_value_limit: 50, }; Promise.all([pluginManager.dbConnection("countly"), pluginManager.dbConnection("countly_drill")]).then(async function([countlyDb, drillDb]) { @@ -56,8 +56,8 @@ Promise.all([pluginManager.dbConnection("countly"), pluginManager.dbConnection(" view_name_limit: pluginsCollectionPlugins?.views?.view_name_limit || DEFAULT_LIMITS.view_name_limit, view_segment_limit: pluginsCollectionPlugins?.views?.segment_limit || DEFAULT_LIMITS.view_segment_limit, view_segment_value_limit: pluginsCollectionPlugins?.views?.segment_value_limit || DEFAULT_LIMITS.view_segment_value_limit, - //custom_prop_limit: pluginsCollectionPlugins?.users?.custom_prop_limit || DEFAULT_LIMITS.custom_prop_limit, - custom_property_limit: pluginsCollectionPlugins?.drill?.custom_property_limit || DEFAULT_LIMITS.custom_property_limit, + custom_prop_limit: pluginsCollectionPlugins?.users?.custom_prop_limit || DEFAULT_LIMITS.custom_prop_limit, + //custom_property_limit: pluginsCollectionPlugins?.drill?.custom_property_limit || DEFAULT_LIMITS.custom_property_limit, custom_prop_value_limit: pluginsCollectionPlugins?.users?.custom_set_limit || DEFAULT_LIMITS.custom_prop_value_limit, }; // GETTING REAL DATA PER APP @@ -247,10 +247,10 @@ Promise.all([pluginManager.dbConnection("countly"), pluginManager.dbConnection(" }); app_results['View Segments Unique Values'] = {"default": defaultVal, "set": currentVal, "real": realVal}; // USER PROPERTIES - //defaultVal = DEFAULT_LIMITS.custom_prop_limit; - //currentVal = CURRENT_LIMITS.custom_prop_limit; - defaultVal = DEFAULT_LIMITS.custom_property_limit; - currentVal = CURRENT_LIMITS.custom_property_limit; + defaultVal = DEFAULT_LIMITS.custom_prop_limit; + currentVal = CURRENT_LIMITS.custom_prop_limit; + //defaultVal = DEFAULT_LIMITS.custom_property_limit; + //currentVal = CURRENT_LIMITS.custom_property_limit; realVal = customPropsPerApp && customPropsPerApp[0]?.customPropertiesCount || 0; app_results['Max user custom properties'] = {"default": defaultVal, "set": currentVal, "real": realVal}; // VALUES IN AN ARRAY FOR ONE USER PROPERTY diff --git a/bin/scripts/fix-data/recalculate_ab_test_cohorts.js b/bin/scripts/fix-data/recalculate_ab_test_cohorts.js new file mode 100644 index 00000000000..654752a7244 --- /dev/null +++ b/bin/scripts/fix-data/recalculate_ab_test_cohorts.js @@ -0,0 +1,74 @@ +/* + * Sends recalculate request for all ab testing experiment variant cohorts + * Server: countly + * Path: $(countly dir)/bin/scripts/fix-data + * Command: node recalculate_ab_test_cohorts.js + */ + +// API key here with permission to update cohorts +const API_KEY = ''; +// Countly app id, if not specified will do nothing +const APP_ID = ''; +// ab test experiment id, will do nothing if not specified +const EXPERIMENT_ID = ''; +// countly instance public url, something like 'https://name.count.ly' +const SERVER_URL = ''; + +const pluginManager = require('../../../plugins/pluginManager.js'); +const request = require('countly-request')(pluginManager.getConfig('security')); + +if (API_KEY.length === 0) { + console.warn('Please provide an API_KEY'); + process.exit(1); +} + +pluginManager.dbConnection('countly_out').then(async(db) => { + let urlObj = {}; + try { + urlObj = new URL(SERVER_URL); + } + catch (err) { + urlObj = new URL((process.env.COUNTLY_CONFIG_PROTOCOL || "http") + "://" + (process.env.COUNTLY_CONFIG_HOSTNAME || "localhost")); + } + urlObj.pathname = 'i/cohorts/recalculate'; + urlObj.searchParams.append('api_key', API_KEY); + urlObj.searchParams.append('app_id', APP_ID); + + console.log(`Finding ab test experiment ${EXPERIMENT_ID} in app ${APP_ID}`); + + const experimentCollectionName = `ab_testing_experiments${APP_ID}`; + const experiment = await db.collection(experimentCollectionName).findOne({ _id: db.ObjectID(EXPERIMENT_ID) }); + + if (experiment?.variants?.length > 0) { + for (let varIdx = 0; varIdx < experiment.variants.length; varIdx += 1) { + const variant = experiment.variants[varIdx]; + + if (variant?.cohorts && Object.keys(variant.cohorts).length > 0) { + for (let cohIdx = 0; cohIdx < Object.keys(variant.cohorts).length; cohIdx += 1) { + const cohortId = variant.cohorts[Object.keys(variant.cohorts)[cohIdx]]; + console.log(`Sending recalculate request for variant ${variant.name}, cohort ${cohortId}`); + + urlObj.searchParams.delete('cohort_id'); + urlObj.searchParams.append('cohort_id', cohortId); + + await new Promise((resolve) => { + request.get(urlObj.href, (err, _, body) => { + if (err) { + console.warn('Request failed ', JSON.stringify(cohortId), err); + } + else { + console.log('Request finished ', JSON.stringify(cohortId), body); + } + resolve(); + }); + }); + } + } + } + } + else { + console.warn(`Experiments ${EXPERIMENT_ID} not found in app ${APP_ID}`); + } + + db.close(); +}); diff --git a/countly-community b/countly-community new file mode 160000 index 00000000000..f772bcd2c0e --- /dev/null +++ b/countly-community @@ -0,0 +1 @@ +Subproject commit f772bcd2c0e918dcf337ef3d978efbaa986bd882 diff --git a/frontend/express/app.js b/frontend/express/app.js index 482b7d91ce1..6d18d81f2db 100644 --- a/frontend/express/app.js +++ b/frontend/express/app.js @@ -67,13 +67,13 @@ var versionInfo = require('./version.info'), url = require('url'), authorize = require('../../api/utils/authorizer.js'), //for token validations languages = require('../../frontend/express/locale.conf'), - render = require('../../api/utils/render.js'), rateLimit = require("express-rate-limit"), membersUtility = require("./libs/members.js"), argon2 = require('argon2'), countlyCommon = require('../../api/lib/countly.common.js'), timezones = require('../../api/utils/timezones.js').getTimeZones, - { validateCreate } = require('../../api/utils/rights.js'); + { validateCreate } = require('../../api/utils/rights.js'), + tracker = require('../../api/parts/mgmt/tracker.js'); console.log("Starting Countly", "version", versionInfo.version, "package", pack.version); @@ -137,21 +137,9 @@ plugins.setConfigs("frontend", { session_timeout: 30, use_google: true, code: true, - offline_mode: false, - self_tracking: "", + offline_mode: false }); -if (!plugins.isPluginEnabled('tracker')) { - plugins.setConfigs('frontend', { - countly_tracking: null, - }); -} -else { - plugins.setConfigs('frontend', { - countly_tracking: true, - }); -} - plugins.setUserConfigs("frontend", { production: false, theme: false, @@ -196,16 +184,13 @@ if (countlyConfig.web && countlyConfig.web.track === "all") { countlyConfig.web.track = null; } -var countlyConfigOrig = JSON.parse(JSON.stringify(countlyConfig)); - Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_fs")]).then(function(dbs) { var countlyDb = dbs[0]; //reference for consistency between app and api processes membersUtility.db = common.db = countlyDb; countlyFs.setHandler(dbs[1]); + tracker.enable(); - //checking remote configuration - membersUtility.recheckConfigs(countlyConfigOrig, countlyConfig); /** * Create sha1 hash string * @param {string} str - string to hash @@ -418,6 +403,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ }; plugins.loadConfigs(countlyDb, function() { + tracker.enable(); curTheme = plugins.getConfig("frontend").theme; app.loadThemeFiles(curTheme); app.dashboard_headers = plugins.getConfig("security").dashboard_additional_headers; @@ -452,8 +438,10 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ next(); }); - app.use('*.svg', function(req, res, next) { - res.setHeader('Content-Type', 'image/svg+xml; charset=UTF-8'); + app.use(function(req, res, next) { + if (req.path.endsWith('.svg')) { + res.setHeader('Content-Type', 'image/svg+xml; charset=UTF-8'); + } next(); }); @@ -835,11 +823,6 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ res.send(plugins.getConfig("security").robotstxt); }); - app.get(countlyConfig.path + '/configs', function(req, res) { - membersUtility.recheckConfigs(countlyConfigOrig, countlyConfig); - res.send("Success"); - }); - app.get(countlyConfig.path + '/session', function(req, res, next) { if (req.session.auth_token) { authorize.verify_return({ @@ -932,7 +915,6 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ **/ function renderDashboard(req, res, next, member, adminOfApps, userOfApps, countlyGlobalApps, countlyGlobalAdminApps) { var configs = plugins.getConfig("frontend", member.settings), - countly_tracking = plugins.isPluginEnabled('tracker') ? true : plugins.getConfig('frontend').countly_tracking, countly_domain = plugins.getConfig('api').domain, licenseNotification, licenseError; var isLocked = false; @@ -1009,6 +991,7 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ member: member, config: req.config, security: plugins.getConfig("security"), + tracking: plugins.getConfig("tracking"), plugins: plugins.getPlugins(), pluginsFull: plugins.getPlugins(true), path: countlyConfig.path || "", @@ -1021,9 +1004,8 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ countlyTypeName: overriddenCountlyNamedType, countlyTypeTrack: COUNTLY_TRACK_TYPE, countlyTypeCE: COUNTLY_TYPE_CE, - countly_tracking, countly_domain, - frontend_app: versionInfo.frontend_app || 'e70ec21cbe19e799472dfaee0adb9223516d238f', + frontend_app: versionInfo.frontend_app || "9c28c347849f2c03caf1b091ec7be8def435e85e", frontend_server: versionInfo.frontend_server || 'https://stats.count.ly/', usermenu: { feedbackLink: COUNTLY_FEEDBACK_LINK, @@ -1877,48 +1859,6 @@ Promise.all([plugins.dbConnection(countlyConfig), plugins.dbConnection("countly_ } }); - app.get(countlyConfig.path + '/render', function(req, res) { - if (!req.session.uid) { - return res.redirect(countlyConfig.path + '/login'); - } - - var options = {}; - var view = req.query.view || ""; - var route = req.query.route || ""; - var id = req.query.id || ""; - - options.view = view + "#" + route; - options.id = id ? "#" + id : ""; - - var randomString = (+new Date()).toString() + (Math.random()).toString(); - var imageName = "screenshot_" + sha1Hash(randomString) + ".png"; - - options.savePath = path.resolve(__dirname, "./public/images/screenshots/" + imageName); - options.source = "core"; - - authorize.save({ - db: countlyDb, - multi: false, - owner: req.session.uid, - ttl: 300, - purpose: "LoginAuthToken", - callback: function(err2, token) { - if (err2) { - console.log(err2); - return res.send(false); - } - options.token = token; - render.renderView(options, function(err3) { - if (err3) { - return res.send(false); - } - - return res.send({path: countlyConfig.path + "/images/screenshots/" + imageName}); - }); - } - }); - }); - app.get(countlyConfig.path + '/login/token/:token', function(req, res) { membersUtility.loginWithToken(req, function(member) { if (member) { diff --git a/frontend/express/libs/express-expose.js b/frontend/express/libs/express-expose.js index 2a490722f5e..34d662e58e3 100644 --- a/frontend/express/libs/express-expose.js +++ b/frontend/express/libs/express-expose.js @@ -157,7 +157,7 @@ function renderNamespace(str) { function renderObject(obj, namespace) { return Object.keys(obj).map(function(key) { var val = obj[key]; - return namespace + '["' + key + '"] = ' + string(val) + ';'; + return namespace + '["' + escape_js_string(key) + '"] = ' + string(val) + ';'; }).join('\n'); } @@ -180,61 +180,49 @@ function string(obj) { } else if ('[object Object]' === Object.prototype.toString.call(obj)) { return '{' + Object.keys(obj).map(function(key) { - return '"' + key + '":' + string(obj[key]); + return '"' + escape_js_string(key) + '":' + string(obj[key]); }).join(', ') + '}'; } else { - obj = escape_html(JSON.stringify(obj)); + obj = JSON.stringify(obj); if (obj) { + // Only escape things that could break out of script context obj = obj.replace(/<\/script>/ig, ''); + obj = obj.replace(/ - - + - <% } %> + } + diff --git a/package-lock.json b/package-lock.json index f421c67b686..017f9567363 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,17 +23,17 @@ "countly-sdk-nodejs": "*", "countly-sdk-web": "*", "csurf": "^1.11.0", - "csvtojson": "2.0.10", + "csvtojson": "2.0.14", "ejs": "3.1.10", "errorhandler": "1.5.1", "express": "4.21.2", - "express-rate-limit": "8.0.1", + "express-rate-limit": "8.2.1", "express-session": "1.18.2", "form-data": "^4.0.0", "formidable": "2.1.3", - "fs-extra": "11.3.0", + "fs-extra": "11.3.2", "geoip-lite": "1.4.10", - "get-random-values": "^3.0.0", + "get-random-values": "^4.0.0", "grunt": "1.6.1", "grunt-cli": "1.5.0", "grunt-contrib-concat": "2.1.0", @@ -53,14 +53,14 @@ "method-override": "3.0.0", "moment": "2.30.1", "moment-timezone": "0.6.0", - "mongodb": "6.18.0", + "mongodb": "6.20.0", "nginx-conf": "2.1.0", - "nodemailer": "7.0.5", + "nodemailer": "7.0.10", "object-hash": "3.0.0", "offline-geocoder": "git+https://github.com/Countly/offline-geocoder.git", "properties-parser": "0.6.0", "puppeteer": "^24.6.1", - "sass": "1.89.2", + "sass": "1.93.3", "semver": "^7.7.1", "sharp": "^0.34.2", "sqlite3": "^5.1.7", @@ -435,9 +435,9 @@ } }, "node_modules/@emnapi/runtime": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.5.tgz", - "integrity": "sha512-++LApOtY0pEEz1zrd9vy1/zXVaVJJ/EbAF3u0fXIzPJEDtnITsBGbbK0EkM72amhl/R5b+5xx0Y/QhcVOpuulg==", + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.5.0.tgz", + "integrity": "sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==", "license": "MIT", "optional": true, "dependencies": { @@ -644,10 +644,19 @@ "dev": true, "license": "BSD-3-Clause" }, + "node_modules/@img/colour": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/@img/colour/-/colour-1.0.0.tgz", + "integrity": "sha512-A5P/LfWGFSl6nsckYtjw9da+19jB8hkJ6ACTGcDfEJ0aE+l2n2El7dsVM7UVHZQ9s2lmYMWlrS21YLy2IR1LUw==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/@img/sharp-darwin-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.3.tgz", - "integrity": "sha512-ryFMfvxxpQRsgZJqBd4wsttYQbCxsJksrv9Lw/v798JcQ8+w84mBWuXwl+TT0WJ/WrYOLaYpwQXi3sA9nTIaIg==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.34.4.tgz", + "integrity": "sha512-sitdlPzDVyvmINUdJle3TNHl+AG9QcwiAMsXmccqsCOMZNIdW2/7S26w0LyU8euiLVzFBL3dXPwVCq/ODnf2vA==", "cpu": [ "arm64" ], @@ -663,13 +672,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-arm64": "1.2.0" + "@img/sharp-libvips-darwin-arm64": "1.2.3" } }, "node_modules/@img/sharp-darwin-x64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.3.tgz", - "integrity": "sha512-yHpJYynROAj12TA6qil58hmPmAwxKKC7reUqtGLzsOHfP7/rniNGTL8tjWX6L3CTV4+5P4ypcS7Pp+7OB+8ihA==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.34.4.tgz", + "integrity": "sha512-rZheupWIoa3+SOdF/IcUe1ah4ZDpKBGWcsPX6MT0lYniH9micvIU7HQkYTfrx5Xi8u+YqwLtxC/3vl8TQN6rMg==", "cpu": [ "x64" ], @@ -685,13 +694,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-darwin-x64": "1.2.0" + "@img/sharp-libvips-darwin-x64": "1.2.3" } }, "node_modules/@img/sharp-libvips-darwin-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.0.tgz", - "integrity": "sha512-sBZmpwmxqwlqG9ueWFXtockhsxefaV6O84BMOrhtg/YqbTaRdqDE7hxraVE3y6gVM4eExmfzW4a8el9ArLeEiQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.2.3.tgz", + "integrity": "sha512-QzWAKo7kpHxbuHqUC28DZ9pIKpSi2ts2OJnoIGI26+HMgq92ZZ4vk8iJd4XsxN+tYfNJxzH6W62X5eTcsBymHw==", "cpu": [ "arm64" ], @@ -705,9 +714,9 @@ } }, "node_modules/@img/sharp-libvips-darwin-x64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.0.tgz", - "integrity": "sha512-M64XVuL94OgiNHa5/m2YvEQI5q2cl9d/wk0qFTDVXcYzi43lxuiFTftMR1tOnFQovVXNZJ5TURSDK2pNe9Yzqg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.2.3.tgz", + "integrity": "sha512-Ju+g2xn1E2AKO6YBhxjj+ACcsPQRHT0bhpglxcEf+3uyPY+/gL8veniKoo96335ZaPo03bdDXMv0t+BBFAbmRA==", "cpu": [ "x64" ], @@ -721,9 +730,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.0.tgz", - "integrity": "sha512-mWd2uWvDtL/nvIzThLq3fr2nnGfyr/XMXlq8ZJ9WMR6PXijHlC3ksp0IpuhK6bougvQrchUAfzRLnbsen0Cqvw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.2.3.tgz", + "integrity": "sha512-x1uE93lyP6wEwGvgAIV0gP6zmaL/a0tGzJs/BIDDG0zeBhMnuUPm7ptxGhUbcGs4okDJrk4nxgrmxpib9g6HpA==", "cpu": [ "arm" ], @@ -737,9 +746,9 @@ } }, "node_modules/@img/sharp-libvips-linux-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.0.tgz", - "integrity": "sha512-RXwd0CgG+uPRX5YYrkzKyalt2OJYRiJQ8ED/fi1tq9WQW2jsQIn0tqrlR5l5dr/rjqq6AHAxURhj2DVjyQWSOA==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.2.3.tgz", + "integrity": "sha512-I4RxkXU90cpufazhGPyVujYwfIm9Nk1QDEmiIsaPwdnm013F7RIceaCc87kAH+oUB1ezqEvC6ga4m7MSlqsJvQ==", "cpu": [ "arm64" ], @@ -753,9 +762,9 @@ } }, "node_modules/@img/sharp-libvips-linux-ppc64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.0.tgz", - "integrity": "sha512-Xod/7KaDDHkYu2phxxfeEPXfVXFKx70EAFZ0qyUdOjCcxbjqyJOEUpDe6RIyaunGxT34Anf9ue/wuWOqBW2WcQ==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-ppc64/-/sharp-libvips-linux-ppc64-1.2.3.tgz", + "integrity": "sha512-Y2T7IsQvJLMCBM+pmPbM3bKT/yYJvVtLJGfCs4Sp95SjvnFIjynbjzsa7dY1fRJX45FTSfDksbTp6AGWudiyCg==", "cpu": [ "ppc64" ], @@ -769,9 +778,9 @@ } }, "node_modules/@img/sharp-libvips-linux-s390x": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.0.tgz", - "integrity": "sha512-eMKfzDxLGT8mnmPJTNMcjfO33fLiTDsrMlUVcp6b96ETbnJmd4uvZxVJSKPQfS+odwfVaGifhsB07J1LynFehw==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.2.3.tgz", + "integrity": "sha512-RgWrs/gVU7f+K7P+KeHFaBAJlNkD1nIZuVXdQv6S+fNA6syCcoboNjsV2Pou7zNlVdNQoQUpQTk8SWDHUA3y/w==", "cpu": [ "s390x" ], @@ -785,9 +794,9 @@ } }, "node_modules/@img/sharp-libvips-linux-x64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.0.tgz", - "integrity": "sha512-ZW3FPWIc7K1sH9E3nxIGB3y3dZkpJlMnkk7z5tu1nSkBoCgw2nSRTFHI5pB/3CQaJM0pdzMF3paf9ckKMSE9Tg==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.2.3.tgz", + "integrity": "sha512-3JU7LmR85K6bBiRzSUc/Ff9JBVIFVvq6bomKE0e63UXGeRw2HPVEjoJke1Yx+iU4rL7/7kUjES4dZ/81Qjhyxg==", "cpu": [ "x64" ], @@ -801,9 +810,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-arm64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.0.tgz", - "integrity": "sha512-UG+LqQJbf5VJ8NWJ5Z3tdIe/HXjuIdo4JeVNADXBFuG7z9zjoegpzzGIyV5zQKi4zaJjnAd2+g2nna8TZvuW9Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.2.3.tgz", + "integrity": "sha512-F9q83RZ8yaCwENw1GieztSfj5msz7GGykG/BA+MOUefvER69K/ubgFHNeSyUu64amHIYKGDs4sRCMzXVj8sEyw==", "cpu": [ "arm64" ], @@ -817,9 +826,9 @@ } }, "node_modules/@img/sharp-libvips-linuxmusl-x64": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.0.tgz", - "integrity": "sha512-SRYOLR7CXPgNze8akZwjoGBoN1ThNZoqpOgfnOxmWsklTGVfJiGJoC/Lod7aNMGA1jSsKWM1+HRX43OP6p9+6Q==", + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.2.3.tgz", + "integrity": "sha512-U5PUY5jbc45ANM6tSJpsgqmBF/VsL6LnxJmIf11kB7J5DctHgqm0SkuXzVWtIY90GnJxKnC/JT251TDnk1fu/g==", "cpu": [ "x64" ], @@ -833,9 +842,9 @@ } }, "node_modules/@img/sharp-linux-arm": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.3.tgz", - "integrity": "sha512-oBK9l+h6KBN0i3dC8rYntLiVfW8D8wH+NPNT3O/WBHeW0OQWCjfWksLUaPidsrDKpJgXp3G3/hkmhptAW0I3+A==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.34.4.tgz", + "integrity": "sha512-Xyam4mlqM0KkTHYVSuc6wXRmM7LGN0P12li03jAnZ3EJWZqj83+hi8Y9UxZUbxsgsK1qOEwg7O0Bc0LjqQVtxA==", "cpu": [ "arm" ], @@ -851,13 +860,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm": "1.2.0" + "@img/sharp-libvips-linux-arm": "1.2.3" } }, "node_modules/@img/sharp-linux-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.3.tgz", - "integrity": "sha512-QdrKe3EvQrqwkDrtuTIjI0bu6YEJHTgEeqdzI3uWJOH6G1O8Nl1iEeVYRGdj1h5I21CqxSvQp1Yv7xeU3ZewbA==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.34.4.tgz", + "integrity": "sha512-YXU1F/mN/Wu786tl72CyJjP/Ngl8mGHN1hST4BGl+hiW5jhCnV2uRVTNOcaYPs73NeT/H8Upm3y9582JVuZHrQ==", "cpu": [ "arm64" ], @@ -873,13 +882,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-arm64": "1.2.0" + "@img/sharp-libvips-linux-arm64": "1.2.3" } }, "node_modules/@img/sharp-linux-ppc64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.3.tgz", - "integrity": "sha512-GLtbLQMCNC5nxuImPR2+RgrviwKwVql28FWZIW1zWruy6zLgA5/x2ZXk3mxj58X/tszVF69KK0Is83V8YgWhLA==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-ppc64/-/sharp-linux-ppc64-0.34.4.tgz", + "integrity": "sha512-F4PDtF4Cy8L8hXA2p3TO6s4aDt93v+LKmpcYFLAVdkkD3hSxZzee0rh6/+94FpAynsuMpLX5h+LRsSG3rIciUQ==", "cpu": [ "ppc64" ], @@ -895,13 +904,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-ppc64": "1.2.0" + "@img/sharp-libvips-linux-ppc64": "1.2.3" } }, "node_modules/@img/sharp-linux-s390x": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.3.tgz", - "integrity": "sha512-3gahT+A6c4cdc2edhsLHmIOXMb17ltffJlxR0aC2VPZfwKoTGZec6u5GrFgdR7ciJSsHT27BD3TIuGcuRT0KmQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.34.4.tgz", + "integrity": "sha512-qVrZKE9Bsnzy+myf7lFKvng6bQzhNUAYcVORq2P7bDlvmF6u2sCmK2KyEQEBdYk+u3T01pVsPrkj943T1aJAsw==", "cpu": [ "s390x" ], @@ -917,13 +926,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-s390x": "1.2.0" + "@img/sharp-libvips-linux-s390x": "1.2.3" } }, "node_modules/@img/sharp-linux-x64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.3.tgz", - "integrity": "sha512-8kYso8d806ypnSq3/Ly0QEw90V5ZoHh10yH0HnrzOCr6DKAPI6QVHvwleqMkVQ0m+fc7EH8ah0BB0QPuWY6zJQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.34.4.tgz", + "integrity": "sha512-ZfGtcp2xS51iG79c6Vhw9CWqQC8l2Ot8dygxoDoIQPTat/Ov3qAa8qpxSrtAEAJW+UjTXc4yxCjNfxm4h6Xm2A==", "cpu": [ "x64" ], @@ -939,13 +948,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linux-x64": "1.2.0" + "@img/sharp-libvips-linux-x64": "1.2.3" } }, "node_modules/@img/sharp-linuxmusl-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.3.tgz", - "integrity": "sha512-vAjbHDlr4izEiXM1OTggpCcPg9tn4YriK5vAjowJsHwdBIdx0fYRsURkxLG2RLm9gyBq66gwtWI8Gx0/ov+JKQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.34.4.tgz", + "integrity": "sha512-8hDVvW9eu4yHWnjaOOR8kHVrew1iIX+MUgwxSuH2XyYeNRtLUe4VNioSqbNkB7ZYQJj9rUTT4PyRscyk2PXFKA==", "cpu": [ "arm64" ], @@ -961,13 +970,13 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-arm64": "1.2.0" + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3" } }, "node_modules/@img/sharp-linuxmusl-x64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.3.tgz", - "integrity": "sha512-gCWUn9547K5bwvOn9l5XGAEjVTTRji4aPTqLzGXHvIr6bIDZKNTA34seMPgM0WmSf+RYBH411VavCejp3PkOeQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.34.4.tgz", + "integrity": "sha512-lU0aA5L8QTlfKjpDCEFOZsTYGn3AEiO6db8W5aQDxj0nQkVrZWmN3ZP9sYKWJdtq3PWPhUNlqehWyXpYDcI9Sg==", "cpu": [ "x64" ], @@ -983,20 +992,20 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-libvips-linuxmusl-x64": "1.2.0" + "@img/sharp-libvips-linuxmusl-x64": "1.2.3" } }, "node_modules/@img/sharp-wasm32": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.3.tgz", - "integrity": "sha512-+CyRcpagHMGteySaWos8IbnXcHgfDn7pO2fiC2slJxvNq9gDipYBN42/RagzctVRKgxATmfqOSulgZv5e1RdMg==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.34.4.tgz", + "integrity": "sha512-33QL6ZO/qpRyG7woB/HUALz28WnTMI2W1jgX3Nu2bypqLIKx/QKMILLJzJjI+SIbvXdG9fUnmrxR7vbi1sTBeA==", "cpu": [ "wasm32" ], "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", "optional": true, "dependencies": { - "@emnapi/runtime": "^1.4.4" + "@emnapi/runtime": "^1.5.0" }, "engines": { "node": "^18.17.0 || ^20.3.0 || >=21.0.0" @@ -1006,9 +1015,9 @@ } }, "node_modules/@img/sharp-win32-arm64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.3.tgz", - "integrity": "sha512-MjnHPnbqMXNC2UgeLJtX4XqoVHHlZNd+nPt1kRPmj63wURegwBhZlApELdtxM2OIZDRv/DFtLcNhVbd1z8GYXQ==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-arm64/-/sharp-win32-arm64-0.34.4.tgz", + "integrity": "sha512-2Q250do/5WXTwxW3zjsEuMSv5sUU4Tq9VThWKlU2EYLm4MB7ZeMwF+SFJutldYODXF6jzc6YEOC+VfX0SZQPqA==", "cpu": [ "arm64" ], @@ -1025,9 +1034,9 @@ } }, "node_modules/@img/sharp-win32-ia32": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.3.tgz", - "integrity": "sha512-xuCdhH44WxuXgOM714hn4amodJMZl3OEvf0GVTm0BEyMeA2to+8HEdRPShH0SLYptJY1uBw+SCFP9WVQi1Q/cw==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.34.4.tgz", + "integrity": "sha512-3ZeLue5V82dT92CNL6rsal6I2weKw1cYu+rGKm8fOCCtJTR2gYeUfY3FqUnIJsMUPIH68oS5jmZ0NiJ508YpEw==", "cpu": [ "ia32" ], @@ -1044,9 +1053,9 @@ } }, "node_modules/@img/sharp-win32-x64": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.3.tgz", - "integrity": "sha512-OWwz05d++TxzLEv4VnsTz5CmZ6mI6S05sfQGEMrNrQcOEERbX46332IvE7pO/EUiw7jUrrS40z/M7kPyjfl04g==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.34.4.tgz", + "integrity": "sha512-xIyj4wpYs8J18sVN3mSQjwrw7fKUqRw+Z5rnHNCy5fYTxigBz81u5mOMPmFumwjcn8+ld1ppptMBCLic1nz6ig==", "cpu": [ "x64" ], @@ -2142,17 +2151,17 @@ } }, "node_modules/@puppeteer/browsers": { - "version": "2.10.6", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.6.tgz", - "integrity": "sha512-pHUn6ZRt39bP3698HFQlu2ZHCkS/lPcpv7fVQcGBSzNNygw171UXAKrCUhy+TEMw4lEttOKDgNpb04hwUAJeiQ==", + "version": "2.10.12", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.12.tgz", + "integrity": "sha512-mP9iLFZwH+FapKJLeA7/fLqOlSUwYpMwjR1P5J23qd4e7qGJwecJccJqHYrjw33jmIZYV4dtiTHPD/J+1e7cEw==", "license": "Apache-2.0", "dependencies": { - "debug": "^4.4.1", + "debug": "^4.4.3", "extract-zip": "^2.0.1", "progress": "^2.0.3", "proxy-agent": "^6.5.0", - "semver": "^7.7.2", - "tar-fs": "^3.1.0", + "semver": "^7.7.3", + "tar-fs": "^3.1.1", "yargs": "^17.7.2" }, "bin": { @@ -2299,14 +2308,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.34.1.tgz", - "integrity": "sha512-nuHlOmFZfuRwLJKDGQOVc0xnQrAmuq1Mj/ISou5044y1ajGNp2BNliIqp7F2LPQ5sForz8lempMFCovfeS1XoA==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.39.0.tgz", + "integrity": "sha512-CTzJqaSq30V/Z2Og9jogzZt8lJRR5TKlAdXmWgdu4hgcC9Kww5flQ+xFvMxIBWVNdxJO7OifgdOK4PokMIWPew==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.34.1", - "@typescript-eslint/types": "^8.34.1", + "@typescript-eslint/tsconfig-utils": "^8.39.0", + "@typescript-eslint/types": "^8.39.0", "debug": "^4.3.4" }, "engines": { @@ -2317,18 +2326,18 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.34.1.tgz", - "integrity": "sha512-beu6o6QY4hJAgL1E8RaXNC071G4Kso2MGmJskCFQhRhg8VOH/FDbC8soP8NHN7e/Hdphwp8G8cE6OBzC8o41ZA==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.39.0.tgz", + "integrity": "sha512-8QOzff9UKxOh6npZQ/4FQu4mjdOCGSdO3p44ww0hk8Vu+IGbg0tB/H1LcTARRDzGCC8pDGbh2rissBuuoPgH8A==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1" + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2339,9 +2348,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.34.1.tgz", - "integrity": "sha512-K4Sjdo4/xF9NEeA2khOb7Y5nY6NSXBnod87uniVYW9kHP+hNlDV8trUSFeynA2uxWam4gIWgWoygPrv9VMWrYg==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.39.0.tgz", + "integrity": "sha512-Fd3/QjmFV2sKmvv3Mrj8r6N8CryYiCS8Wdb/6/rgOXAWGcFuc+VkQuG28uk/4kVNVZBQuuDHEDUpo/pQ32zsIQ==", "dev": true, "license": "MIT", "engines": { @@ -2352,13 +2361,13 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/types": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.34.1.tgz", - "integrity": "sha512-rjLVbmE7HR18kDsjNIZQHxmv9RZwlgzavryL5Lnj2ujIRTeXlKtILHgRNmQ3j4daw7zd+mQgy+uyt6Zo6I0IGA==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.39.0.tgz", + "integrity": "sha512-ArDdaOllnCj3yn/lzKn9s0pBQYmmyme/v1HbGIGB0GB/knFI3fWMHloC+oYTJW46tVbYnGKTMDK4ah1sC2v0Kg==", "dev": true, "license": "MIT", "engines": { @@ -2370,16 +2379,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.34.1.tgz", - "integrity": "sha512-rjCNqqYPuMUF5ODD+hWBNmOitjBWghkGKJg6hiCHzUvXRy6rK22Jd3rwbP2Xi+R7oYVvIKhokHVhH41BxPV5mA==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.39.0.tgz", + "integrity": "sha512-ndWdiflRMvfIgQRpckQQLiB5qAKQ7w++V4LlCHwp62eym1HLB/kw7D9f2e8ytONls/jt89TEasgvb+VwnRprsw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.34.1", - "@typescript-eslint/tsconfig-utils": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/visitor-keys": "8.34.1", + "@typescript-eslint/project-service": "8.39.0", + "@typescript-eslint/tsconfig-utils": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/visitor-keys": "8.39.0", "debug": "^4.3.4", "fast-glob": "^3.3.2", "is-glob": "^4.0.3", @@ -2395,20 +2404,20 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/utils": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.34.1.tgz", - "integrity": "sha512-mqOwUdZ3KjtGk7xJJnLbHxTuWVn3GO2WZZuM+Slhkun4+qthLdXx32C8xIXbO1kfCECb3jIs3eoxK3eryk7aoQ==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.39.0.tgz", + "integrity": "sha512-4GVSvNA0Vx1Ktwvf4sFE+exxJ3QGUorQG1/A5mRfRNZtkBT2xrA/BCO2H0eALx/PnvCS6/vmYwRdDA41EoffkQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.7.0", - "@typescript-eslint/scope-manager": "8.34.1", - "@typescript-eslint/types": "8.34.1", - "@typescript-eslint/typescript-estree": "8.34.1" + "@typescript-eslint/scope-manager": "8.39.0", + "@typescript-eslint/types": "8.39.0", + "@typescript-eslint/typescript-estree": "8.39.0" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2419,17 +2428,17 @@ }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.9.0" + "typescript": ">=4.8.4 <6.0.0" } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.34.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.34.1.tgz", - "integrity": "sha512-xoh5rJ+tgsRKoXnkBPFRLZ7rjKM0AfVbC68UZ/ECXoDbfggb9RbEySN359acY1vS3qZ0jVTVWzbtfapwm5ztxw==", + "version": "8.39.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.39.0.tgz", + "integrity": "sha512-ldgiJ+VAhQCfIjeOgu8Kj5nSxds0ktPOSO9p4+0VDH2R2pLvQraaM5Oen2d7NxzMCm+Sn/vJT+mv2H5u6b/3fA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.34.1", + "@typescript-eslint/types": "8.39.0", "eslint-visitor-keys": "^4.2.1" }, "engines": { @@ -2574,9 +2583,9 @@ } }, "node_modules/ansi-escapes": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", - "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.1.1.tgz", + "integrity": "sha512-Zhl0ErHcSRUaVfGUeUdDuLgpkEo8KIFjB4Y9uAc46ScOpdDiU1Dbyplh7qWJeJ/ZHpbyMSM26+X3BySgnIz40Q==", "dev": true, "license": "MIT", "dependencies": { @@ -2924,10 +2933,18 @@ } }, "node_modules/b4a": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.6.7.tgz", - "integrity": "sha512-OnAYlL5b7LEkALw87fUVafQw5rVR9RjwGd4KUwNQ6DrrNmaVaUCgLipfVlzrPQ4tWOR9P0IXGNOx50jYCCdSJg==", - "license": "Apache-2.0" + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } }, "node_modules/balanced-match": { "version": "1.0.2", @@ -2936,22 +2953,31 @@ "license": "MIT" }, "node_modules/bare-events": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.6.0.tgz", - "integrity": "sha512-EKZ5BTXYExaNqi3I3f9RtEsaI/xBSGjE0XZCZilPzFAV/goswFHuPd9jEZlPIZ/iNZJwDSao9qRiScySz7MbQg==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.8.1.tgz", + "integrity": "sha512-oxSAxTS1hRfnyit2CL5QpAOS5ixfBjj6ex3yTNvXyY/kE719jQ/IjuESJBK2w5v4wwQRAHGseVJXx9QBYOtFGQ==", "license": "Apache-2.0", - "optional": true + "peerDependencies": { + "bare-abort-controller": "*" + }, + "peerDependenciesMeta": { + "bare-abort-controller": { + "optional": true + } + } }, "node_modules/bare-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.6.tgz", - "integrity": "sha512-25RsLF33BqooOEFNdMcEhMpJy8EoR88zSMrnOQOaM3USnOK2VmaJ1uaQEwPA6AQjrv1lXChScosN6CzbwbO9OQ==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.5.0.tgz", + "integrity": "sha512-GljgCjeupKZJNetTqxKaQArLK10vpmK28or0+RwWjEl5Rk+/xG3wkpmkv+WrcBm3q1BwHKlnhXzR8O37kcvkXQ==", "license": "Apache-2.0", "optional": true, "dependencies": { "bare-events": "^2.5.4", "bare-path": "^3.0.0", - "bare-stream": "^2.6.4" + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" }, "engines": { "bare": ">=1.16.0" @@ -2966,9 +2992,9 @@ } }, "node_modules/bare-os": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", - "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", "license": "Apache-2.0", "optional": true, "engines": { @@ -2986,9 +3012,9 @@ } }, "node_modules/bare-stream": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", - "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", "license": "Apache-2.0", "optional": true, "dependencies": { @@ -3007,6 +3033,16 @@ } } }, + "node_modules/bare-url": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.3.1.tgz", + "integrity": "sha512-v2yl0TnaZTdEnelkKtXZGnotiV6qATBlnNuUMrHl6v9Lmmrh9mw9RYyImPU7/4RahumSwQS1k2oKXcRfXcbjJw==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -3629,9 +3665,9 @@ } }, "node_modules/chromium-bidi": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-7.2.0.tgz", - "integrity": "sha512-gREyhyBstermK+0RbcJLbFhcQctg92AGgDe/h/taMJEOLRdtSswBAO9KmvltFSQWgM2LrwWu5SIuEUbdm3JsyQ==", + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-10.5.1.tgz", + "integrity": "sha512-rlj6OyhKhVTnk4aENcUme3Jl9h+cq4oXu4AzBcvr8RMmT6BR4a3zSNT9dbIfXr9/BS6ibzRyDhowuw4n2GgzsQ==", "license": "Apache-2.0", "dependencies": { "mitt": "^3.0.1", @@ -3689,26 +3725,26 @@ } }, "node_modules/cli-truncate": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", - "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-5.1.1.tgz", + "integrity": "sha512-SroPvNHxUnk+vIW/dOSfNqdy1sPEFkrTk6TUtqLCnBlo3N7TNYYkzzN7uSD6+jVjrdO4+p8nH7JzH6cIvUem6A==", "dev": true, "license": "MIT", "dependencies": { - "slice-ansi": "^5.0.0", - "string-width": "^7.0.0" + "slice-ansi": "^7.1.0", + "string-width": "^8.0.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate/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": { @@ -3718,35 +3754,27 @@ "url": "https://github.com/chalk/ansi-regex?sponsor=1" } }, - "node_modules/cli-truncate/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", - "dev": true, - "license": "MIT" - }, "node_modules/cli-truncate/node_modules/string-width": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", - "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-8.1.0.tgz", + "integrity": "sha512-Kxl3KJGb/gxkaUMOjRsQ8IrXiGW75O4E3RPjFIINOVH8AMl2SQ/yWdTzWwF3FevIX9LcMAjJW+GRwAlAbTSXdg==", "dev": true, "license": "MIT", "dependencies": { - "emoji-regex": "^10.3.0", - "get-east-asian-width": "^1.0.0", + "get-east-asian-width": "^1.3.0", "strip-ansi": "^7.1.0" }, "engines": { - "node": ">=18" + "node": ">=20" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" } }, "node_modules/cli-truncate/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.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -3831,19 +3859,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/color": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", - "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1", - "color-string": "^1.9.0" - }, - "engines": { - "node": ">=12.5.0" - } - }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", @@ -3862,16 +3877,6 @@ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "license": "MIT" }, - "node_modules/color-string": { - "version": "1.9.1", - "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", - "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", - "license": "MIT", - "dependencies": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" - } - }, "node_modules/color-support": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", @@ -4116,9 +4121,9 @@ "link": true }, "node_modules/countly-sdk-nodejs": { - "version": "24.10.2", - "resolved": "https://registry.npmjs.org/countly-sdk-nodejs/-/countly-sdk-nodejs-24.10.2.tgz", - "integrity": "sha512-ags7l6aXy31ZYgXHgE/GAH/12PhYp9Mzn2vtQHSxhKl0BT6AdlSu3XEYeFmbk8ouwHN8AnGB7YeqOiw5Z3TrxQ==", + "version": "24.10.3", + "resolved": "https://registry.npmjs.org/countly-sdk-nodejs/-/countly-sdk-nodejs-24.10.3.tgz", + "integrity": "sha512-Xf5P6AuyGR63s91ZHAPZbz5vq6xnP0hSUq4tA5qVvZcGJMhzzeOtG1sadY5oXYFfbvKolhrzcL3QlstkUfqz0A==", "license": "MIT" }, "node_modules/countly-sdk-web": { @@ -4274,20 +4279,18 @@ } }, "node_modules/csvtojson": { - "version": "2.0.10", - "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.10.tgz", - "integrity": "sha512-lUWFxGKyhraKCW8Qghz6Z0f2l/PqB1W3AO0HKJzGIQ5JRSlR651ekJDiGJbBT4sRNNv5ddnSGVEnsxP9XRCVpQ==", + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/csvtojson/-/csvtojson-2.0.14.tgz", + "integrity": "sha512-F7NNvhhDyob7OsuEGRsH0FM1aqLs/WYITyza3l+hTEEmOK9sGPBlYQZwlVG0ezCojXYpE17lhS5qL6BCOZSPyA==", "license": "MIT", "dependencies": { - "bluebird": "^3.5.1", - "lodash": "^4.17.3", - "strip-bom": "^2.0.0" + "lodash": "^4.17.21" }, "bin": { "csvtojson": "bin/csvtojson" }, "engines": { - "node": ">=4.0.0" + "node": ">=8.0.0" } }, "node_modules/dargs": { @@ -4318,9 +4321,9 @@ } }, "node_modules/debug": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz", - "integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==", + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", "dependencies": { "ms": "^2.1.3" @@ -4494,9 +4497,9 @@ } }, "node_modules/devtools-protocol": { - "version": "0.0.1464554", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", - "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", + "version": "0.0.1508733", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1508733.tgz", + "integrity": "sha512-QJ1R5gtck6nDcdM+nlsaJXcelPEI7ZxSMw1ujHpO1c4+9l+Nue5qlebi9xO1Z2MGr92bFOQTW7/rrheh5hHxDg==", "license": "BSD-3-Clause" }, "node_modules/dezalgo": { @@ -4542,11 +4545,6 @@ "node": ">=6.0.0" } }, - "node_modules/dom-walk": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/dom-walk/-/dom-walk-0.1.2.tgz", - "integrity": "sha512-6QvTW9mrGeIegrFXdtQi9pk7O/nSK6lSdXW2eqUspN5LWD7UTji2Fqw5V2YLjBpHEoU9Xl/eUWNpDeZvoyOv2w==" - }, "node_modules/dtrace-provider": { "version": "0.8.8", "resolved": "https://registry.npmjs.org/dtrace-provider/-/dtrace-provider-0.8.8.tgz", @@ -4889,9 +4887,9 @@ } }, "node_modules/eslint-plugin-vue": { - "version": "10.3.0", - "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.3.0.tgz", - "integrity": "sha512-A0u9snqjCfYaPnqqOaH6MBLVWDUIN4trXn8J3x67uDcXvR7X6Ut8p16N+nYhMCQ9Y7edg2BIRGzfyZsY0IdqoQ==", + "version": "10.5.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-10.5.1.tgz", + "integrity": "sha512-SbR9ZBUFKgvWAbq3RrdCtWaW0IKm6wwUiApxf3BVTNfqUIo4IQQmreMg2iHFJJ6C/0wss3LXURBJ1OwS/MhFcQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4906,11 +4904,15 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" }, "peerDependencies": { + "@stylistic/eslint-plugin": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0", "@typescript-eslint/parser": "^7.0.0 || ^8.0.0", "eslint": "^8.57.0 || ^9.0.0", "vue-eslint-parser": "^10.0.0" }, "peerDependenciesMeta": { + "@stylistic/eslint-plugin": { + "optional": true + }, "@typescript-eslint/parser": { "optional": true } @@ -5116,6 +5118,15 @@ "node": ">=0.8.x" } }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, "node_modules/exif-parser": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/exif-parser/-/exif-parser-0.1.12.tgz", @@ -5197,9 +5208,9 @@ } }, "node_modules/express-rate-limit": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.0.1.tgz", - "integrity": "sha512-aZVCnybn7TVmxO4BtlmnvX+nuz8qHW124KKJ8dumsBsmv5ZLxE0pYu7S2nwyRBGHHCAzdmnGyrc5U/rksSPO7Q==", + "version": "8.2.1", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", + "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", "license": "MIT", "dependencies": { "ip-address": "10.0.1" @@ -5892,9 +5903,9 @@ "license": "MIT" }, "node_modules/fs-extra": { - "version": "11.3.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.0.tgz", - "integrity": "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew==", + "version": "11.3.2", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-11.3.2.tgz", + "integrity": "sha512-Xr9F6z6up6Ws+NjzMCZc6WXg2YFRlrLP9NQDO3VQrWrfiojdhS56TzueT88ze0uBdCTwEIhQ3ptnmKeWGFAe0A==", "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", @@ -6135,9 +6146,9 @@ } }, "node_modules/get-east-asian-width": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.3.0.tgz", - "integrity": "sha512-vpeMIQKxczTD/0s2CdEWHcb0eeJe6TFjxb+J5xgX7hScxqrGuyjmv4c1D4A/gelKfyox0gJJwIHF+fLjeaM8kQ==", + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.4.0.tgz", + "integrity": "sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==", "dev": true, "license": "MIT", "engines": { @@ -6195,15 +6206,15 @@ } }, "node_modules/get-random-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-random-values/-/get-random-values-3.0.0.tgz", - "integrity": "sha512-mNznaBdYcpz7UAdnOtDGcLdNwAa79mXl5htEyyZ51YaeAWNf2g4x/2yCVBdNNTbi35wX0Stc2PJXM7G6rcONOA==", + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/get-random-values/-/get-random-values-4.1.0.tgz", + "integrity": "sha512-nq0Ud4+6jJPDxMSN6J8y6PuqvMHFoJLUqHVKtSj1qJ5WObHkBCRAI8YLVFNWEbCZERbS0D9RYKN8fwALRuJR7g==", "license": "MIT", "dependencies": { - "global": "^4.4.0" + "window-or-global": "^1.0.1" }, "engines": { - "node": "18 || >=20" + "node": "20 || 22 || >=24" } }, "node_modules/get-stream": { @@ -6293,16 +6304,6 @@ "node": ">=10.13.0" } }, - "node_modules/global": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/global/-/global-4.4.0.tgz", - "integrity": "sha512-wv/LAoHdRE3BeTGz53FAamhGlPLhlssK45usmGFThIi4XqnBmjKQ16u+RNbP7WvigRZDxUsM0J3gcQ5yicaL0w==", - "license": "MIT", - "dependencies": { - "min-document": "^2.19.0", - "process": "^0.11.10" - } - }, "node_modules/global-modules": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-1.0.0.tgz", @@ -7550,13 +7551,16 @@ } }, "node_modules/is-fullwidth-code-point": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", - "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz", + "integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==", "dev": true, "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.3.1" + }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/sponsors/sindresorhus" @@ -7679,12 +7683,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha512-rMYPYvCzsXywIsldgLaSoPlw5PfoB/ssr7hY4pLfcodrA5M/eArza1a9VmTiNIBNMjOGr1Ow9mTyU2o69U6U9Q==", - "license": "MIT" - }, "node_modules/is-windows": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/is-windows/-/is-windows-1.0.2.tgz", @@ -8233,19 +8231,6 @@ "node": ">= 8" } }, - "node_modules/lilconfig": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz", - "integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/antonk52" - } - }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", @@ -8253,22 +8238,19 @@ "license": "MIT" }, "node_modules/lint-staged": { - "version": "16.1.2", - "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.1.2.tgz", - "integrity": "sha512-sQKw2Si2g9KUZNY3XNvRuDq4UJqpHwF0/FQzZR2M7I5MvtpWvibikCjUVJzZdGE0ByurEl3KQNvsGetd1ty1/Q==", + "version": "16.2.6", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-16.2.6.tgz", + "integrity": "sha512-s1gphtDbV4bmW1eylXpVMk2u7is7YsrLl8hzrtvC70h4ByhcMLZFY01Fx05ZUDNuv1H8HO4E+e2zgejV1jVwNw==", "dev": true, "license": "MIT", "dependencies": { - "chalk": "^5.4.1", - "commander": "^14.0.0", - "debug": "^4.4.1", - "lilconfig": "^3.1.3", - "listr2": "^8.3.3", + "commander": "^14.0.1", + "listr2": "^9.0.5", "micromatch": "^4.0.8", - "nano-spawn": "^1.0.2", + "nano-spawn": "^2.0.0", "pidtree": "^0.6.0", "string-argv": "^0.3.2", - "yaml": "^2.8.0" + "yaml": "^2.8.1" }, "bin": { "lint-staged": "bin/lint-staged.js" @@ -8280,23 +8262,10 @@ "url": "https://opencollective.com/lint-staged" } }, - "node_modules/lint-staged/node_modules/chalk": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz", - "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==", - "dev": true, - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, "node_modules/lint-staged/node_modules/commander": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz", - "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.1.tgz", + "integrity": "sha512-2JkV3gUZUVrbNA+1sjBOYLsMZ5cEEl8GTFP2a4AVz5hvasAMCQ1D2l2le/cX+pV4N6ZU17zjUahLpIXRrnWL8A==", "dev": true, "license": "MIT", "engines": { @@ -8304,13 +8273,13 @@ } }, "node_modules/listr2": { - "version": "8.3.3", - "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz", - "integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==", + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-9.0.5.tgz", + "integrity": "sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==", "dev": true, "license": "MIT", "dependencies": { - "cli-truncate": "^4.0.0", + "cli-truncate": "^5.0.0", "colorette": "^2.0.20", "eventemitter3": "^5.0.1", "log-update": "^6.1.0", @@ -8318,13 +8287,13 @@ "wrap-ansi": "^9.0.0" }, "engines": { - "node": ">=18.0.0" + "node": ">=20.0.0" } }, "node_modules/listr2/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": { @@ -8335,9 +8304,9 @@ } }, "node_modules/listr2/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": { @@ -8348,9 +8317,9 @@ } }, "node_modules/listr2/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, @@ -8373,9 +8342,9 @@ } }, "node_modules/listr2/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.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -8389,9 +8358,9 @@ } }, "node_modules/listr2/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -8560,9 +8529,9 @@ } }, "node_modules/log-update/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": { @@ -8573,9 +8542,9 @@ } }, "node_modules/log-update/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": { @@ -8586,45 +8555,12 @@ } }, "node_modules/log-update/node_modules/emoji-regex": { - "version": "10.4.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.4.0.tgz", - "integrity": "sha512-EC+0oUMY1Rqm4O6LLrgjtYDvcVYTy7chDnM4Q7030tP4Kwj3u/pR6gP9ygnp2CJMK5Gq+9Q2oqmrFJAz01DXjw==", + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz", + "integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==", "dev": true, "license": "MIT" }, - "node_modules/log-update/node_modules/is-fullwidth-code-point": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", - "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", - "dev": true, - "license": "MIT", - "dependencies": { - "get-east-asian-width": "^1.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/log-update/node_modules/slice-ansi": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", - "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", - "dev": true, - "license": "MIT", - "dependencies": { - "ansi-styles": "^6.2.1", - "is-fullwidth-code-point": "^5.0.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/chalk/slice-ansi?sponsor=1" - } - }, "node_modules/log-update/node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -8644,9 +8580,9 @@ } }, "node_modules/log-update/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.1.2", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", "dev": true, "license": "MIT", "dependencies": { @@ -8660,9 +8596,9 @@ } }, "node_modules/log-update/node_modules/wrap-ansi": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", - "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "version": "9.0.2", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz", + "integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==", "dev": true, "license": "MIT", "dependencies": { @@ -9039,14 +8975,6 @@ "node": ">=4" } }, - "node_modules/min-document": { - "version": "2.19.0", - "resolved": "https://registry.npmjs.org/min-document/-/min-document-2.19.0.tgz", - "integrity": "sha512-9Wy1B3m3f66bPPmU5hdA4DR4PB2OfDU/+GS3yAB7IQozE3tqXaVv2zOjgla7MEGSRv95+ILmOuvhLkOK6wJtCQ==", - "dependencies": { - "dom-walk": "^0.1.0" - } - }, "node_modules/minimatch": { "version": "9.0.5", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", @@ -9531,14 +9459,14 @@ } }, "node_modules/mongodb": { - "version": "6.18.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.18.0.tgz", - "integrity": "sha512-fO5ttN9VC8P0F5fqtQmclAkgXZxbIkYRTUi1j8JO6IYwvamkhtYDilJr35jOPELR49zqCJgXZWwCtW7B+TM8vQ==", + "version": "6.20.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.20.0.tgz", + "integrity": "sha512-Tl6MEIU3K4Rq3TSHd+sZQqRBoGlFsOgNrH5ltAcFBV62Re3Fd+FcaVf8uSEQFOJ51SDowDVttBTONMfoYWrWlQ==", "license": "Apache-2.0", "dependencies": { - "@mongodb-js/saslprep": "^1.1.9", + "@mongodb-js/saslprep": "^1.3.0", "bson": "^6.10.4", - "mongodb-connection-string-url": "^3.0.0" + "mongodb-connection-string-url": "^3.0.2" }, "engines": { "node": ">=16.20.1" @@ -9549,7 +9477,7 @@ "gcp-metadata": "^5.2.0", "kerberos": "^2.0.1", "mongodb-client-encryption": ">=6.0.0 <7", - "snappy": "^7.2.2", + "snappy": "^7.3.2", "socks": "^2.7.1" }, "peerDependenciesMeta": { @@ -9671,9 +9599,9 @@ "optional": true }, "node_modules/nano-spawn": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-1.0.2.tgz", - "integrity": "sha512-21t+ozMQDAL/UGgQVBbZ/xXvNO10++ZPuTmKRO8k9V3AClVRht49ahtDjfY8l1q6nSHOrE5ASfthzH3ol6R/hg==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/nano-spawn/-/nano-spawn-2.0.0.tgz", + "integrity": "sha512-tacvGzUY5o2D8CBh2rrwxyNojUsZNU2zjNTzKQrkgGJQTbGAfArVWXSKMBokBeeg6C7OLRGUEyoFlYbfeWQIqw==", "dev": true, "license": "MIT", "engines": { @@ -9876,9 +9804,10 @@ "license": "MIT" }, "node_modules/nodemailer": { - "version": "7.0.5", - "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.5.tgz", - "integrity": "sha512-nsrh2lO3j4GkLLXoeEksAMgAOqxOv6QumNRVQTJwKH4nuiww6iC2y7GyANs9kRAxCexg3+lTWM3PZ91iLlVjfg==", + "version": "7.0.10", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-7.0.10.tgz", + "integrity": "sha512-Us/Se1WtT0ylXgNFfyFSx4LElllVLJXQjWi2Xz17xWw7amDKO2MLtFnVp1WACy7GkVGs+oBlRopVNUzlrGSw1w==", + "license": "MIT-0", "engines": { "node": ">=6.0.0" } @@ -10926,9 +10855,9 @@ } }, "node_modules/prebuild-install/node_modules/tar-fs": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", - "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", "license": "MIT", "dependencies": { "chownr": "^1.1.1", @@ -11106,17 +11035,17 @@ } }, "node_modules/puppeteer": { - "version": "24.15.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.15.0.tgz", - "integrity": "sha512-HPSOTw+DFsU/5s2TUUWEum9WjFbyjmvFDuGHtj2X4YUz2AzOzvKMkT3+A3FR+E+ZefiX/h3kyLyXzWJWx/eMLQ==", + "version": "24.26.1", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.26.1.tgz", + "integrity": "sha512-3RG2UqclzMFolM2fS4bN8t5/EjZ0VwEoAGVxG8PMGeprjLzj+x0U4auH7MQ4B6ftW+u1JUnTTN8ab4ABPdl4mA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.10.6", - "chromium-bidi": "7.2.0", + "@puppeteer/browsers": "2.10.12", + "chromium-bidi": "10.5.1", "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1464554", - "puppeteer-core": "24.15.0", + "devtools-protocol": "0.0.1508733", + "puppeteer-core": "24.26.1", "typed-query-selector": "^2.12.0" }, "bin": { @@ -11127,16 +11056,17 @@ } }, "node_modules/puppeteer-core": { - "version": "24.15.0", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.15.0.tgz", - "integrity": "sha512-2iy0iBeWbNyhgiCGd/wvGrDSo73emNFjSxYOcyAqYiagkYt5q4cPfVXaVDKBsukgc2fIIfLAalBZlaxldxdDYg==", + "version": "24.26.1", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.26.1.tgz", + "integrity": "sha512-YHZdo3chJ5b9pTYVnuDuoI3UX/tWJFJyRZvkLbThGy6XeHWC+0KI8iN0UMCkvde5l/YOk3huiVZ/PvwgSbwdrA==", "license": "Apache-2.0", "dependencies": { - "@puppeteer/browsers": "2.10.6", - "chromium-bidi": "7.2.0", - "debug": "^4.4.1", - "devtools-protocol": "0.0.1464554", + "@puppeteer/browsers": "2.10.12", + "chromium-bidi": "10.5.1", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1508733", "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.3.8", "ws": "^8.18.3" }, "engines": { @@ -11598,9 +11528,9 @@ "license": "MIT" }, "node_modules/sass": { - "version": "1.89.2", - "resolved": "https://registry.npmjs.org/sass/-/sass-1.89.2.tgz", - "integrity": "sha512-xCmtksBKd/jdJ9Bt9p7nPKiuqrlBMBuuGkQlkhZjjQk3Ty48lv93k5Dq6OPkKt4XwxDJ7tvlfrTa1MPA9bf+QA==", + "version": "1.93.3", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.93.3.tgz", + "integrity": "sha512-elOcIZRTM76dvxNAjqYrucTSI0teAF/L2Lv0s6f6b7FOwcwIuA357bIE871580AjHJuSvLIRUosgV+lIWx6Rgg==", "license": "MIT", "dependencies": { "chokidar": "^4.0.0", @@ -11624,9 +11554,9 @@ "license": "ISC" }, "node_modules/semver": { - "version": "7.7.2", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", - "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -11734,14 +11664,14 @@ "license": "ISC" }, "node_modules/sharp": { - "version": "0.34.3", - "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.3.tgz", - "integrity": "sha512-eX2IQ6nFohW4DbvHIOLRB3MHFpYqaqvXd3Tp5e/T/dSH83fxaNJQRvDMhASmkNTsNTVF2/OOopzRCt7xokgPfg==", + "version": "0.34.4", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.34.4.tgz", + "integrity": "sha512-FUH39xp3SBPnxWvd5iib1X8XY7J0K0X7d93sie9CJg2PO8/7gmg89Nve6OjItK53/MlAushNNxteBYfM6DEuoA==", "hasInstallScript": true, "license": "Apache-2.0", "dependencies": { - "color": "^4.2.3", - "detect-libc": "^2.0.4", + "@img/colour": "^1.0.0", + "detect-libc": "^2.1.0", "semver": "^7.7.2" }, "engines": { @@ -11751,34 +11681,34 @@ "url": "https://opencollective.com/libvips" }, "optionalDependencies": { - "@img/sharp-darwin-arm64": "0.34.3", - "@img/sharp-darwin-x64": "0.34.3", - "@img/sharp-libvips-darwin-arm64": "1.2.0", - "@img/sharp-libvips-darwin-x64": "1.2.0", - "@img/sharp-libvips-linux-arm": "1.2.0", - "@img/sharp-libvips-linux-arm64": "1.2.0", - "@img/sharp-libvips-linux-ppc64": "1.2.0", - "@img/sharp-libvips-linux-s390x": "1.2.0", - "@img/sharp-libvips-linux-x64": "1.2.0", - "@img/sharp-libvips-linuxmusl-arm64": "1.2.0", - "@img/sharp-libvips-linuxmusl-x64": "1.2.0", - "@img/sharp-linux-arm": "0.34.3", - "@img/sharp-linux-arm64": "0.34.3", - "@img/sharp-linux-ppc64": "0.34.3", - "@img/sharp-linux-s390x": "0.34.3", - "@img/sharp-linux-x64": "0.34.3", - "@img/sharp-linuxmusl-arm64": "0.34.3", - "@img/sharp-linuxmusl-x64": "0.34.3", - "@img/sharp-wasm32": "0.34.3", - "@img/sharp-win32-arm64": "0.34.3", - "@img/sharp-win32-ia32": "0.34.3", - "@img/sharp-win32-x64": "0.34.3" + "@img/sharp-darwin-arm64": "0.34.4", + "@img/sharp-darwin-x64": "0.34.4", + "@img/sharp-libvips-darwin-arm64": "1.2.3", + "@img/sharp-libvips-darwin-x64": "1.2.3", + "@img/sharp-libvips-linux-arm": "1.2.3", + "@img/sharp-libvips-linux-arm64": "1.2.3", + "@img/sharp-libvips-linux-ppc64": "1.2.3", + "@img/sharp-libvips-linux-s390x": "1.2.3", + "@img/sharp-libvips-linux-x64": "1.2.3", + "@img/sharp-libvips-linuxmusl-arm64": "1.2.3", + "@img/sharp-libvips-linuxmusl-x64": "1.2.3", + "@img/sharp-linux-arm": "0.34.4", + "@img/sharp-linux-arm64": "0.34.4", + "@img/sharp-linux-ppc64": "0.34.4", + "@img/sharp-linux-s390x": "0.34.4", + "@img/sharp-linux-x64": "0.34.4", + "@img/sharp-linuxmusl-arm64": "0.34.4", + "@img/sharp-linuxmusl-x64": "0.34.4", + "@img/sharp-wasm32": "0.34.4", + "@img/sharp-win32-arm64": "0.34.4", + "@img/sharp-win32-ia32": "0.34.4", + "@img/sharp-win32-x64": "0.34.4" } }, "node_modules/sharp/node_modules/detect-libc": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.4.tgz", - "integrity": "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -11997,21 +11927,6 @@ "simple-concat": "^1.0.0" } }, - "node_modules/simple-swizzle": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", - "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.3.1" - } - }, - "node_modules/simple-swizzle/node_modules/is-arrayish": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", - "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", - "license": "MIT" - }, "node_modules/simple-xml-to-json": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/simple-xml-to-json/-/simple-xml-to-json-1.2.3.tgz", @@ -12022,26 +11937,26 @@ } }, "node_modules/slice-ansi": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", - "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz", + "integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==", "dev": true, "license": "MIT", "dependencies": { - "ansi-styles": "^6.0.0", - "is-fullwidth-code-point": "^4.0.0" + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" }, "engines": { - "node": ">=12" + "node": ">=18" }, "funding": { "url": "https://github.com/chalk/slice-ansi?sponsor=1" } }, "node_modules/slice-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": { @@ -12246,16 +12161,14 @@ } }, "node_modules/streamx": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.22.1.tgz", - "integrity": "sha512-znKXEBxfatz2GBNK02kRnCXjV+AA4kjZIUxeWSr3UGirZMJfTE9uiwKHobnbgxWyL/JWro8tTq+vOqAK1/qbSA==", + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", "license": "MIT", "dependencies": { + "events-universal": "^1.0.0", "fast-fifo": "^1.3.2", "text-decoder": "^1.1.0" - }, - "optionalDependencies": { - "bare-events": "^2.2.0" } }, "node_modules/string_decoder": { @@ -12409,18 +12322,6 @@ "node": ">=8" } }, - "node_modules/strip-bom": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-2.0.0.tgz", - "integrity": "sha512-kwrX1y7czp1E69n2ajbG65mIo9dqvJ+8aBQXOGVxqwvNbsXdFM6Lq37dLAY3mknUwru8CfcCbfOLL/gMo+fi3g==", - "license": "MIT", - "dependencies": { - "is-utf8": "^0.2.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -12563,9 +12464,9 @@ } }, "node_modules/tar-fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", - "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", "license": "MIT", "dependencies": { "pump": "^3.0.0", @@ -12884,9 +12785,9 @@ } }, "node_modules/typescript": { - "version": "5.8.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", - "integrity": "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ==", + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "devOptional": true, "license": "Apache-2.0", "bin": { @@ -13139,6 +13040,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.8.tgz", + "integrity": "sha512-21Yi2GhGntMc671vNBCjiAeEVknXjVRoyu+k+9xOMShu+ZQfpGQwnBqbNz/Sv4GXZ6JmutlPAi2nIJcrymAWuQ==", + "license": "Apache-2.0" + }, "node_modules/webidl-conversions": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-7.0.0.tgz", @@ -13250,6 +13157,12 @@ "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", + "integrity": "sha512-tE12J/NenOv4xdVobD+AD3fT06T4KNqnzRhkv5nBIu7K+pvOH2oLCEgYP+i+5mF2jtI6FEADheOdZkA8YWET9w==", + "license": "MIT" + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -13489,9 +13402,9 @@ "license": "ISC" }, "node_modules/yaml": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.0.tgz", - "integrity": "sha512-4lLa/EcQCB0cJkyts+FpIRx5G/llPxfP6VQU5KByHEhLxY3IJCH0f0Hy1MHI8sClTvsIb8qwRJ6R/ZdlDJ/leQ==", + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.1.tgz", + "integrity": "sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==", "dev": true, "license": "ISC", "bin": { diff --git a/package.json b/package.json index 95ddf4ee808..e76aa2116b4 100644 --- a/package.json +++ b/package.json @@ -55,17 +55,17 @@ "countly-sdk-nodejs": "*", "countly-sdk-web": "*", "csurf": "^1.11.0", - "csvtojson": "2.0.10", + "csvtojson": "2.0.14", "ejs": "3.1.10", "errorhandler": "1.5.1", "express": "4.21.2", - "express-rate-limit": "8.0.1", + "express-rate-limit": "8.2.1", "express-session": "1.18.2", "form-data": "^4.0.0", "formidable": "2.1.3", - "fs-extra": "11.3.0", + "fs-extra": "11.3.2", "geoip-lite": "1.4.10", - "get-random-values": "^3.0.0", + "get-random-values": "^4.0.0", "grunt": "1.6.1", "grunt-cli": "1.5.0", "grunt-contrib-concat": "2.1.0", @@ -85,14 +85,14 @@ "method-override": "3.0.0", "moment": "2.30.1", "moment-timezone": "0.6.0", - "mongodb": "6.18.0", + "mongodb": "6.20.0", "nginx-conf": "2.1.0", - "nodemailer": "7.0.5", + "nodemailer": "7.0.10", "object-hash": "3.0.0", "offline-geocoder": "git+https://github.com/Countly/offline-geocoder.git", "properties-parser": "0.6.0", "puppeteer": "^24.6.1", - "sass": "1.89.2", + "sass": "1.93.3", "semver": "^7.7.1", "sharp": "^0.34.2", "sqlite3": "^5.1.7", diff --git a/plugins/alerts/api/alertModules/events.js b/plugins/alerts/api/alertModules/events.js index 5e1304a5c2b..14f6b57b735 100644 --- a/plugins/alerts/api/alertModules/events.js +++ b/plugins/alerts/api/alertModules/events.js @@ -2,7 +2,6 @@ * @typedef {import('../parts/common-lib.js').App} App */ -const crypto = require('crypto'); const log = require('../../../../api/utils/log.js')('alert:events'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); @@ -101,14 +100,10 @@ async function getEventMetricByDate(app, event, metric, date, period, segments) if (segments) { segmentKeys = Object.keys(segments); } - - const collectionName = "events" + crypto - .createHash('sha1') - .update(event + app._id.toString()) - .digest('hex'); - - const records = await common.db.collection(collectionName) + const records = await common.db.collection("events_data") .find({ + a: app._id.toString(), + e: event, m: monthFilter, s: { $in: segmentKeys }, }) @@ -162,11 +157,13 @@ async function getEventMetricByDate(app, event, metric, date, period, segments) } /* (async function() { + if (!require("cluster").isPrimary) { + return; + } await new Promise(res => setTimeout(res, 2000)); - const app = { _id: "65c1f875a12e98a328d5eb9e", timezone: "Europe/Istanbul" }; - const date = new Date("2024-01-02T12:47:19.247Z"); - const date2 = new Date("2024-01-03T13:47:19.247Z"); - const event = "Checkout"; + const app = { _id: "67fff00d901abe2f8cc57646", timezone: "Europe/Istanbul" }; + const date = new Date("2025-02-02T12:47:19.247Z"); + const event = "Product Viewed"; const prop = "c"; const hourly = await getEventMetricByDate(app, event, prop, date, "hourly"); diff --git a/plugins/alerts/api/alertModules/rating.js b/plugins/alerts/api/alertModules/rating.js index aad6c6d6485..41c0159e059 100644 --- a/plugins/alerts/api/alertModules/rating.js +++ b/plugins/alerts/api/alertModules/rating.js @@ -2,7 +2,6 @@ * @typedef {import('../parts/common-lib.js').App} App */ -const crypto = require('crypto'); const log = require('../../../../api/utils/log.js')('alert:rating'); const moment = require('moment-timezone'); const common = require('../../../../api/utils/common.js'); @@ -111,14 +110,13 @@ module.exports.check = async function({ alertConfigs: alert, done, scheduledTo: async function getRatingResponsesByDate(app, widgetId, date, period, ratings) { const { years } = commonLib.getDateComponents(date, app.timezone); const eventName = "[CLY]_star_rating"; - const collectionName = "events" + crypto - .createHash('sha1') - .update(eventName + app._id.toString()) - .digest('hex'); - // find all segment values: - const records = await common.db.collection(collectionName) - .find({ m: String(years) + ":0" }) + const records = await common.db.collection("events_data") + .find({ + a: app._id.toString(), + e: eventName, + m: String(years) + ":0", + }) .toArray(); const segmentValueSet = new Set; for (const record of records) { @@ -154,10 +152,13 @@ async function getRatingResponsesByDate(app, widgetId, date, period, ratings) { /* (async function() { + if (!require("cluster").isPrimary) { + return; + } await new Promise(res => setTimeout(res, 2000)); - const app = { _id: ObjectId("65c1f875a12e98a328d5eb9e"), timezone: "Europe/Istanbul" }; - const date = new Date("2024-02-07T12:00:00.000Z"); - const widgetId = "65c383fbb46a4d172d7c58e1"; + const app = { _id: new ObjectId("68ca8d133bded4a5d888bb45"), timezone: "Europe/Istanbul" }; + const date = new Date("2025-09-29T12:47:19.247Z"); + const widgetId = "68ca8d133bded4a5d888bb4a"; let monthlyData = await getRatingResponsesByDate(app, widgetId, date, "monthly", [1, 2, 3, 4, 5]); let dailyData = await getRatingResponsesByDate(app, widgetId, date, "daily"); console.log(monthlyData, dailyData); diff --git a/plugins/alerts/frontend/public/javascripts/countly.models.js b/plugins/alerts/frontend/public/javascripts/countly.models.js index 5e8d3d112c4..e27d7b05a7a 100644 --- a/plugins/alerts/frontend/public/javascripts/countly.models.js +++ b/plugins/alerts/frontend/public/javascripts/countly.models.js @@ -569,7 +569,7 @@ compareValue: list[j].users, compareValue2: list[j].minutes, alertValues: list[j].email, - createdByUser: "-", + createdByUser: list[j].createdByUser || '-', _canUpdate: countlyAuth.validateUpdate( FEATURE_NAME, countlyGlobal.member, diff --git a/plugins/alerts/frontend/public/javascripts/countly.views.js b/plugins/alerts/frontend/public/javascripts/countly.views.js index 0e390d62eaa..8640fe7b49b 100644 --- a/plugins/alerts/frontend/public/javascripts/countly.views.js +++ b/plugins/alerts/frontend/public/javascripts/countly.views.js @@ -50,22 +50,22 @@ defaultAlertDefine: { events: { target: [ - { value: "count", label: "count" }, - { value: "sum", label: "sum" }, - { value: "duration", label: "duration" }, - { value: "average sum", label: "average sum" }, + { value: "count", label: jQuery.i18n.map["alert.count"] || "count" }, + { value: "sum", label: jQuery.i18n.map["alert.sum"] || "sum" }, + { value: "duration", label: jQuery.i18n.map["alert.duration"] || "duration" }, + { value: "average sum", label: jQuery.i18n.map["alert.average-sum"] || "average sum" }, { value: "average duration", - label: "average duration", + label: jQuery.i18n.map["alert.average-duration"] || "average duration", }, ], }, views: { target: [ - { value: "bounce rate", label: "bounce rate" }, + { value: "bounce rate", label: jQuery.i18n.map["alert.bounce-rate"] || "bounce rate" }, { value: "# of page views", - label: "# of page views", + label: jQuery.i18n.map["alert.page-views"] || "# of page views", }, ], }, @@ -73,17 +73,17 @@ target: [ { value: "average session duration", - label: "average session duration", + label: jQuery.i18n.map["alert.average-session-duration"] || "average session duration", }, - { value: "# of sessions", label: "# of sessions" }, + { value: "# of sessions", label: jQuery.i18n.map["alert.sessions-count"] || "# of sessions" }, ], }, users: { target: [ - { value: "# of users", label: "# of users" }, + { value: "# of users", label: jQuery.i18n.map["alert.users-count"] || "# of users" }, { value: "# of new users", - label: "# of new users", + label: jQuery.i18n.map["alert.new-users-count"] || "# of new users", }, ], }, @@ -91,19 +91,19 @@ target: [ { value: "# of crashes/errors", - label: "# of crashes/errors", + label: jQuery.i18n.map["alert.crashes-count"] || "# of crashes/errors", }, { value: "non-fatal crashes/errors per session", - label: "non-fatal crashes/errors per session", + label: jQuery.i18n.map["alert.non-fatal-crashes"] || "non-fatal crashes/errors per session", }, { value: "fatal crashes/errors per session", - label: "fatal crashes/errors per session", + label: jQuery.i18n.map["alert.fatal-crashes"] || "fatal crashes/errors per session", }, { value: "new crash/error", - label: "new crash/error", + label: jQuery.i18n.map["alert.new-crash"] || "new crash/error", }, ], }, @@ -111,11 +111,11 @@ target: [ { value: "# of survey responses", - label: "# of survey responses", + label: jQuery.i18n.map["alert.survey-responses-count"] || "# of survey responses", }, { value: "new survey response", - label: "new survey response", + label: jQuery.i18n.map["alert.new-survey-response"] || "new survey response", }, ], }, @@ -123,11 +123,11 @@ target: [ { value: "# of responses", - label: "# of responses", + label: jQuery.i18n.map["alert.nps-responses-count"] || "# of responses", }, { value: "new NPS response", - label: "new NPS response", + label: jQuery.i18n.map["alert.new-nps-response"] || "new NPS response", }, ], }, @@ -135,11 +135,11 @@ target: [ { value: "# of responses", - label: "# of responses", + label: jQuery.i18n.map["alert.rating-responses-count"] || "# of responses", }, { value: "new rating response", - label: "new rating response", + label: jQuery.i18n.map["alert.new-rating-response"] || "new rating response", }, ], }, @@ -147,7 +147,7 @@ target: [ { value: "total data points", - label: "total data points", + label: jQuery.i18n.map["alert.total-data-points"] || "total data points", }, ], }, @@ -155,20 +155,20 @@ target: [ { value: "t", - label: "# of online users", + label: jQuery.i18n.map["alert.online-users-count"] || "# of online users", }, { value: "o", - label: "overall record", + label: jQuery.i18n.map["alert.overall-record"] || "overall record", }, - { value: "m", label: "30-day record" }, + { value: "m", label: jQuery.i18n.map["alert.30day-record"] || "30-day record" }, ], }, cohorts: { target: [ { value: "# of users in the cohort", - label: "# of users in the cohort", + label: jQuery.i18n.map["alert.cohort-users-count"] || "# of users in the cohort", }, ], }, @@ -176,24 +176,24 @@ target: [ { value: "# of users in the profile group", - label: "# of users in the profile group", + label: jQuery.i18n.map["alert.profile-group-users-count"] || "# of users in the profile group", }, ], }, revenue: { target: [ - { value: "total revenue", label: "total revenue" }, + { value: "total revenue", label: jQuery.i18n.map["alert.total-revenue"] || "total revenue" }, { value: "average revenue per user", - label: "average revenue per user", + label: jQuery.i18n.map["alert.average-revenue-per-user"] || "average revenue per user", }, { value: "average revenue per paying user", - label: "average revenue per paying user", + label: jQuery.i18n.map["alert.average-revenue-per-paying-user"] || "average revenue per paying user", }, { value: "# of paying users", - label: "# of paying users", + label: jQuery.i18n.map["alert.paying-users-count"] || "# of paying users", }, ], }, diff --git a/plugins/alerts/frontend/public/localization/alerts.properties b/plugins/alerts/frontend/public/localization/alerts.properties index 427a0bd335c..5d4e1f400c2 100644 --- a/plugins/alerts/frontend/public/localization/alerts.properties +++ b/plugins/alerts/frontend/public/localization/alerts.properties @@ -111,3 +111,36 @@ alerts.crashes-icon = Alert will be triggered as soon as a new, before unseen cr alerts.status-all = All alerts alerts.status-enabled = Enabled alerts.status-disabled = Disabled + +# Alert target types +alert.count = count +alert.sum = sum +alert.duration = duration +alert.average-sum = average sum +alert.average-duration = average duration +alert.bounce-rate = bounce rate +alert.page-views = # of page views +alert.average-session-duration = average session duration +alert.sessions-count = # of sessions +alert.users-count = # of users +alert.new-users-count = # of new users +alert.crashes-count = # of crashes/errors +alert.non-fatal-crashes = non-fatal crashes/errors per session +alert.fatal-crashes = fatal crashes/errors per session +alert.new-crash = new crash/error +alert.survey-responses-count = # of survey responses +alert.new-survey-response = new survey response +alert.nps-responses-count = # of responses +alert.new-nps-response = new NPS response +alert.rating-responses-count = # of responses +alert.new-rating-response = new rating response +alert.total-data-points = total data points +alert.online-users-count = # of online users +alert.overall-record = overall record +alert.30day-record = 30-day record +alert.cohort-users-count = # of users in the cohort +alert.profile-group-users-count = # of users in the profile group +alert.total-revenue = total revenue +alert.average-revenue-per-user = average revenue per user +alert.average-revenue-per-paying-user = average revenue per paying user +alert.paying-users-count = # of paying users diff --git a/plugins/alerts/frontend/public/localization/alerts_fr.properties b/plugins/alerts/frontend/public/localization/alerts_fr.properties index f1c1ac15795..61d947204ca 100644 --- a/plugins/alerts/frontend/public/localization/alerts_fr.properties +++ b/plugins/alerts/frontend/public/localization/alerts_fr.properties @@ -1,83 +1,146 @@ #empty -alert.plugin-title = Alerts -#empty.plugin-description = Use it to start developing new plugin +alert.plugin-title = Alertes +#empty.plugin-description = Utilisez-le pour commencer à développer un nouveau plugin -alert.Add_New_Alert=Add New Alert -alert.Edit_Your_Alert=Edit Your Alert -alert.Alert_Name=Alert Name -alert.Data_type=Data Type -alert.Metric=Metric -alert.Event=Event -alert.Crash=Crash -alert.For_Application=For Application -alert.all-applications=All Applications -alert.Select_app=Select Application -alert.Select_apps=Select Applications -alert.Event=Event -alert.Select_event=Select Event -alert.Alert_type=Alert type -alert.Select_a_type=Select a Type -alert.Alert_Me_When=Alert Me When -alert.Action=Action -alert.e-mail=e-mail -alert.HTTP_request=HTTP request -alert.E-mail_address=E-mail address(One email per line) -alert.Http_Request_URL=Http Request URL -alert.Create_New_Alert=Add New Alert -alert.RUNNING_ALERTS=Active Alerts -alert.TOTAL_ALERTS_SENT=TOTAL ALERTS SENT -alert.Today_ALERT_SENT=Today ALERT SENT -alert.Edit=Edit -alert.Delete=Delete -alert.Save_Changes=Save Changes -alert.Discard_Changes=Discard Changes -alert.Data_type=Data Type -alert.Metric=Metric -alert.Event=Event -alert.Event=Event -alert.For_Applications=For Application -alert.Select_metric=Select Metric -alert.Define_variation=Define variation -alert.Select_event=Select Event -alert.Define_variation=Define variation -alert.Select_metric=Select Metric -alert.Define_variation=Define variation -alert.TOTAL_ALERTS_SENT=TOTAL ALERTS SENT -alert.ALERTS_SENT_TODAY=Alerts Sent Today -alert.Alert_definition=ALERT DEFINITION -alert.Users_to_send_alerts=Users to send alerts -alert.Select_users=Select Users -alert.You_will_be_notified_via_email=You will be notified via email -alert.delete-confirm-title = Delete Alert -alert.delete-confirm = Confirm to delete this alert? -alert.yes-delete-alert = Yes, delete alert -alert.email-place-holder=Enter emails -alert.enter-alert-name = Enter Alert Name +alert.Add_New_Alert = Ajouter une Nouvelle Alerte +alert.Edit_Your_Alert = Modifier Votre Alerte +alert.Alert_Name = Nom de l'Alerte +alert.Event = Événements +alert.View = Vues +alert.Session = Sessions +alert.User = Utilisateurs +alert.Crash = Crashes +alert.Survey = Sondage +alert.NPS = NPS +alert.Rating = Notation +alert.Data-points = Points de Données +alert.Online-users = Utilisateurs en Ligne +alert.Cohorts = Cohortes +alert.Profile-groups = Groupes de Profil +alert.Revenue = Revenus +alert.Data_type = Type de Données +alert.For_Application = Pour l'Application +alert.all-applications = Toutes les Applications +alert.Select_app = Sélectionner l'Application +alert.Select_apps = Sélectionner les Applications +alert.Select_event = Sélectionner l'Événement +alert.Alert_type = Type d'Alerte +alert.Select_a_type = Sélectionner un Type +alert.Alert_Me_When = M'Alerter Quand +alert.Action = Action +alert.e-mail = Email +alert.HTTP_request = Requête HTTP +alert.E-mail_address = Adresse Email (Un email par ligne) +alert.Http_Request_URL = URL de Requête HTTP +alert.Create_New_Alert = Créer une Nouvelle Alerte +alert.RUNNING_ALERTS = Alertes Actives +alert.TOTAL_ALERTS_SENT = TOTAL DES ALERTES ENVOYÉES +alert.Today_ALERT_SENT = Alertes Envoyées Aujourd'hui +alert.Edit = Modifier +alert.Delete = Supprimer +alert.Save_Changes = Sauvegarder les Changements +alert.Discard_Changes = Abandonner les Changements +alert.For_Applications = Application +alert.Select_metric = Sélectionner la Métrique +alert.Define_variation = Définir la variation +alert.Select_event = Sélectionner l'Événement +alert.Define_variation = Définir la variation +alert.Select_metric = Sélectionner la Métrique +alert.Define_variation = Définir la variation +alert.TOTAL_ALERTS_SENT = Total des Alertes Envoyées +alert.ALERTS_SENT_TODAY = Alertes Envoyées Aujourd'hui +alert.Alert_Trigger = Déclencheur +alert.Users_to_send_alerts = Utilisateurs pour envoyer les alertes +alert.Select_users = Sélectionner les Utilisateurs +alert.You_will_be_notified_via_email = Vous serez notifié par email +alert.delete-confirm-title = Supprimer l'Alerte +alert.delete-confirm = Confirmer la suppression de cette alerte ? +alert.yes-delete-alert = Oui, supprimer l'alerte +alert.email-place-holder = Entrer les Emails +alert.enter-alert-name = Entrer le Nom de l'Alerte alert.Application = Application alert.Condition = Condition -alert.CreateBy = Created by -alert.compare-remind = Metrics are compared on a daily basis. -alert.add-number = Add Number -alert.make-changes-remind = You made {0} changes. -alert.make-change-remind = You made 1 change. -alert.rating = Rating -alert.starView-disabled-title = Ratings plugin is disabled! -alert.starView-disabled-desc = You will not receive data alerts from Ratings. -alert.starView-disabled-suggest = Please enable Ratings plugin to receive alerts. -alert.crashesView-disabled-title = Crashes plugin is disabled! -alert.crashesView-disabled-desc = You will not receive data alerts from Crashes. -alert.crashesView-disabled-suggest = Please enable Crashes plugin to receive alerts. -alert.data-point = Data point -alert.email-to-receive = E-mail to receive alerts -alert.select-metric=Select Metric -alert.define-variable=Define Variation -alert.add-number=Add Number -alert.add-alert=Add Alert -alert.save-alert=Save Alert -alert.tips = Overview of all alerts set up. Create new alerts to receive emails when
specific conditions related to metrics are met. -alerts.update-status-success = Successfully update status -alerts.save-alert-success= Alert saved -alerts.empty-title= ...hmm, seems empty here -alerts.empty-action-button-title=+ Create New Alert -alerts.empty-subtitle= Create new alerts to receive emails when specific conditions related to metrics are met. +alert.CreateBy = Créé Par +alert.compare-remind = Les métriques sont comparées quotidiennement. +alert.add-number = Ajouter un Nombre +alert.make-changes-remind = Vous avez effectué {0} changements. +alert.make-change-remind = Vous avez effectué 1 changement. +alert.starView-disabled-title = Le plugin Notations est désactivé ! +alert.starView-disabled-desc = Vous ne recevrez pas d'alertes de données des Notations. +alert.starView-disabled-suggest = Veuillez activer le plugin Notations pour recevoir des alertes. +alert.crashesView-disabled-title = Le plugin Crashes est désactivé ! +alert.crashesView-disabled-desc = Vous ne recevrez pas d'alertes de données des Crashes. +alert.crashesView-disabled-suggest = Veuillez activer le plugin Crashes pour recevoir des alertes. +alert.email-to-receive = Email pour recevoir les alertes +alert.data-point = Point de données +alert.select-metric = Sélectionner la Métrique +alert.define-variable = Définir la Variation +alert.add-number = Ajouter un Nombre +alert.add-alert = Ajouter une Alerte +alert.save-alert = Sauvegarder l'Alerte +alert.save = Créer +alert.tips = Aperçu de toutes les alertes configurées. Créez de nouvelles alertes pour recevoir des emails quand des conditions spécifiques liées aux métriques sont remplies. +alerts.application-tooltip = Points de données est le seul type de données disponible pour votre Application sélectionnée. +alerts.update-status-success = Statut mis à jour avec succès +alerts.save-alert-success = Alerte sauvegardée +alerts.empty-title = ...hmm, ça semble vide ici +alerts.empty-action-button-title = + Créer une Nouvelle Alerte +alerts.empty-subtitle = Créez de nouvelles alertes pour recevoir des emails quand des conditions spécifiques liées aux métriques sont remplies. +alerts.alert-is-enabled = L'Alerte est Activée +alerts.alert-is-disabled = L'Alerte est Désactivée +alerts.select-data-type = Sélectionner un type de données +alerts.select-an-application = Sélectionner une Application +alerts.filter = Filtre +alerts.add-filter = + Ajouter un Filtre +alerts.segment-value = Chaîne +alerts.widget-name = Nom du Widget +alert.email-to-specific-address = À une adresse spécifique +alert.email-to-group = Aux utilisateurs d'un groupe +alerts.select-group = Entrer le Nom du Groupe d'Utilisateurs +alerts.email-icon1 = Cette alerte ne peut être utilisée que dans +alerts.email-icon2 = pour déclencher des actions. +alerts.period-select-reminder-hourly = La valeur sera vérifiée toutes les heures +alerts.period-select-reminder-daily = La valeur sera vérifiée à minuit dans le fuseau horaire de l'app +alert.email-to-dont-send = Ne pas envoyer pour cette alerte +alerts.select-event = Sélectionner un Événement +alert.email-header = Notification par Email +alerts.email-icon = Cette alerte ne peut être utilisée que dans Hooks pour déclencher des actions. +alerts.common-icon-info = L'alerte sera déclenchée dès qu'une nouvelle réponse est soumise pour le widget sélectionné. +alerts.crashes-icon = L'alerte sera déclenchée dès qu'un nouveau crash jamais vu auparavant se produit. +alerts.status-all = Toutes les alertes +alerts.status-enabled = Activé +alerts.status-disabled = Désactivé + +# Types de cibles d'alerte +alert.count = compte +alert.sum = somme +alert.duration = durée +alert.average-sum = somme moyenne +alert.average-duration = durée moyenne +alert.bounce-rate = taux de rebond +alert.page-views = nombre de vues de page +alert.average-session-duration = durée moyenne de session +alert.sessions-count = nombre de sessions +alert.users-count = nombre d'utilisateurs +alert.new-users-count = nombre de nouveaux utilisateurs +alert.crashes-count = nombre de crashes/erreurs +alert.non-fatal-crashes = crashes/erreurs non fatales par session +alert.fatal-crashes = crashes/erreurs fatales par session +alert.new-crash = nouveau crash/erreur +alert.survey-responses-count = nombre de réponses de sondage +alert.new-survey-response = nouvelle réponse de sondage +alert.nps-responses-count = nombre de réponses +alert.new-nps-response = nouvelle réponse NPS +alert.rating-responses-count = nombre de réponses +alert.new-rating-response = nouvelle réponse de notation +alert.total-data-points = points de données totaux +alert.online-users-count = nombre d'utilisateurs en ligne +alert.overall-record = record global +alert.30day-record = record de 30 jours +alert.cohort-users-count = nombre d'utilisateurs dans la cohorte +alert.profile-group-users-count = nombre d'utilisateurs dans le groupe de profil +alert.total-revenue = revenus totaux +alert.average-revenue-per-user = revenus moyens par utilisateur +alert.average-revenue-per-paying-user = revenus moyens par utilisateur payant +alert.paying-users-count = nombre d'utilisateurs payants diff --git a/plugins/browser/frontend/public/localization/browser_fr.properties b/plugins/browser/frontend/public/localization/browser_fr.properties index 498a7ba4e7b..70c410ea0c1 100644 --- a/plugins/browser/frontend/public/localization/browser_fr.properties +++ b/plugins/browser/frontend/public/localization/browser_fr.properties @@ -1,11 +1,12 @@ -#browser -browser.title = Browsers -browser.description = Enable gathering and displaying browser related metrics -browser.page-desc = Details of the browsers from which your visitors access your application, in the selected time period. -browser.table.browser = Browser -browser.table.browser-version = Browser versions for -help.browser.chart = Pie chart on the left shows total visitors from each browser. Pie chart on the right shows new visitors from each browser. The table below shows a breakdown of this data for each browser. -browser.browser-for = Browsers for -browser.version-distribution = Browsers Version Distribution + +#navigateur +browser.title = Navigateurs +browser.description = Activer la collecte et l'affichage des métriques liées aux navigateurs +browser.page-desc = Détails des navigateurs utilisés par vos visiteurs pour accéder à votre application, sur la période sélectionnée. +browser.table.browser = Navigateur +browser.table.browser-version = Versions du navigateur pour +help.browser.chart = Le graphique circulaire de gauche montre le nombre total de visiteurs par navigateur. Celui de droite montre les nouveaux visiteurs par navigateur. Le tableau ci-dessous présente une répartition de ces données pour chaque navigateur. +browser.browser-for = Navigateurs pour +browser.version-distribution = Répartition des versions de navigateurs browser.versions = Versions -browser.drill-data = Drill Data +browser.drill-data = Données détaillées \ No newline at end of file diff --git a/plugins/compare/frontend/public/localization/compare_fr.properties b/plugins/compare/frontend/public/localization/compare_fr.properties index 6f430701a50..d6da112953e 100644 --- a/plugins/compare/frontend/public/localization/compare_fr.properties +++ b/plugins/compare/frontend/public/localization/compare_fr.properties @@ -1,47 +1,48 @@ -# e.g. select maximum 10 events to compare -compare.plugin-title = Compare -compare.plugin-description = Extensible plugin to compare multiple apps and custom events -compare.limit = Select maximum {0} {1} to compare -compare.button = Compare -compare.back = Back -compare.results.by = Results by -compare.events.title = COMPARE EVENTS -compare.events.back = BACK TO EVENTS -compare.events.back-dashboard = BACK TO OVERVIEW -compare.events.table = EVENT -compare.events.limit = events -compare.apps.title = Compare Apps -compare.apps.app-select = Compare Apps +# par exemple, sélectionnez au maximum 10 événements à comparer +compare.plugin-title = Comparer +compare.plugin-description = Plugin extensible pour comparer plusieurs applications et événements personnalisés. +compare.limit = Sélectionnez au maximum {0} {1} à comparer +compare.button = Comparer +compare.back = Retour +compare.results.by = Résultats par +compare.events.title = COMPARER LES ÉVÉNEMENTS +compare.events.back = RETOUR AUX ÉVÉNEMENTS +compare.events.back-dashboard = RETOUR À LA VUE D'ENSEMBLE +compare.events.table = ÉVÉNEMENT +compare.events.limit = événements + +compare.apps.title = Comparer les applications +compare.apps.app-select = Comparer les applications compare.apps.table = APPLICATION compare.apps.limit = applications -compare.apps.app-name = App Name -compare.apps.total-unique = Total Visitors/Users -compare.apps.new-unique = New Visitors/Users -compare.apps.all-apps = All apps -compare.apps.maximum.placeholder = Select maximum 20 applications to compare -compare.apps.results.by.total.sessions = Total Sessions -compare.apps.results.by.total.visitors = Total Visitors / Users -compare.apps.results.by.new.visitors = New Visitors / Users -compare.apps.results.by.time.spent = Time Spent -compare.apps.results.by.avg.session.duration = Avg. Session Duration -compare.apps.table.name = APP Name -compare.apps.table.total.sessions = TOTAL SESSIONS -compare.apps.table.total.users = TOTAL VISITORS/USERS -compare.apps.table.new.users = NEW VISITORS/USERS -compare.apps.table.time.spent = TIME SPENT -compare.apps.table.avg.session.duration = AVG. SESSION DURATION -compare.apps.metric = METRIC -compare.events.title = COMPARE EVENTS -compare.events = Compare -compare.events.event = EVENT -compare.events.count = COUNT -compare.events.sum = SUM -compare.events.duration = DURATION -compare.events.avg-duration = AVG. DURATION -compare.events.results.by.count = Count -compare.events.results.by.sum = Sum -compare.events.results.by.duration = Duration -compare.events.results.by.avg.duration = Avg. Duration -compare.events.previous.period = Previous Period -compare.events.metric = METRIC +compare.apps.app-name = Nom de l'application +compare.apps.total-unique = Visiteurs/Utilisateurs totaux +compare.apps.new-unique = Nouveaux visiteurs/utilisateurs +compare.apps.all-apps = Toutes les applications +compare.apps.maximum.placeholder = Sélectionnez au maximum 20 applications à comparer +compare.apps.results.by.total.sessions = Sessions totales +compare.apps.results.by.total.visitors = Visiteurs/Utilisateurs totaux +compare.apps.results.by.new.visitors = Nouveaux visiteurs/utilisateurs +compare.apps.results.by.time.spent = Temps passé +compare.apps.results.by.avg.session.duration = Durée moyenne de session +compare.apps.table.name = Nom de l'application +compare.apps.table.total.sessions = SESSIONS TOTALES +compare.apps.table.total.users = VISITEURS/UTILISATEURS TOTAUX +compare.apps.table.new.users = NOUVEAUX VISITEURS/UTILISATEURS +compare.apps.table.time.spent = TEMPS PASSÉ +compare.apps.table.avg.session.duration = DURÉE MOYENNE DE SESSION +compare.apps.metric = MÉTRIQUE +compare.events.title = Comparer les événements +compare.events = Comparer +compare.events.event = ÉVÉNEMENT +compare.events.count = NOMBRE +compare.events.sum = SOMME +compare.events.duration = DURÉE +compare.events.avg-duration = DURÉE MOYENNE +compare.events.results.by.count = Nombre +compare.events.results.by.sum = Somme +compare.events.results.by.duration = Durée +compare.events.results.by.avg.duration = Durée moyenne +compare.events.previous.period = Période précédente +compare.events.metric = MÉTRIQUE \ No newline at end of file diff --git a/plugins/compliance-hub/api/api.js b/plugins/compliance-hub/api/api.js index e3a2f39957a..3b5ef533c6a 100644 --- a/plugins/compliance-hub/api/api.js +++ b/plugins/compliance-hub/api/api.js @@ -16,6 +16,13 @@ const FEATURE_NAME = 'compliance_hub'; plugins.internalDrillEvents.push("[CLY]_consent"); + plugins.register("/master", function() { + common.db.collection('consent_history').ensureIndex({app_id: 1, device_id: 1}, function() {}); + common.db.collection('consent_history').ensureIndex({app_id: 1, uid: 1}, function() {}); + common.db.collection('consent_history').ensureIndex({app_id: 1, type: 1}, function() {}); + common.db.collection('consent_history').ensureIndex({app_id: 1, ts: 1}, function() {}); + }); + //write api call plugins.register("/sdk/user_properties", function(ob) { var params = ob.params; @@ -142,7 +149,7 @@ const FEATURE_NAME = 'compliance_hub'; } } common.db.collection("app_users" + params.qstring.app_id).findOne(query, function(err, res) { - common.returnOutput(params, res.consent || {}); + common.returnOutput(params, res?.consent || {}); }); }); break; @@ -162,7 +169,8 @@ const FEATURE_NAME = 'compliance_hub'; query = {}; } } - common.db.collection("consent_history").count(query, function(err, total) { + query.app_id = params.app_id.toString(); + common.db.collection("consent_history").countDocuments(query, function(err, total) { if (err) { common.returnMessage(params, 400, err); } @@ -338,7 +346,7 @@ const FEATURE_NAME = 'compliance_hub'; var newUid = ob.newUser.uid; if (oldUid !== newUid) { return new Promise(function(resolve, reject) { - common.db.collection('consent_history').update({uid: oldUid}, {'$set': {uid: newUid}}, {multi: true}, function(err) { + common.db.collection('consent_history').update({uid: oldUid}, {'$set': {app_id: ob.app_id, uid: newUid}}, {multi: true}, function(err) { if (err) { reject(err); return; @@ -364,7 +372,7 @@ const FEATURE_NAME = 'compliance_hub'; plugins.register("/i/apps/delete", function(ob) { var appId = ob.appId; common.db.collection('consents').remove({'_id': {$regex: appId + ".*"}}, function() {}); - common.db.collection('consent_history').drop(function() {}); + common.db.collection('consent_history').deleteMany({app_id: appId}, function() {}); if (common.drillDb) { common.drillDb.collection("drill_events" + crypto.createHash('sha1').update("[CLY]_consent" + appId).digest('hex')).drop(function() {}); } @@ -373,7 +381,7 @@ const FEATURE_NAME = 'compliance_hub'; plugins.register("/i/apps/reset", function(ob) { var appId = ob.appId; common.db.collection('consents').remove({'_id': {$regex: appId + ".*"}}, function() {}); - common.db.collection('consent_history').drop(function() {}); + common.db.collection('consent_history').deleteMany({app_id: appId}, function() {}); if (common.drillDb) { common.drillDb.collection("drill_events" + crypto.createHash('sha1').update("[CLY]_consent" + appId).digest('hex')).drop(function() {}); } @@ -382,6 +390,7 @@ const FEATURE_NAME = 'compliance_hub'; plugins.register("/i/apps/clear_all", function(ob) { var appId = ob.appId; common.db.collection('consents').remove({'_id': {$regex: appId + ".*"}}, function() {}); + common.db.collection('consent_history').deleteMany({app_id: appId}, function() {}); if (common.drillDb) { common.drillDb.collection("drill_events" + crypto.createHash('sha1').update("[CLY]_consent" + appId).digest('hex')).drop(function() {}); } @@ -391,6 +400,7 @@ const FEATURE_NAME = 'compliance_hub'; var appId = ob.appId; var ids = ob.ids; common.db.collection('consents').remove({$and: [{'_id': {$regex: appId + ".*"}}, {'_id': {$nin: ids}}]}, function() {}); + common.db.collection('consent_history').deleteMany({app_id: appId, ts: {$lt: ob.moment.valueOf()}}, function() {}); if (common.drillDb) { common.drillDb.collection("drill_events" + crypto.createHash('sha1').update("[CLY]_consent" + appId).digest('hex')).remove({ts: {$lt: ob.moment.valueOf()}}, function() {}); } diff --git a/plugins/compliance-hub/frontend/public/localization/compliance-hub_fr.properties b/plugins/compliance-hub/frontend/public/localization/compliance-hub_fr.properties index 1964a6cceab..fd32d135b38 100644 --- a/plugins/compliance-hub/frontend/public/localization/compliance-hub_fr.properties +++ b/plugins/compliance-hub/frontend/public/localization/compliance-hub_fr.properties @@ -1,41 +1,42 @@ -#consent -compliance_hub.title = Compliance Hub -compliance-hub.plugin-title = Data Compliance Hub -compliance-hub.description = Providing and aggregating features in compliance with GDPR -compliance_hub.Export-finished = App User export finished -compliance_hub.App-user-deleted = App User deleted -compliance_hub.Export-file-deleted = App User export file deleted + +#consentement +compliance_hub.title = Centre de conformité +compliance-hub.plugin-title = Centre de conformité des données +compliance-hub.description = Fournit et regroupe des fonctionnalités conformes au RGPD. +compliance_hub.Export-finished = Exportation de l'utilisateur de l'application terminée. +compliance_hub.App-user-deleted = Utilisateur de l'application supprimé. +compliance_hub.Export-file-deleted = Fichier d'exportation de l'utilisateur de l'application supprimé. compliance_hub.Sessions = Sessions -compliance_hub.Events = Events -compliance_hub.Views = Views -compliance_hub.Scrolls = Scrolls -compliance_hub.Clicks = Clicks -compliance_hub.Forms = Forms -compliance_hub.Crashes = Crashes -compliance_hub.Push = Push +compliance_hub.Events = Événements +compliance_hub.Views = Vues +compliance_hub.Scrolls = Défilements +compliance_hub.Clicks = Clics +compliance_hub.Forms = Formulaires +compliance_hub.Crashes = Crashs +compliance_hub.Push = Notifications push compliance_hub.Attribution = Attribution -compliance_hub.Users = Users -compliance_hub.Star-rating = Star-rating -consent.title = Consents -consent.changes = Changes -consent.opt-i = Opt in -consent.opt-o = Opt out -consent.opt-in = Opted in for {0} feature(s) -consent.opt-out = Opted out from {0} feature(s) -consent.metrics = Metrics -consent.data-compliance = Data compliance metrics -consent.history = Consent history -consent.history-for = Consent history for {0} -consent.go-history = Go to consent history -consent.export-history = Export/Purge history -consent.search-device-id = Search for Device ID -consent.userdata-exports = User data exports -consent.userdata-purges = User data purges -consent.type = Consent type -consent.feature = Feature type -consent.metrics-desc = View consent requests (opt-ins or opt-outs) in a time series graph -consent.users-desc = View list of users and their consent states -consent.history-desc = View a history of consent changes -consent.exports-desc = View all export and purge actions previously executed -userdata.consents = User's consent history -internal-events.[CLY]_consent = Consent +compliance_hub.Users = Utilisateurs +compliance_hub.Star-rating = Évaluation par étoiles +consent.title = Consentements +consent.changes = Modifications +consent.opt-i = Accepter +consent.opt-o = Refuser +consent.opt-in = Accepté pour {0} fonctionnalité(s) +consent.opt-out = Refusé pour {0} fonctionnalité(s) +consent.metrics = Métriques +consent.data-compliance = Métriques de conformité des données +consent.history = Historique des consentements +consent.history-for = Historique des consentements pour {0} +consent.go-history = Aller à l'historique des consentements +consent.export-history = Historique des exportations/purges +consent.search-device-id = Rechercher l'ID de l'appareil +consent.userdata-exports = Exportations des données utilisateur. +consent.userdata-purges = Purges des données utilisateur. +consent.type = Type de consentement +consent.feature = Type de fonctionnalité +consent.metrics-desc = Voir les demandes de consentement (acceptations ou refus) dans un graphique chronologique. +consent.users-desc = Voir la liste des utilisateurs et leurs états de consentement +consent.history-desc = Voir l'historique des modifications de consentement +consent.exports-desc = Voir toutes les actions d'exportation et de purge précédemment exécutées +userdata.consents = Historique de consentement de l'utilisateur +internal-events.[CLY]_consent = Consentement \ No newline at end of file diff --git a/plugins/compliance-hub/install.js b/plugins/compliance-hub/install.js index 9fbea495bed..e69de29bb2d 100644 --- a/plugins/compliance-hub/install.js +++ b/plugins/compliance-hub/install.js @@ -1,32 +0,0 @@ -var pluginManager = require('../pluginManager.js'), - async = require('async'); - -console.log("Installing compliance-hub plugin"); -pluginManager.dbConnection().then((countlyDb) => { - countlyDb.collection('apps').find({}).toArray(function(err, apps) { - - if (!apps || err) { - console.log("No apps to upgrade"); - countlyDb.close(); - return; - } - function upgrade(app, done) { - console.log("Adding compliance-hub indexes to " + app.name); - var cnt = 0; - function cb() { - cnt++; - if (cnt == 4) { - done(); - } - } - countlyDb.collection('consent_history').ensureIndex({device_id: 1}, cb); - countlyDb.collection('consent_history').ensureIndex({uid: 1}, cb); - countlyDb.collection('consent_history').ensureIndex({type: 1}, cb); - countlyDb.collection('consent_history').ensureIndex({ts: 1}, cb); - } - async.forEach(apps, upgrade, function() { - console.log("Compliance hub plugin installation finished"); - countlyDb.close(); - }); - }); -}); \ No newline at end of file diff --git a/plugins/consolidate/frontend/public/localization/consolidate_fr.properties b/plugins/consolidate/frontend/public/localization/consolidate_fr.properties index 37c2745e408..81d6b18c6f8 100644 --- a/plugins/consolidate/frontend/public/localization/consolidate_fr.properties +++ b/plugins/consolidate/frontend/public/localization/consolidate_fr.properties @@ -1,7 +1,7 @@ -#consolidate -consolidate.plugin-title = Consolidate -consolidate.plugin-description = Duplicate data from multiple apps into a single app -consolidate.app = Select apps for consolidation -configs.help.consolidate-app = Select applications from which data will be combined into this application. -configs.help.consolidate-select-apps = Please select the applications from list +#consolidation +consolidate.plugin-title = Consolider +consolidate.plugin-description = Dupliquer les données de plusieurs applications dans une seule application +consolidate.app = Sélectionner les applications à consolider +configs.help.consolidate-app = Sélectionnez les applications dont les données seront combinées dans cette application. +configs.help.consolidate-select-apps = Veuillez sélectionner les applications dans la liste \ No newline at end of file diff --git a/plugins/crashes/api/api.js b/plugins/crashes/api/api.js index c898241672c..a8174c0af5d 100644 --- a/plugins/crashes/api/api.js +++ b/plugins/crashes/api/api.js @@ -1487,7 +1487,7 @@ plugins.setConfigs("crashes", { var crashes = params.qstring.args.crashes || [params.qstring.args.crash_id]; common.db.collection('app_crashgroups' + params.qstring.app_id).update({'_id': {$in: crashes} }, {"$set": {is_resolving: true}}, {multi: true}, function() { for (var i = 0; i < crashes.length; i++) { - plugins.dispatch("/systemlogs", {params: params, action: "crash_shown", data: {app_id: params.qstring.app_id, crash_id: params.qstring.args.crash_id}}); + plugins.dispatch("/systemlogs", {params: params, action: "crash_resolving", data: {app_id: params.qstring.app_id, crash_id: params.qstring.args.crash_id}}); } common.returnMessage(params, 200, 'Success'); return true; diff --git a/plugins/crashes/frontend/public/javascripts/countly.views.js b/plugins/crashes/frontend/public/javascripts/countly.views.js index dcfded97912..40f96fb4a3e 100644 --- a/plugins/crashes/frontend/public/javascripts/countly.views.js +++ b/plugins/crashes/frontend/public/javascripts/countly.views.js @@ -47,31 +47,31 @@ filterProperties.push( new countlyQueryBuilder.Property({ id: "nonfatal", - name: "Fatality", + name: jQuery.i18n.prop("crashes.fatality-label") || "Fatality", type: countlyQueryBuilder.PropertyType.LIST, group: "Main", getValueList: function() { return [ - {name: "Fatal", value: false}, - {name: "Non-fatal", value: true} + {name: jQuery.i18n.prop("crashes.fatal") || "Fatal", value: false}, + {name: jQuery.i18n.prop("crashes.non-fatal") || "Non-fatal", value: true} ]; } }), new countlyQueryBuilder.Property({ id: "is_hidden", - name: "Visibility", + name: jQuery.i18n.prop("crashes.visibility") || "Visibility", type: countlyQueryBuilder.PropertyType.LIST, group: "Main", getValueList: function() { return [ - {name: "Hidden", value: true}, - {name: "Shown", value: false} + {name: jQuery.i18n.prop("crashes.hidden") || "Hidden", value: true}, + {name: jQuery.i18n.prop("crashes.shown") || "Shown", value: false} ]; } }), new countlyQueryBuilder.Property({ id: "is_new", - name: "Viewed", + name: jQuery.i18n.prop("crashes.viewed") || "Viewed", type: countlyQueryBuilder.PropertyType.LIST, group: "Main", getValueList: function() { @@ -83,7 +83,7 @@ }), new countlyQueryBuilder.Property({ id: "is_renewed", - name: "Reoccured", + name: jQuery.i18n.prop("crashes.reoccurred") || "Reoccurred", type: countlyQueryBuilder.PropertyType.LIST, group: "Main", getValueList: function() { @@ -95,7 +95,7 @@ }), new countlyQueryBuilder.Property({ id: "is_resolved", - name: "Resolved", + name: jQuery.i18n.prop("crashes.resolved") || "Resolved", type: countlyQueryBuilder.PropertyType.LIST, group: "Main", getValueList: function() { @@ -107,7 +107,7 @@ }), new countlyQueryBuilder.Property({ id: "is_resolving", - name: "Resolving", + name: jQuery.i18n.prop("crashes.resolving") || "Resolving", type: countlyQueryBuilder.PropertyType.LIST, group: "Main", getValueList: function() { diff --git a/plugins/crashes/frontend/public/localization/crashes.properties b/plugins/crashes/frontend/public/localization/crashes.properties index f8d01454d31..a64f8bc3a96 100644 --- a/plugins/crashes/frontend/public/localization/crashes.properties +++ b/plugins/crashes/frontend/public/localization/crashes.properties @@ -219,6 +219,7 @@ crashes.home.unique = Number of crashes (fatal or non-fatal) that occurred uniqu crashes.home.per-session=Number of crashes for the applied filter occurring per session, expressed as a percentage, in the selected time period. systemlogs.action.crash_resolved = Crash Resolved systemlogs.action.crash_unresolved = Crash Unresolved +systemlogs.action.crash_resolving = Crash Resolving systemlogs.action.crash_shared = Crash Shared systemlogs.action.crash_unshared = Crash Unshared systemlogs.action.crash_modify_share = Crash Modified Share @@ -263,4 +264,13 @@ crashes.user = User crashes.minutes-short = mins crashes.detail = Crash Detail crashes.filter.orientation.portrait = Portrait -crashes.filter.orientation.landscape = Landscape \ No newline at end of file +crashes.filter.orientation.landscape = Landscape + +# Filter properties +crashes.fatality-label = Fatality +crashes.fatal = Fatal +crashes.non-fatal = Non-fatal +crashes.visibility = Visibility +crashes.shown = Shown +crashes.viewed = Viewed +crashes.reoccurred = Reoccurred \ No newline at end of file diff --git a/plugins/crashes/frontend/public/localization/crashes_fr.properties b/plugins/crashes/frontend/public/localization/crashes_fr.properties index 23a401c228d..19ce5fe4537 100644 --- a/plugins/crashes/frontend/public/localization/crashes_fr.properties +++ b/plugins/crashes/frontend/public/localization/crashes_fr.properties @@ -1,258 +1,287 @@ #crashes -crashes.title = Crashes -crashes.plugin-title = Crash analytics -crashes.plugin-description = See actionable information about crashes and exceptions including which users are impacted -crashes.not-found = This crash report cannot be viewed. Possible reasons:

You are trying to view a crash report that you do not have access to or
Link to this crash is invalid. -crashes.search = Search for Error -crashes.all = All -crashes.show = Show -crashes.hide = Hide -crashes.delete = Delete -crashes.hidden = Hidden -crashes.resolution-status = Resolution Status -crashes.resolved = Resolved -crashes.resolved-for = Resolved for -crashes.unresolved = Unresolved -crashes.resolving = Resolving -crashes.unprocessed = Unprocessed -crashes.unresolved-crashes = Unresolved crashes -crashes.groupid = Crash ID -crashes.platform = Platform -crashes.platform_version = Platform Version -crashes.error = Error +crashes.title = Crashs +crashes.plugin-title = Analyse des crashs +crashes.plugin-description = Consultez des informations exploitables sur les crashs et exceptions, y compris les utilisateurs impactés. +crashes.nsystemlogs.action.crash_resolved = Crash résolu +systemlogs.action.crash_unresolved = Crash non résolu +systemlogs.action.crash_shared = Crash partagé +systemlogs.action.crash_unshared = Crash non partagé +systemlogs.action.crash_modify_share = Partage de crash modifié +systemlogs.action.crash_hidden = Crash masqué +systemlogs.action.crash_shown = Crash affiché +systemlogs.action.crash_added_comment = Commentaire ajouté au crash +systemlogs.action.crash_edited_comment = Commentaire de crash modifié +systemlogs.action.crash_deleted_comment = Commentaire de crash supprimé +systemlogs.action.crash_deleted = Crash supprimé +systemlogs.action.crash_group_deleted = Les données du groupe de crash ont été supprimées. +internal-events.[CLY]_crash = Le rapport de crash ne peut pas être consulté. Raisons possibles : Vous essayez de consulter un rapport de crash auquel vous n'avez pas accès ou le lien vers ce crash est invalide. +crashes.search = Rechercher une erreur +crashes.all = Tous +crashes.show = Afficher +crashes.hide = Masquer +crashes.delete = Supprimer +crashes.hidden = Masqué +crashes.resolution-status = Statut de résolution +crashes.resolved = Résolu +crashes.resolved-for = Résolu pour +crashes.unresolved = Non résolu +crashes.resolving = En cours de résolution +crashes.unprocessed = Non traité +crashes.unresolved-crashes = Crashs non résolus +crashes.groupid = ID du crash +crashes.platform = Plateforme +crashes.platform_version = Version de la plateforme +crashes.error = Erreur crashes.reports = Occurrences -crashes.last_time = Last occurrence -crashes.latest_app = Latest app version -crashes.build_id = App Build Number -crashes.build_info = Build Info -crashes.users = Users -crashes.crashed = Crashed -crashes.last-crash = Last Crash -crashes.highest-version = Latest App Version +crashes.last_time = Dernière occurrence +crashes.latest_app = Dernière version de l'app +crashes.build_id = Numéro de build de l'app +crashes.build_info = Informations de build +crashes.users = Utilisateurs +crashes.crashed = A crashé +crashes.last-crash = Dernier crash +crashes.highest-version = Dernière version de l'app crashes.os = OS -crashes.varies = Varies -crashes.view = View -crashes.browser = Browser -crashes.os_version = OS Version -crashes.app_version = App Version -crashes.manufacture = Manufacturer -crashes.device = Device -crashes.group-metrics = Device properties -crashes.group-custom = Custom properties -crashes.resolution = Resolution +crashes.varies = Varie +crashes.view = Voir +crashes.browser = Navigateur +crashes.os_version = Version de l'OS +crashes.app_version = Version de l'app +crashes.manufacture = Fabricant +crashes.device = Appareil +crashes.group-metrics = Propriétés de l'appareil +crashes.group-custom = Propriétés personnalisées +crashes.resolution = Résolution crashes.orientation = Orientation -crashes.root = Rooted/Jailbroken -crashes.online = Crashed when Online -crashes.muted = Crashed while Muted -crashes.background = Crashed in Background -crashes.ram = Ram -crashes.disk = Disk -crashes.battery = Battery -crashes.error-happened = Error happened -crashes.back-to-crashes = Back to crashes overview -crashes.back-to-crash = Back to crash -crashes.crashes-by = Crash occurrences by -crashes.mark-resolved = Mark as resolved -crashes.mark-unresolved = Mark as unresolved -crashes.total = Total Occurences -crashes.total-crashes = Total Crashes -crashes.unique = Unique Crashes -crashes.rate = Crash Frequency -web.crashes.rate = Error Frequency -crashes.top-crash = Top crash type -crashes.affected-users = Affected Users -crashes.free-users = Crash-free Users -web.crashes.free-users = Error-free Users -crashes.free-sessions = Crash-free Sessions -web.crashes.free-sessions = Error-free Sessions -crashes.affected = Affected -crashes.notaffected = Not affected -crashes.top-app = Top App version -crashes.new-crashes = New Crashes -crashes.renew-crashes = Reoccurred Crashes -crashes.new = New -crashes.viewed = Viewed -crashes.fatality = Crash Fatality +crashes.root = Rooté/Jailbreaké +crashes.online = Crashé en ligne +crashes.muted = Crashé en mode silencieux +crashes.background = Crashé en arrière-plan +crashes.ram = RAM +crashes.disk = Disque +crashes.battery = Batterie +crashes.error-happened = Une erreur s'est produite +crashes.back-to-crashes = Retour à l'aperçu des crashs +crashes.back-to-crash = Retour au crash +crashes.crashes-by = Occurrences de crashs par +crashes.mark-resolved = Marquer comme résolu +crashes.mark-unresolved = Marquer comme non résolu +crashes.total = Total des occurrences +crashes.total-crashes = Total des crashs +crashes.unique = Crashs uniques +crashes.rate = Fréquence des crashs +web.crashes.rate = Fréquence des erreurs +crashes.top-crash = Type de crash principal +crashes.affected-users = Utilisateurs affectés +crashes.free-users = Utilisateurs sans crash +web.crashes.free-users = Utilisateurs sans erreur +crashes.free-sessions = Sessions sans crash +web.crashes.free-sessions = Sessions sans erreur +crashes.affected = Affectés +crashes.notaffected = Non affectés +crashes.top-app = Version d'app principale +crashes.new-crashes = Nouveaux crashs +crashes.renew-crashes = Crashs réapparus +crashes.new = Nouveau +crashes.viewed = Vu +crashes.fatality = Fatalité du crash crashes.fatal = Fatal -crashes.nonfatal = Non-fatal -crashes.nonfatal-crashes = Non-fatal Crashes -crashes.total-per-session = Crashes / Sessions -web.crashes.total-per-session = Errors / Sessions +crashes.nonfatal = Non fatal +crashes.nonfatal-crashes = Crashs non fatals +crashes.total-per-session = Crashs / Sessions +web.crashes.total-per-session = Erreurs / Sessions crashes.min = MIN crashes.max = MAX -crashes.avg = AVG -crashes.total-stats = Overall statistics -crashes.run = Running -crashes.cpu = CPU model -crashes.opengl = OpenGL version -crashes.state = Device State -crashes.logs = Logs -crashes.custom = Custom -crashes.loss = Revenue Loss -crashes.first-crash = First crash -crashes.after = After +crashes.avg = MOY +crashes.total-stats = Statistiques globales +crashes.run = En cours d'exécution +crashes.cpu = Modèle de CPU +crashes.opengl = Version OpenGL +crashes.state = État de l'appareil +crashes.logs = Journaux +crashes.custom = Personnalisé +crashes.loss = Perte de revenus +crashes.first-crash = Premier crash +crashes.after = Après crashes.sessions = session(s) -crashes.frequency = Frequency -crashes.share = Share -crashes.unshare = Unshare -crashes.public-link = Public link -crashes.public-crashes = Make publicly available -crashes.public-reports = Display latest reports publicly -crashes.public-loss = Display revenue loss publicly -crashes.public-users = Display affected users publicly -crashes.comments = Comments -crashes.crashed-thread = Crashed Thread -crashes.all-threads = All Threads -crashes.author = Author -crashes.add_comment = Add Comment -crashes.posted_comment = Posted -crashes.edit = Edit -crashes.cancel = Cancel -crashes.edited_comment = Edited -crashes.alert-fails = Some operations were not succesful -crashes.confirm-delete = Are you sure you want to delete this crash? -crashes.confirm-comment-delete = Are you sure you want to delete this comment? -crashes.resolved-users = Resolved upgrades -crashes.try-later = Can not perform this operation now, try again later. -crashes.not-viewed = This error is not viewed yet -crashes.re-occurred = This error re-occurred -crashes.expand = EXPAND -crashes.collapse = COLLAPSE -crashes.of-users = {0} of {1} crashes -crashes.make-action = Perform action -crashes.action-disabled-tooltip = Select crashes from the table to perform actions -crashes.action-resolved = Mark as resolved -crashes.action-unresolved = Mark as unresolved -crashes.action-view = Mark as seen -crashes.action-hide = Mark as hidden -crashes.action-show = Mark as not hidden -crashes.action-deselect = Deselect all -crashes.action-delete = Delete -crashes.action-resolving = Mark as resolving -crashes.confirm-action-resolved = Are you sure you want to mark {0} item(s) as resolved -crashes.confirm-action-unresolved = Are you sure you want to mark {0} item(s) as unresolved -crashes.confirm-action-view = Are you sure you want to mark {0} item(s) as seen -crashes.confirm-action-hide = Are you sure you want to hide {0} item(s) -crashes.confirm-action-show = Are you sure you want to unhide {0} item(s) -crashes.confirm-action-resolving = Are you sure you want to move {0} item(s) to resolving state? +crashes.frequency = Fréquence +crashes.share = Partager +crashes.unshare = Ne plus partager +crashes.public-link = Lien public +crashes.public-crashes = Rendre publiquement disponible +crashes.public-reports = Afficher publiquement les derniers rapports +crashes.public-loss = Afficher publiquement la perte de revenus +crashes.public-users = Afficher publiquement les utilisateurs affectés +crashes.comments = Commentaires +crashes.crashed-thread = Thread du crash +crashes.all-threads = Tous les threads +crashes.author = Auteur +crashes.add_comment = Ajouter un commentaire +crashes.posted_comment = Publié +crashes.edit = Modifier +crashes.cancel = Annuler +crashes.edited_comment = Modifié +crashes.alert-fails = Certaines opérations n'ont pas réussi +crashes.confirm-delete = Êtes-vous sûr de vouloir supprimer ce crash ? +crashes.confirm-comment-delete = Êtes-vous sûr de vouloir supprimer ce commentaire ? +crashes.resolved-users = Mises à niveau résolues +crashes.try-later = Impossible d'effectuer cette opération maintenant, réessayez plus tard. +crashes.not-viewed = Cette erreur n'a pas encore été consultée +crashes.re-occurred = Cette erreur s'est reproduite +crashes.expand = DÉVELOPPER +crashes.collapse = RÉDUIRE +crashes.of-users = {0} sur {1} crashs +crashes.make-action = Effectuer une action +crashes.action-disabled-tooltip = Sélectionnez des crashs dans le tableau pour effectuer des actions +crashes.action-resolved = Marquer comme résolu +crashes.action-unresolved = Marquer comme non résolu +crashes.action-view = Marquer comme vu +crashes.action-hide = Marquer comme masqué +crashes.action-show = Marquer comme non masqué +crashes.action-deselect = Désélectionner tout +crashes.action-delete = Supprimer +crashes.action-resolving = Marquer comme en résolution +crashes.confirm-action-resolved = Êtes-vous sûr de vouloir marquer {0} élément(s) comme résolu(s) +crashes.confirm-action-unresolved = Êtes-vous sûr de vouloir marquer {0} élément(s) comme non résolu(s) +crashes.confirm-action-view = Êtes-vous sûr de vouloir marquer {0} élément(s) comme vu(s) +crashes.confirm-action-hide = Êtes-vous sûr de vouloir masquer {0} élément(s) +crashes.confirm-action-show = Êtes-vous sûr de vouloir afficher {0} élément(s) +crashes.confirm-action-resolving = Êtes-vous sûr de vouloir déplacer {0} élément(s) vers l'état en cours de résolution ? +crashes.confirm-action-title = Supprimer le groupe de crash +crashes.confirm-action-title-plural = Supprimer les groupes de crash +crashes.yes-delete = Oui, supprimer le groupe crashes.stacktrace = Stacktrace -crashes.download-stacktrace = Download stacktrace -crashes.download-binary = Download binary +crashes.download-stacktrace = Télécharger la stacktrace +crashes.download-binary = Télécharger le binaire -crashes.confirm-action-delete = Are you sure you want to permanently delete {0} item(s) -crashes.help-crash-group = An overview of all Crash Groups. Filter, edit, and review the Crash Groups to see crash details. -crashes.help-crash-statistics = An overview of the statistics of all crashes, as well as a graphic representation of selected crash metrics, in a selected time period. -crashes.help-resolved-users = Timeline of resolved crashes for users, due to user app upgrades to an app version which resolves a crash -crashes.help-loss = Value of revenue potentially lost as a result of crashes occurred on Revenue Events. -crashes.help-root = Percentage of rooted/jailbroken devices -crashes.help-online = Percentage of devices that were connected to Internet during crash -crashes.help-muted = Percentage of devices that were muted during crash (i.e. volume level 0) -crashes.help-background = Percentage of devices that had your app in background during crash -crashes.help-platform = Platform of the crash -crashes.help-reports = How many times this crash occurred -crashes.help-affected = Amount of all your app users that had this crash. Amount gets reduced when users upgrade to version higher than for which crash was resolved -crashes.help-app-version = Latest app version that you released to market that had this crash -crashes.help-latest-version = Latest app version that you released to market that had any crash -crashes.help-unresolved = Amount of unresolved crashes -crashes.help-resolved = Percentage of the crashes that have been resolved over the total number of crashes that have occurred. -crashes.help-frequency-short = How often in amount of sessions the app crashes for each user -crashes.help-affected-levels = Breakdown of affected users, who had at least one fatal crash, then at least one only non-fatal crash/catched exception and unaffected users, that did not have any crashes or their crashes were resolved. Amount of affected users gets reduced when users upgrade to version higher than for which crash was resolved -crashes.help-platforms = Details of the platforms on which crashes have occurred. Top 4 platforms are listed here. -crashes.help-fatals = Comparison of fatal crashes and non-fatal/catched exceptions -crashes.help-new = Number of crashes that have not yet been viewed. -crashes.help-reoccurred = Number of crashes that have occurred multiple times. -crashes.help-total = Timeline of all occurrences of all crashes. Same crash may occurred multiple times for same or different users. -crashes.help-nonfatal = Timeline of non-fatal crashes or catched exceptions that were reported to server -crashes.help-session = Timeline for crashes/session in % -crashes.help-unique = Timeline of crash types. Only the first ocurrence of each crash time recorded here. -crashes.help-fatal = Timeline of fatal crashes, which made users exit your app -crashes.help-ram = Shows how much RAM was in use at the time of the crash, displaying the average, minimum, and maximum amounts of RAM used when the crash occurred. -crashes.help-disk = Shows how full was disk space in the moment of crash, displaying the average, minimum, and maximum values of all crashes -crashes.help-battery = Shows how full was the battery at the moment of crash, displaying the average, minimum, and maximum values of all crashes -crashes.help-run = Showing how long app was running before it crashed, displaying the average, minimum, and maximum values of all crashes -crashes.help-frequency = Showing how often in amount of sessions does the app crash for each user, displaying minimum amount of sessions between crashes, maximum amount and average value of sessions between each repeated crash -crashes.help-free-users= Number of users who have not experienced a crash for the applied filter in the selected time period, expressed as a percentage of the total number of users within that time period. -crashes.help-free-sessions = Number of sessions during which the selected crash did not occur in the selected time period, expressed as a percentage of the total number of sessions within that time period. -crashes.help-crash-fatality = Number of fatal crashes, expressed as a percentage of the total number of crashes that have occurred. +crashes.confirm-action-delete = Êtes-vous sûr de vouloir supprimer définitivement {0} élément(s) +crashes.help-crash-group = Un aperçu de tous les groupes de crash. Filtrez, modifiez et examinez les groupes de crash pour voir les détails des crashs. +crashes.help-crash-statistics = Un aperçu des statistiques de tous les crashs, ainsi qu'une représentation graphique des métriques de crash sélectionnées, dans une période donnée. +crashes.help-resolved-users = Chronologie des crashs résolus pour les utilisateurs, due aux mises à niveau des utilisateurs vers une version d'application qui résout un crash +crashes.help-loss = Valeur des revenus potentiellement perdus à la suite de crashs survenus lors d'événements générant des revenus. +crashes.help-root = Pourcentage d'appareils rootés/jailbreakés +crashes.help-online = Pourcentage d'appareils qui étaient connectés à Internet lors du crash +crashes.help-muted = Pourcentage d'appareils qui étaient en mode silencieux lors du crash (c.-à-d. niveau de volume 0) +crashes.help-background = Pourcentage d'appareils qui avaient votre application en arrière-plan lors du crash +crashes.help-platform = Plateforme du crash +crashes.help-reports = Nombre de fois où ce crash s'est produit +crashes.help-affected = Nombre de tous vos utilisateurs d'application qui ont eu ce crash. Le nombre diminue lorsque les utilisateurs passent à une version supérieure à celle pour laquelle le crash a été résolu +crashes.help-app-version = Dernière version de l'application que vous avez publiée sur le marché qui a eu ce crash +crashes.help-latest-version = Dernière version de l'application que vous avez publiée sur le marché qui a eu un crash +crashes.help-unresolved = Nombre de crashs non résolus +crashes.help-resolved = Pourcentage de crashs qui ont été résolus par rapport au nombre total de crashs qui se sont produits. +crashes.help-frequency-short = Fréquence en nombre de sessions entre les crashs de l'application pour chaque utilisateur +crashes.help-affected-levels = Répartition des utilisateurs affectés, qui ont eu au moins un crash fatal, puis au moins un crash non fatal/exception capturée, et des utilisateurs non affectés, qui n'ont pas eu de crashs ou dont les crashs ont été résolus. Le nombre d'utilisateurs affectés diminue lorsque les utilisateurs passent à une version supérieure à celle pour laquelle le crash a été résolu. +crashes.help-platforms = Détails des plateformes sur lesquelles des crashs se sont produits. Les 4 principales plateformes sont listées ici. +crashes.help-fatals = Comparaison des crashs fatals et des exceptions non fatales/capturées +crashes.help-new = Nombre de crashs qui n'ont pas encore été consultés. +crashes.help-reoccurred = Nombre de crashs qui se sont produits plusieurs fois. +crashes.help-total = Chronologie de toutes les occurrences de tous les crashs. Le même crash peut s'être produit plusieurs fois pour le même utilisateur ou pour différents utilisateurs. +crashes.help-nonfatal = Chronologie des crashs non fatals ou des exceptions capturées qui ont été signalés au serveur +crashes.help-session = Chronologie des crashs/session en % +crashes.help-unique = Chronologie des types de crash. Seule la première occurrence de chaque type de crash est enregistrée ici. +crashes.help-fatal = Chronologie des crashs fatals qui ont fait quitter votre application aux utilisateurs +crashes.help-ram = Montre la quantité de RAM utilisée au moment du crash, affichant les valeurs moyenne, minimale et maximale de RAM utilisée lors du crash. +crashes.help-disk = Montre le niveau de remplissage de l'espace disque au moment du crash, affichant les valeurs moyenne, minimale et maximale de tous les crashs. +crashes.help-battery = Montre le niveau de charge de la batterie au moment du crash, affichant les valeurs moyenne, minimale et maximale de tous les crashs. +crashes.help-run = Montre depuis combien de temps l'application était en cours d'exécution avant de crasher, affichant les valeurs moyenne, minimale et maximale de tous les crashs. +crashes.help-frequency = Montre la fréquence, en nombre de sessions, des crashs de l'application pour chaque utilisateur, affichant le nombre minimum de sessions entre les crashs, le nombre maximum et la valeur moyenne des sessions entre chaque crash répété. +crashes.help-free-users= Le nombre d'utilisateurs qui n'ont pas subi de crash pour le filtre appliqué dans la période sélectionnée, exprimé en pourcentage du nombre total d'utilisateurs dans cette période. +crashes.help-free-sessions = Nombre de sessions pendant lesquelles le crash sélectionné ne s'est pas produit dans la période sélectionnée, exprimé en pourcentage du nombre total de sessions dans cette période. +crashes.help-crash-fatality = Nombre de crashs fatals, exprimé en pourcentage du nombre total de crashs qui se sont produits. -crashes.report_limit = Amount of reports displayed -crashes.total_overall=OVERALL -crashes.fatal_crash_count=Fatal Crash Count -web.crashes.fatal_crash_count=Fatal Error Count +crashes.groups-confirm-delete= 1 élément sera affecté par cette action +crashes.groups-confirm-delete-plural= {0} éléments seront affectés par cette action +crashes.groups-want-to-delete= Voulez-vous supprimer définitivement le(s) groupe(s) de crash ? Cette action n'est pas réversible. -crashes.filter.reset-filters=Reset Filters -crashes.filter.all-fatalities=Overall -crashes.filter.all-versions=All Versions -crashes.filter.all-platforms=All Platforms +crashes.report_limit = Nombre de rapports affichés +crashes.total_overall=GLOBAL +crashes.fatal_crash_count=Nombre de crashs fatals +web.crashes.fatal_crash_count=Nombre d'erreurs fatales -crashes.grouping_strategy = Crash grouping strategy -crashes.grouping_strategy.stacktrace = By full stack trace -crashes.grouping_strategy.error_and_file = Error and file where error happened -configs.help.crashes-grouping_strategy = How crashes should be grouped together +crashes.filter.reset-filters=Réinitialiser les filtres +crashes.filter.all-fatalities=Global +crashes.filter.all-versions=Toutes les versions +crashes.filter.all-platforms=Toutes les plateformes -crashes.smart_preprocessing = Smart stack trace preprocessing -configs.help.crashes-smart_preprocessing = Merges together more groups by removing dynamic content based on heuristics +crashes.grouping_strategy = Stratégie de regroupement des crashs +crashes.grouping_strategy.stacktrace = Par trace de pile complète +crashes.grouping_strategy.error_and_file = Erreur et fichier où l'erreur s'est produite +configs.help.crashes-grouping_strategy = Comment les crashs doivent être regroupés ensemble -crashes.smart_regexes = Smart regexes to remove information from stacktrace -configs.help.crashes-smart_regexes = JavaScript regex as string without needed options, one regex per new line, (example removing contents between {} brackets: {.*?}), test: stack.replace(new RegExp(reg, "gim"), ""); +crashes.smart_preprocessing = Prétraitement intelligent de la trace de pile +configs.help.crashes-smart_preprocessing = Fusionne davantage de groupes en supprimant le contenu dynamique basé sur des heuristiques -crashes.same_app_version_crash_update = Latest crash update -configs.help.crashes-same_app_version_crash_update = Update latest crash in crashgroup even when incoming crash has the same app version as latest crash +crashes.smart_regexes = Expressions régulières intelligentes pour supprimer des informations de la stacktrace +configs.help.crashes-smart_regexes = Expression régulière JavaScript sous forme de chaîne sans options nécessaires, une regex par nouvelle ligne, (exemple de suppression du contenu entre accolades {} : {.*?}), test : stack.replace(new RegExp(reg, "gim"), ""); -crashes.max_custom_field_keys = Maximum custom field keys -configs.help.crashes-max_custom_field_keys = Maximum number of unique custom field keys to keep in a crashgroup. Do not set a large number for this as it will increase the size of the crashgroup data being saved. +crashes.same_app_version_crash_update = Mise à jour du dernier crash +configs.help.crashes-same_app_version_crash_update = Mettre à jour le dernier crash dans le groupe de crashs même lorsque le crash entrant a la même version d'application que le dernier crash -crashes.activate_custom_field_cleanup_job = Activate custom field cleanup job -configs.help.crashes-activate_custom_field_cleanup_job = This job will try to cleanup custom field from crashgroups. Do not activate this when there are a very large number of crashgroups (around 100.000 or more). +crashes.max_custom_field_keys = Nombre maximum de clés de champs personnalisés +configs.help.crashes-max_custom_field_keys = Nombre maximum de clés de champs personnalisés uniques à conserver dans un groupe de crashs. Ne définissez pas un nombre élevé car cela augmentera la taille des données du groupe de crashs enregistrées. -crashes.home.total = Total number of crashes or crash groups occurrences for the applied filter, in the selected time period. -crashes.home.unique = Number of crashes (fatal or non-fatal) that occurred uniquely, in the selected time period. Only the first occurrence of the crash is recorded. -crashes.home.per-session=Number of crashes for the applied filter occurring per session, expressed as a percentage, in the selected time period. -systemlogs.action.crash_resolved = Crash Resolved -systemlogs.action.crash_unresolved = Crash Unresolved -systemlogs.action.crash_shared = Crash Shared -systemlogs.action.crash_unshared = Crash Unshared -systemlogs.action.crash_modify_share = Crash Modified Share -systemlogs.action.crash_hidden = Crash Hidden -systemlogs.action.crash_shown = Crash Shown -systemlogs.action.crash_added_comment = Crash Added Comment -systemlogs.action.crash_edited_comment = Crash Edited Comment -systemlogs.action.crash_deleted_comment = Crash Deleted Comment -systemlogs.action.crash_deleted = Crash Deleted +crashes.activate_custom_field_cleanup_job = Activer la tâche de nettoyage des champs personnalisés +configs.help.crashes-activate_custom_field_cleanup_job = Cette tâche essaiera de nettoyer les champs personnalisés des groupes de crashs. Ne l'activez pas lorsqu'il y a un très grand nombre de groupes de crashs (environ 100 000 ou plus). + +crashes.home.total = Nombre total d'occurrences de crashs ou de groupes de crashs pour le filtre appliqué, dans la période sélectionnée. +crashes.home.unique = Nombre de crashs (fatals ou non fatals) qui se sont produits de façon unique, dans la période sélectionnée. Seule la première occurrence du crash est enregistrée. +crashes.home.per-session=Nombre de crashs pour le filtre appliqué se produisant par session, exprimé en pourcentage, dans la période sélectionnée. +systemlogs.action.crash_resolved = Crash résolu +systemlogs.action.crash_unresolved = Crash non résolu +systemlogs.action.crash_shared = Crash partagé +systemlogs.action.crash_unshared = Crash non partagé +systemlogs.action.crash_modify_share = Partage de crash modifié +systemlogs.action.crash_hidden = Crash masqué +systemlogs.action.crash_shown = Crash affiché +systemlogs.action.crash_added_comment = Commentaire ajouté au crash +systemlogs.action.crash_edited_comment = Commentaire de crash modifié +systemlogs.action.crash_deleted_comment = Commentaire de crash supprimé +systemlogs.action.crash_deleted = Crash supprimé +systemlogs.action.crash_group_deleted = Les données du groupe de crash ont été supprimées. internal-events.[CLY]_crash = Crash -crashes.show-binary-images = Show binary images -crashes.binary-images = Binary Images -crashes.go-to-crashes = Go to Crashes -web.crashes.go-to-crashes = Go to Errors -crashes.app-performance = App Performance +crashes.show-binary-images = Afficher les images binaires +crashes.binary-images = Images binaires +crashes.go-to-crashes = Aller aux crashs +web.crashes.go-to-crashes = Aller aux erreurs +crashes.app-performance = Performance de l'application -crashes.crash-group = Crash Group -web.crashes.crash-group = Error Group -crashes.crash-groups = Crash Groups -web.crashes.crash-groups = Error Groups -crashes.crash-statistics = Crash Statistics -web.crashes.crash-statistics = Error Statistics -crashes.top-platforms = Top Platforms -crashes.crash-filters = Crash Filters -crashes.reoccuring = Reoccuring -crashes.every-n-sessions = Every {0} Sessions -crashes.every-session = Every Session -crashes.shown = Shown -crashes.no-comments-yet = No comments, yet... -crashes.no-comments-yet-long = No comments here right now. Add a comment to share your thoughts. -crashes.crash-metrics = Crash Metrics -crashes.average = Average +crashes.crash-group = Groupe de crash +web.crashes.crash-group = Groupe d'erreur +crashes.crash-groups = Groupes de crash +web.crashes.crash-groups = Groupes d'erreur +crashes.crash-statistics = Statistiques de crash +web.crashes.crash-statistics = Statistiques d'erreur +crashes.top-platforms = Principales plateformes +crashes.crash-filters = Filtres de crash +crashes.reoccuring = Récurrent +crashes.every-n-sessions = Toutes les {0} sessions +crashes.every-session = Chaque session +crashes.shown = Affiché +crashes.no-comments-yet = Pas encore de commentaires... +crashes.no-comments-yet-long = Pas de commentaires ici pour le moment. Ajoutez un commentaire pour partager vos réflexions. +crashes.crash-metrics = Métriques de crash +crashes.average = Moyenne crashes.minimum = Minimum crashes.maximum = Maximum -crashes.crash-occurences-by = {0} Crash Occurences by -web.crashes.crash-occurences-by = {0} Error Occurences by -crashes.crash-occurences = Crash Occurences -web.crashes.crash-occurences = Error Occurences -crashes.sdk-logs = SDK Logs -crashes.user = User -crashes.minutes-short = mins -crashes.detail = Crash Detail +crashes.crash-occurences-by = {0} Occurrences de crash par +web.crashes.crash-occurences-by = {0} Occurrences d'erreur par +crashes.crash-occurences = Occurrences de crash +web.crashes.crash-occurences = Occurrences d'erreur +crashes.sdk-logs = Journaux SDK +crashes.user = Utilisateur +crashes.minutes-short = min +crashes.detail = Détail du crash crashes.filter.orientation.portrait = Portrait -crashes.filter.orientation.landscape = Landscape +crashes.filter.orientation.landscape = Paysage + +# Filter properties +crashes.fatality-label = Fatalité +crashes.fatal = Fatal +crashes.non-fatal = Non-fatal +crashes.visibility = Visibilité +crashes.shown = Affiché +crashes.viewed = Vu +crashes.reoccurred = Réapparu \ No newline at end of file diff --git a/plugins/dashboards/api/api.js b/plugins/dashboards/api/api.js index 4ff954cb067..5637f8b98e0 100644 --- a/plugins/dashboards/api/api.js +++ b/plugins/dashboards/api/api.js @@ -17,7 +17,8 @@ var pluginOb = {}, var ejs = require("ejs"); plugins.setConfigs("dashboards", { - sharing_status: true + sharing_status: true, + allow_public_dashboards: true }); (function() { @@ -455,17 +456,24 @@ plugins.setConfigs("dashboards", { groups = [groups]; } groups = groups.map(group_id => group_id + ""); + + var orConditions = [ + {owner_id: memberId}, + {shared_with_edit: memberId}, + {shared_with_view: memberId}, + {shared_email_view: memberEmail}, + {shared_email_edit: memberEmail}, + {shared_user_groups_edit: {$in: groups}}, + {shared_user_groups_view: {$in: groups}} + ]; + + var allowPublicDashboards = plugins.getConfig("dashboards").allow_public_dashboards; + if (allowPublicDashboards !== false) { + orConditions.push({share_with: "all-users"}); + } + filterCond = { - $or: [ - {owner_id: memberId}, - {share_with: "all-users"}, - {shared_with_edit: memberId}, - {shared_with_view: memberId}, - {shared_email_view: memberEmail}, - {shared_email_edit: memberEmail}, - {shared_user_groups_edit: {$in: groups}}, - {shared_user_groups_view: {$in: groups}} - ] + $or: orConditions }; } let projection = {}; @@ -686,6 +694,12 @@ plugins.setConfigs("dashboards", { sharedUserGroupView = []; } + var allowPublicDashboards = plugins.getConfig("dashboards").allow_public_dashboards; + if (shareWith === "all-users" && allowPublicDashboards === false) { + common.returnMessage(params, 400, 'Public dashboards are disabled'); + return true; + } + var sharing = checkSharingStatus(params.member, shareWith, sharedEmailEdit, sharedEmailView, sharedUserGroupEdit, sharedUserGroupView); if (!sharing) { @@ -978,6 +992,12 @@ plugins.setConfigs("dashboards", { sharedUserGroupView = []; } + var allowPublicDashboards = plugins.getConfig("dashboards").allow_public_dashboards; + if (shareWith === "all-users" && allowPublicDashboards === false) { + common.returnMessage(params, 400, 'Public dashboards are disabled'); + return true; + } + common.db.collection("dashboards").findOne({_id: common.db.ObjectID(dashboardId)}, function(err, dashboard) { if (err || !dashboard) { common.returnMessage(params, 400, "Dashboard with the given id doesn't exist"); @@ -1747,6 +1767,16 @@ plugins.setConfigs("dashboards", { } if (dashboard.share_with === "all-users") { + var allowPublicDashboards = plugins.getConfig("dashboards").allow_public_dashboards; + if (allowPublicDashboards === false) { + if (member._id + "" === dashboard.owner_id) { + return cb(null, true); + } + if (member.global_admin) { + return cb(null, true); + } + return cb(null, false); + } return cb(null, true); } @@ -1817,6 +1847,13 @@ plugins.setConfigs("dashboards", { return cb(null, false); } + if (dashboard.share_with === "all-users") { + var allowPublicDashboards = plugins.getConfig("dashboards").allow_public_dashboards; + if (allowPublicDashboards === false) { + return cb(null, false); + } + } + if ((Array.isArray(dashboard.shared_with_edit) && dashboard.shared_with_edit.indexOf(member._id + "") !== -1) || (Array.isArray(dashboard.shared_email_edit) && dashboard.shared_email_edit.indexOf(member.email) !== -1)) { return cb(null, true); diff --git a/plugins/dashboards/frontend/app.js b/plugins/dashboards/frontend/app.js index c623758c11c..cdb3316922d 100644 --- a/plugins/dashboards/frontend/app.js +++ b/plugins/dashboards/frontend/app.js @@ -10,6 +10,7 @@ var countlyFs = require('../../../api/utils/countlyFs.js'); plugin.renderDashboard = function(ob) { ob.data.countlyGlobal.sharing_status = plugins.getConfig("dashboards").sharing_status; + ob.data.countlyGlobal.allow_public_dashboards = plugins.getConfig("dashboards").allow_public_dashboards; }; plugin.staticPaths = function(app/*, countlyDb*/) { diff --git a/plugins/dashboards/frontend/public/javascripts/countly.views.js b/plugins/dashboards/frontend/public/javascripts/countly.views.js index 83883a0bef1..51b4b2c3cd2 100644 --- a/plugins/dashboards/frontend/public/javascripts/countly.views.js +++ b/plugins/dashboards/frontend/public/javascripts/countly.views.js @@ -484,25 +484,6 @@ saveButtonLabel: "", sharingAllowed: countlyGlobal.sharing_status || AUTHENTIC_GLOBAL_ADMIN, groupSharingAllowed: countlyGlobal.plugins.indexOf("groups") > -1 && AUTHENTIC_GLOBAL_ADMIN, - constants: { - sharingOptions: [ - { - value: "all-users", - name: this.i18nM("dashboards.share.all-users"), - description: this.i18nM("dashboards.share.all-users.description"), - }, - { - value: "selected-users", - name: this.i18nM("dashboards.share.selected-users"), - description: this.i18nM("dashboards.share.selected-users.description"), - }, - { - value: "none", - name: this.i18nM("dashboards.share.none"), - description: this.i18nM("dashboards.share.none.description"), - } - ] - }, sharedEmailEdit: [], sharedEmailView: [], sharedGroupEdit: [], @@ -511,6 +492,34 @@ }; }, computed: { + constants: function() { + var allSharingOptions = [ + { + value: "all-users", + name: this.i18nM("dashboards.share.all-users"), + description: this.i18nM("dashboards.share.all-users.description"), + }, + { + value: "selected-users", + name: this.i18nM("dashboards.share.selected-users"), + description: this.i18nM("dashboards.share.selected-users.description"), + }, + { + value: "none", + name: this.i18nM("dashboards.share.none"), + description: this.i18nM("dashboards.share.none.description"), + } + ]; + + var allowPublicDashboards = countlyGlobal.allow_public_dashboards !== false; + var sharingOptions = allowPublicDashboards ? allSharingOptions : allSharingOptions.filter(function(option) { + return option.value !== "all-users"; + }); + + return { + sharingOptions: sharingOptions + }; + }, canShare: function() { var canShare = this.sharingAllowed && (this.controls.initialEditedObject.is_owner || AUTHENTIC_GLOBAL_ADMIN); return canShare; diff --git a/plugins/dashboards/frontend/public/localization/dashboards.properties b/plugins/dashboards/frontend/public/localization/dashboards.properties index b5b456cc996..06b17334afc 100644 --- a/plugins/dashboards/frontend/public/localization/dashboards.properties +++ b/plugins/dashboards/frontend/public/localization/dashboards.properties @@ -45,6 +45,8 @@ dashboards.compared-to-prev-period = compared to prev.period dashboards.sharing_status = Allow Dashboard Sharing configs.help.dashboards-sharing_status = Enable dashboard sharing for users to share a dashboard with other users. If set to off, dashboards cannot be shared with others. +dashboards.allow_public_dashboards = Allow public dashboards +configs.help.dashboards-allow_public_dashboards = Allow sharing dashboards with all Countly dashboard users dashboards.access-denied = This dashboard is no longer shared with you or an error has occurred. Please ask your global administrator if you think this is due to an issue. dashbaords.access-denied-title = Access Denied dashboards.edit-access-denied = You don't have the edit permission for this dashboard. Please ask your global administrator if you think this is due to an issue. diff --git a/plugins/dashboards/frontend/public/localization/dashboards_fr.properties b/plugins/dashboards/frontend/public/localization/dashboards_fr.properties index 79df0ec9d17..09db224e11e 100644 --- a/plugins/dashboards/frontend/public/localization/dashboards_fr.properties +++ b/plugins/dashboards/frontend/public/localization/dashboards_fr.properties @@ -1,212 +1,217 @@ -#No dashboards -dashboards.home-title = Welcome to Dashboards -dashboards.home-description = Dashboards lets you visualise data for multiple applications and metrics with ease. Using full screen mode on a TV you will always be on top of your KPIs. -dashboards.save-dashboard = Save Changes -dashboards.activate-full-screen = Activate full screen mode +dashboards.home-title = Bienvenue sur les tableaux de bord +dashboards.home-description = Les tableaux de bord vous permettent de visualiser facilement les données de plusieurs applications et métriques. En utilisant le mode plein écran sur une TV, vous restez toujours informé de vos KPIs. +dashboards.save-dashboard = Enregistrer les modifications +dashboards.activate-full-screen = Activer le mode plein écran #No widgets -dashboards.empty-dashboard = Your dashboard is empty -dashboards.empty-dashboard-description = Create your own composition of widgets to focus on metrics most important for your business -dashboards.select-cohorts-multi = Select maximum 5 cohorts -dashboards.cohorts-label = Cohorts -dashboards.cohorts-visualization = Visualization -dashboards.bar-chart-multi-cohort = Bar Chart -dashboards.number-selection-cohort = Number -dashboards.over-time-multi-cohort = Over Time -dashboards.bar-chart-selection-cohort-description = Multiple cohorts visualized on a bar chart -dashboards.number-selection-cohort-description = A number widget for a single cohort -dashboards.over-time-selection-cohort-description = Multiple cohorts visualized over time on a bar chart -dashboards.bar-color = Bar color -dashboards.font-color = Font color -dashboards.create-and-add = Create and add widget -dashboards.save-changes = Save Changes - -dashboards.dashboard-theme = Dashboard theme -dashboards.create-email-reports = Create E-mail Report -dashboards.select_dashboards = Select custom dashboard to receive reports from -dashboards.select-report-date-range = Select dashboard data date range -dashboards.select = Select dashboard -dashboards.select-date-range = Select date range -dashboards.report = Dashboard report -dashboards.custom-period = Use custom time period -dashboards.custom-cohort-widget-title= Show overall number of users in the chart -dashboards.email-subject = {0} stats for {1}! -dashbaords.dashboard-theme-1 = Charcoal Blue -dashbaords.dashboard-theme-2 = Deep Violet -dashbaords.dashboard-theme-3 = Dark Green -dashbaords.dashboard-theme-4 = Grey -dashbaords.dashboard-theme-5 = Bright Grey -dashbaords.dashboard-theme-6 = Ruby -dashbaords.dashboard-theme-7 = Deep Blue -dashboards.compared-to-prev-period = compared to prev.period - -dashboards.sharing_status = Allow Dashboard Sharing -configs.help.dashboards-sharing_status = Enable dashboard sharing for users to share a dashboard with other users. If set to off, a dashboard cannot be shared with others. -dashboards.access-denied = This dashboard is no longer shared with you or an error has occurred. Please ask your global administrator if you think this is due to an issue. -dashbaords.access-denied-title = Access Denied -dashboards.edit-access-denied = You don't have the edit permission for this dashboard. Please ask your global administrator if you think this is due to an issue. -dashboards.sharing-denied = Dashboard sharing has been disabled. Please ask your global administrator if you think this is due to an issue. -dashboards.widget-warning.time = This widget has data for {0} to {1}. Please select a date range in this period. - -systemlogs.action.widget_added = Widget Added -systemlogs.action.widget_edited = Widget Edited -systemlogs.action.widget_deleted = Widget Deleted -systemlogs.action.dashboard_added = Dashboard Added -systemlogs.action.dashboard_edited = Dashboard Edited -systemlogs.action.dashboard_deleted = Dashboard Deleted - -dashboards.select-color = Text color -dashboards.font-size = Font size -dashboards.text-style = Text style -dashboards.text-alignment = Alignment -dashbords.align.left = Left -dashbords.align.right = Right -dashbords.align.center = Center -dashbords.align.justify = Justify - -dashboards.select-text-decorations = Select text decorations -dashboards.add-hyperlink = Add Hyperlink -dashboards.link-text = Link text -dashboards.link-to = Link To - - -dashboards.default = Dashboards -dashboards.create-dashboard = Create a dashboard - -dashboards.custom-period.select-date-range = Select period - +dashboards.empty-dashboard = Votre tableau de bord est vide +dashboards.empty-dashboard-description = Créez votre propre composition de widgets pour vous concentrer sur les métriques les plus importantes pour votre entreprise +dashboards.select-cohorts-multi = Sélectionnez jusqu'à 5 cohortes +dashboards.cohorts-label = Cohortes +dashboards.cohorts-visualization = Visualisation +dashboards.bar-chart-multi-cohort = Diagramme en barres +dashboards.number-selection-cohort = Nombre +dashboards.over-time-multi-cohort = Au fil du temps +dashboards.bar-chart-selection-cohort-description = Plusieurs cohortes visualisées sur un diagramme en barres +dashboards.number-selection-cohort-description = Un widget de nombre pour une seule cohorte +dashboards.over-time-selection-cohort-description = Plusieurs cohortes visualisées au fil du temps sur un diagramme en barres +dashboards.bar-color = Couleur de la barre +dashboards.font-color = Couleur de la police +dashboards.create-and-add = Créer et ajouter un widget +dashboards.save-changes = Enregistrer les modifications + +dashboards.dashboard-theme = Thème du tableau de bord +dashboards.create-email-reports = Créer un rapport par e-mail +dashboards.select_dashboards = Sélectionnez le tableau de bord personnalisé pour recevoir des rapports +dashboards.select-report-date-range = Sélectionnez la plage de dates des données du tableau de bord +dashboards.select = Sélectionner le tableau de bord +dashboards.select-date-range = Sélectionner la plage de dates +dashboards.report = Rapport du tableau de bord +dashboards.period = Période +dashboards.custom-period = Utiliser une période personnalisée +dashboards.custom-cohort-widget-title= Afficher le nombre total d'utilisateurs dans le graphique +dashboards.custom-refresh-rate = Fréquence de rafraîchissement personnalisée +dashboards.custom-refresh-rate-description = La fréquence de rafraîchissement doit être spécifiée en minutes, avec une valeur minimale de 5 minutes. Le tableau de bord tentera de rafraîchir les widgets basés sur les rapports (tels que les entonnoirs et les analyses) selon la fréquence spécifiée. Cependant, si le calcul de tout widget prend plus de temps que la période de rafraîchissement définie, il ne sera pas rafraîchi aussi fréquemment que le taux défini. +dashboards.email-subject = Statistiques {0} pour {1} ! +dashbaords.dashboard-theme-1 = Bleu charbon +dashbaords.dashboard-theme-2 = Violet profond +dashbaords.dashboard-theme-3 = Vert foncé +dashbaords.dashboard-theme-4 = Gris +dashbaords.dashboard-theme-5 = Gris clair +dashbaords.dashboard-theme-6 = Rubis +dashbaords.dashboard-theme-7 = Bleu profond +dashboards.compared-to-prev-period = comparé à la période précédente + +dashboards.sharing_status = Autoriser le partage du tableau de bord +configs.help.dashboards-sharing_status = Activez le partage du tableau de bord pour permettre aux utilisateurs de partager un tableau de bord avec d'autres utilisateurs. Si désactivé, les tableaux de bord ne peuvent pas être partagés. +dashboards.access-denied = Ce tableau de bord n'est plus partagé avec vous ou une erreur s'est produite. Veuillez contacter votre administrateur global si vous pensez que cela est dû à un problème. +dashbaords.access-denied-title = Accès refusé +dashboards.edit-access-denied = Vous n'avez pas la permission de modifier ce tableau de bord. Veuillez contacter votre administrateur global si vous pensez que cela est dû à un problème. +dashboards.sharing-denied = Le partage du tableau de bord a été désactivé. Veuillez contacter votre administrateur global si vous pensez que cela est dû à un problème. +dashboards.widget-warning.time = Ce widget contient des données de {0} à {1}. Veuillez sélectionner une plage de dates dans cette période. + +systemlogs.action.widget_added = Widget ajouté +systemlogs.action.widget_edited = Widget modifié +systemlogs.action.widget_deleted = Widget supprimé +systemlogs.action.dashboard_added = Tableau de bord ajouté +systemlogs.action.dashboard_edited = Tableau de bord modifié +systemlogs.action.dashboard_deleted = Tableau de bord supprimé + +dashboards.select-color = Couleur du texte +dashboards.font-size = Taille de la police +dashboards.text-style = Style du texte +dashboards.text-alignment = Alignement +dashbords.align.left = Gauche +dashbords.align.right = Droite +dashbords.align.center = Centre +dashbords.align.justify = Justifier + +dashboards.select-text-decorations = Sélectionner les décorations du texte +dashboards.add-hyperlink = Ajouter un hyperlien +dashboards.link-text = Texte du lien +dashboards.link-to = Lien vers + + +dashboards.default = Tableaux de bord +dashboards.create-dashboard = Créer un tableau de bord + +dashboards.custom-period.select-date-range = Sélectionner la période #Final localization -dashboards.plugin-title = Dashboards -dashboards.dashboard = Dashboards +dashboards.plugin-title = Tableaux de bord +dashboards.dashboard = Tableaux de bord #Dashboards -dashboards.dashboard-name = Dashboard name -dashboards.create-new-dashboard-heading = Create new dashboard -dashboards.edit-dashboard-heading = Edit dashboard -dashboards.duplicate-dashboard-heading = Duplicate dashboard -dashboards.create-dashboard = Create a dashboard -dashboards.save-dashboard = Save Changes -dashboards.share.all-users = All users -dashboards.share.all-users.description = Share view access to dashbaord with all users -dashboards.share.selected-users = Some specific users -dashboards.share.selected-users.description = Share the dashboard with some specific users -dashboards.share.none = Private -dashboards.share.none.description = Share the dashboard with no one -dashboards.share-with = Visibility -dashboards.user-permissions = User permissions -dashboards.user-group-permission = User group permissions -dashboards.users-edit-permission = Edit permission -dashboards.users-view-permission = View-only permission -dashboards.enter-user-email = Enter user email -dashboards.users-edit-description = Select users who can view and edit the dashboard -dashboards.users-view-description = Select users who can only view the dashboard and can't do any editing. -dashbaords.sharing-disabled = Adding or editing users is disabled by global administrator - -placeholder.dashboards.dashboard-name = Enter dashboard name -placeholder.dashboards.select-user-group = Select user groups - -dashboards.duplicate-dashboard = Duplicate - -dashboards.yes-delete-dashboard = Yes, delete dashboard -dashboards.delete-dashboard-title = Delete dashboard? -dashboards.delete-dashboard-text = Do you really want to delete dashboard called {0} ? -dashbaords.created = Created -dashbaords.created-by = by {0} +dashboards.dashboard-name = Nom du tableau de bord +dashboards.create-new-dashboard-heading = Créer un nouveau tableau de bord +dashboards.edit-dashboard-heading = Modifier le tableau de bord +dashboards.duplicate-dashboard-heading = Dupliquer le tableau de bord +dashboards.create-dashboard = Créer le tableau de bord +dashboards.save-dashboard = Enregistrer le tableau de bord +dashboards.share.all-users = Tous les utilisateurs +dashboards.share.all-users.description = Partager l'accès en lecture au tableau de bord avec tous les utilisateurs +dashboards.share.selected-users = Certains utilisateurs spécifiques +dashboards.share.selected-users.description = Partager le tableau de bord avec certains utilisateurs spécifiques +dashboards.share.none = Privé +dashboards.share.none.description = Ne partager le tableau de bord avec personne +dashboards.share-with = Visibilité +dashboards.share-with-tooltip-content = Les paramètres de visibilité d'un tableau de bord sont indépendants des autorisations globales de l'utilisateur. +dashboards.user-permissions = Permissions utilisateur +dashboards.user-permissions-tooltip-content = Les permissions utilisateur pour un tableau de bord sont indépendantes des autorisations globales de l'utilisateur. +dashboards.user-group-permission = Permissions du groupe d'utilisateurs +dashboards.user-group-permission-tooltip-content = Les permissions du groupe d'utilisateurs pour un tableau de bord sont indépendantes des autorisations globales du groupe d'utilisateurs. +dashboards.users-edit-permission = Permission de modification +dashboards.users-view-permission = Permission de lecture seule +dashboards.enter-user-email = Saisir l'e-mail de l'utilisateur +dashboards.users-edit-description = Sélectionner les utilisateurs qui peuvent voir et modifier le tableau de bord +dashboards.users-view-description = Sélectionner les utilisateurs qui peuvent uniquement voir le tableau de bord et ne peuvent pas le modifier. +dashbaords.sharing-disabled = L'ajout ou la modification d'utilisateurs est désactivé par l'administrateur global + +placeholder.dashboards.dashboard-name = Saisir le nom du tableau de bord +placeholder.dashboards.select-user-group = Sélectionner les groupes d'utilisateurs + +dashboards.duplicate-dashboard = Dupliquer + +dashboards.yes-delete-dashboard = Oui, supprimer le tableau de bord +dashboards.delete-dashboard-title = Supprimer le tableau de bord ? +dashboards.delete-dashboard-text = Voulez-vous vraiment supprimer le tableau de bord nommé {0} ? +dashbaords.created = Créé +dashbaords.created-by = par {0} #Widgets -dashboards.add-widget = New widget -dashboards.add-new-widget-heading = Add new widget -dashboards.edit-widget-heading = Edit your widget -dashbaords.create-widget = Create widget -dashboards.save-widget = Save widget -dashboards.bold = Bold -dashboards.italic = Italic -dashboards.underline = Underline -dashboards.delete-widget = Yes, delete widget -dashboards.delete-widget-title = Delete widget? -dashboards.delete-widget-text = Do you want to delete the selected widget? - -dashboards.widget-type = Widget Type -dashboards.widget-type.analytics = Analytics +dashboards.add-widget = Nouveau widget +dashboards.add-new-widget-heading = Ajouter un nouveau widget +dashboards.edit-widget-heading = Modifier votre widget +dashbaords.create-widget = Créer un widget +dashboards.save-widget = Enregistrer le widget +dashboards.bold = Gras +dashboards.italic = Italique +dashboards.underline = Souligné +dashboards.delete-widget = Oui, supprimer le widget +dashboards.delete-widget-title = Supprimer le widget ? +dashboards.delete-widget-text = Voulez-vous supprimer le widget sélectionné ? + +dashboards.widget-type = Type de widget +dashboards.widget-type.analytics = Analytique dashboards.widget-type.note = Note -dashboards.widget-type.events = Events -dashboards.widget-type.crash = Crashes +dashboards.widget-type.events = Événements +dashboards.widget-type.crash = Crashs -dashboards.plugin-disabled = {0} plugin has been disabled, either enable it or delete the widget. -dashboards.plugin-invalid-data = The data for {0} widget is invalid. Please delete the widget and create again. +dashboards.plugin-disabled = Le plugin {0} a été désactivé, veuillez l'activer ou supprimer le widget. +dashboards.plugin-invalid-data = Les données du widget {0} sont invalides. Veuillez supprimer le widget et le recréer. -dashboards.widget-type-description = Select the type of widget you want to create -dashboards.add-note = Add note +dashboards.widget-type-description = Sélectionner le type de widget que vous souhaitez créer +dashboards.add-note = Ajouter une note +dashboards.note-placeholder = Écrivez quelque chose ... -dashboards.multiple-apps-count = {0} apps +dashboards.multiple-apps-count = {0} applications #Widgets drawer -dashboards.data-type = Data Type -dashboards.data-type.user-analytics = User Analytics +dashboards.data-type = Type de données +dashboards.data-type.user-analytics = Analytique utilisateur dashboards.data-type.session = Session -dashboards.data-type.technology = Technology -dashboards.data-type.geo = Geo -dashboards.data-type.views = Views +dashboards.data-type.technology = Technologie +dashboards.data-type.geo = Géo +dashboards.data-type.views = Vues dashboards.data-type.sources = Sources -dashboards.metrics-single = Metric -dashboards.metrics-multi = Metrics -dashboards.app-count = App count -dashboards.multiple-apps = Multiple apps -dashboards.single-app = Single app -dashboards.multiple-apps-description = Select single metric from multiple apps -dashboards.single-app-description = Select multiple metrics from a single app -dashbaords.visualization = Visualization -dashbaords.visualization-description = Select visualization type -dashboards.visualization.time-series = Time Series -dashboards.visualization.bar-chart = Bar Chart -dashboards.visualization.pie-chart = Pie Chart -dashboards.visualization.number = Number -dashboards.visualization.table = Table -dashboards.source-apps = Source app(s) -dashboards.custom-widget-title = Use custom widget title -dashboards.additional-settings = Additional Settings - -placeholder.dashboards.select-applications-multi = Select maximum {0} apps -placeholder.dashboards.select-applications-single = Select app -placeholder.dashboards.enter-widget-title = Enter custom widget title - -placeholder.dashbaords.select-data-type = Select data type -placeholder.dashboards.select-metric-single = Select a metric -placeholder.dashboards.select-metric-multi = Select maximum {0} metrics - -dashboards.breakdown = Breakdown -placeholder.dashboards.select-breakdown = Select a breakdown - -dashboards.events = Events -dashboards.event = Event -placeholder.dashboards.select-event-multi = Select maximum {0} events -placeholder.dashboards.select-event-single = Select event +dashboards.metrics-single = Métrique +dashboards.metrics-multi = Métriques +dashboards.app-count = Nombre d'applications +dashboards.multiple-apps = Plusieurs applications +dashboards.single-app = Une seule application +dashboards.multiple-apps-description = Sélectionner une seule métrique parmi plusieurs applications +dashboards.single-app-description = Sélectionner plusieurs métriques d'une seule application +dashbaords.visualization = Visualisation +dashbaords.visualization-description = Sélectionner le type de visualisation +dashboards.visualization.time-series = Série temporelle +dashboards.visualization.bar-chart = Diagramme en barres +dashboards.visualization.pie-chart = Diagramme circulaire +dashboards.visualization.number = Nombre +dashboards.visualization.table = Tableau +dashboards.source-apps = Application(s) source +dashboards.custom-widget-title = Utiliser un titre de widget personnalisé +dashboards.additional-settings = Paramètres supplémentaires + +placeholder.dashboards.select-applications-multi = Sélectionner au maximum {0} applications +placeholder.dashboards.select-applications-single = Sélectionner une application +placeholder.dashboards.enter-widget-title = Saisir le titre du widget personnalisé + +placeholder.dashbaords.select-data-type = Sélectionner le type de données +placeholder.dashboards.select-metric-single = Sélectionner une métrique +placeholder.dashboards.select-metric-multi = Sélectionner au maximum {0} métriques + +dashboards.breakdown = Répartition +placeholder.dashboards.select-breakdown = Sélectionner une répartition + +dashboards.events = Événements +dashboards.event = Événement +placeholder.dashboards.select-event-multi = Sélectionner au maximum {0} événements +placeholder.dashboards.select-event-single = Sélectionner un événement #Error messages -dashboards.error.something-went-wrong = Something went wrong. +dashboards.error.something-went-wrong = Une erreur s'est produite. #Push -dashboards.sent = Push Sent -dashboards.actioned = Push Actioned +dashboards.sent = Push envoyé +dashboards.actioned = Push actionné #Crash -dashboards.crf = Total Fatal Crashes -dashboards.crnf = Total Non-Fatal Crashes -dashboards.cruf = Unique Fatal Crashes -dashboards.crunf = Unique Non-Fatal Crashes -dashboards.crauf = Fatal Crash-Free Users -dashboards.craunf = Non-Fatal Crash-Free Users -dashboards.crfses = Fatal Crash-Free Sessions -dashboards.crnfses = Non-Fatal Crash-Free Sessions -dashboards.send-email = Notify all users via emails -dashboards.additional-settings = Additional Settings -dashboards.dashboard-invite-subtitle = A new dashboard on Countly was shared with you. -dashboards.dashboard-invite-content = was shared with you with View permission. Click to access the dashboard. -dashboards.dashbhoard-invite-content-edit = was shared with you with Edit permission. Click to access the dashboard. -dashboards.dashboard-invite-link-text = Go to Dashboard -dashboards.dashboard-invite-title = New dashboard invitation from Countly +dashboards.crf = Total des crashs fatals +dashboards.crnf = Total des crashs non fatals +dashboards.cruf = Crashs fatals uniques +dashboards.crunf = Crashs non fatals uniques +dashboards.crauf = Utilisateurs sans crash fatal +dashboards.craunf = Utilisateurs sans crash non fatal +dashboards.crfses = Sessions sans crash fatal +dashboards.crnfses = Sessions sans crash non fatal +dashboards.send-email = Notifier tous les utilisateurs par e-mail +dashboards.additional-settings = Paramètres supplémentaires +dashboards.dashboard-invite-subtitle = Un nouveau tableau de bord sur Countly a été partagé avec vous. +dashboards.dashboard-invite-content = a été partagé avec vous avec la permission de lecture. Cliquez pour accéder au tableau de bord. +dashboards.dashbhoard-invite-content-edit = a été partagé avec vous avec la permission de modification. Cliquez pour accéder au tableau de bord. +dashboards.dashboard-invite-link-text = Aller au tableau de bord +dashboards.dashboard-invite-title = Nouvelle invitation au tableau de bord depuis Countly diff --git a/plugins/dashboards/frontend/public/templates/dashboards-drawer.html b/plugins/dashboards/frontend/public/templates/dashboards-drawer.html index 7ce091962fd..fb8f86cc810 100644 --- a/plugins/dashboards/frontend/public/templates/dashboards-drawer.html +++ b/plugins/dashboards/frontend/public/templates/dashboards-drawer.html @@ -3,25 +3,28 @@ @copy="onCopy" @close="onClose" :title="title" + test-id="create-dashboard-drawer" :saveButtonLabel="saveButtonLabel" v-bind="controls"> - + \ No newline at end of file diff --git a/plugins/dashboards/frontend/public/templates/dashboards-menu.html b/plugins/dashboards/frontend/public/templates/dashboards-menu.html index 01c0b4b4053..3ae592500df 100644 --- a/plugins/dashboards/frontend/public/templates/dashboards-menu.html +++ b/plugins/dashboards/frontend/public/templates/dashboards-menu.html @@ -1,8 +1,8 @@
- Dashboards + Dashboards - + New @@ -10,6 +10,7 @@
@@ -20,7 +21,12 @@