đ New tracker script version has been released to NPM!
"}'
+
+ - name: Notify team on failure
+ if: ${{ failure() }}
+ uses: fjogeleit/http-request-action@551353b829c3646756b2ec2b3694f819d7957495 # v2.0.0
+ with:
+ url: ${{ secrets.BUILD_NOTIFICATION_URL }}
+ method: 'POST'
+ customHeaders: '{"Content-Type": "application/json"}'
+ data: '{"content": "NPM release failed"}'
diff --git a/.github/workflows/tracker-script-update.yml b/.github/workflows/tracker-script-update.yml
new file mode 100644
index 000000000000..fc9f589cb98b
--- /dev/null
+++ b/.github/workflows/tracker-script-update.yml
@@ -0,0 +1,137 @@
+name: "Tracker script update"
+
+on:
+ pull_request:
+ paths:
+ - 'tracker/src/**'
+ - 'tracker/package.json'
+ - 'tracker/package-lock.json'
+
+jobs:
+ tracker-script-update:
+ runs-on: ubuntu-latest
+ permissions:
+ pull-requests: write
+ contents: read
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ repository: ${{ github.event.pull_request.head.repo.full_name }}
+ ref: ${{ github.event.pull_request.head.ref }}
+ token: ${{ secrets.PLAUSIBLE_BOT_GITHUB_TOKEN }}
+ fetch-depth: 1
+
+ - name: Checkout master for comparison
+ uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ with:
+ ref: master
+ path: master-branch
+
+ - name: Install jq and clickhouse-local
+ run: |
+ sudo apt-get install apt-transport-https ca-certificates dirmngr
+ sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 --recv 8919F6BD2B48D754
+ echo "deb https://packages.clickhouse.com/deb stable main" | sudo tee \
+ /etc/apt/sources.list.d/clickhouse.list
+ sudo apt-get update
+
+ sudo apt-get install jq clickhouse-server -y
+
+ - name: Compare and increment tracker_script_version
+ id: increment
+ run: |
+ cd tracker
+ # Get current version from PR branch
+ PR_VERSION=$(jq '.tracker_script_version' package.json)
+
+ # Get version from master, default to 0 if not present
+ MASTER_VERSION=$(jq '.tracker_script_version // 0' ../master-branch/tracker/package.json)
+
+ echo "PR tracker_script_version: $PR_VERSION"
+ echo "Master tracker_script_version: $MASTER_VERSION"
+
+ # Calculate new version
+ NEW_VERSION=$((PR_VERSION + 1))
+
+ # Check version conditions
+ if [ $PR_VERSION -lt $MASTER_VERSION ]; then
+ echo "::error::PR tracker tracker_script_version ($PR_VERSION) is less than master ($MASTER_VERSION) and cannot be incremented."
+ echo "::error::Rebase or merge master into your PR to fix this."
+ exit 1
+ elif [ $NEW_VERSION -eq $((MASTER_VERSION + 1)) ]; then
+ echo "Incrementing version from $PR_VERSION to $NEW_VERSION"
+ jq ".tracker_script_version = $NEW_VERSION" package.json > package.json.tmp
+ mv package.json.tmp package.json
+ echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
+ echo "changed=true" >> $GITHUB_OUTPUT
+ else
+ echo "Already incremented tracker_script_version in PR, skipping."
+ echo "version=$PR_VERSION" >> $GITHUB_OUTPUT
+ echo "changed=false" >> $GITHUB_OUTPUT
+ fi
+
+ - name: Commit and push changes
+ uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5
+ if: steps.increment.outputs.changed == 'true'
+ with:
+ message: 'chore: Bump tracker_script_version to ${{ steps.increment.outputs.version }}'
+ github_token: ${{ secrets.PLAUSIBLE_BOT_GITHUB_TOKEN }}
+ add: |
+ - tracker/package.json
+
+ - name: Compile tracker code
+ run: |
+ cd master-branch/tracker
+ npm install
+ node compile.js --suffix master
+ cp ../priv/tracker/js/plausible* ../../priv/tracker/js/
+
+ cd ../../tracker
+ npm install
+ node compile.js --suffix pr
+
+ - name: Run script size analyzer and set output
+ id: analyze
+ run: |
+ cd tracker
+ OUT=$(node compiler/analyze-sizes.js --baselineSuffix master --currentSuffix pr)
+ # Set multiline output
+ echo "sizes<> $GITHUB_OUTPUT
+ echo "$OUT" >> $GITHUB_OUTPUT
+ echo "EOF" >> $GITHUB_OUTPUT
+
+ - name: Comment script size report on PR
+ uses: thollander/actions-comment-pull-request@24bffb9b452ba05a4f3f77933840a6a841d1b32b # v3.0.1
+ with:
+ message: |
+ ${{ steps.analyze.outputs.sizes }}
+ comment-tag: size-report
+
+ - name: Check PR has tracker release label set
+ if: >-
+ ${{ !(
+ contains(github.event.pull_request.labels.*.name, 'tracker-release: patch') ||
+ contains(github.event.pull_request.labels.*.name, 'tracker-release: minor') ||
+ contains(github.event.pull_request.labels.*.name, 'tracker-release: major') ||
+ contains(github.event.pull_request.labels.*.name, 'tracker-release: none') ) }}
+
+ run: |
+ echo "::error::PR changes tracker script but does not have a 'tracker release:' label. Please add one."
+ exit 1
+
+ - name: Get changed files
+ id: changelog_changed
+ uses: tj-actions/changed-files@e0021407031f5be11a464abee9a0776171c79891
+ with:
+ files: |
+ tracker/npm_package/CHANGELOG.md
+
+ - name: Error if PR no tracker CHANGELOG.md updates
+ if: >-
+ ${{ (
+ steps.changelog_changed.outputs.any_changed == 'false' &&
+ !contains(github.event.pull_request.labels.*.name, 'tracker-release: none') ) }}
+ run: |
+ echo "::error::PR changes tracker script but does not have a tracker NPM package CHANGELOG.md update."
+ exit 1
diff --git a/.github/workflows/tracker-version-bump.yml b/.github/workflows/tracker-version-bump.yml
deleted file mode 100644
index 35d6bbf4e1fc..000000000000
--- a/.github/workflows/tracker-version-bump.yml
+++ /dev/null
@@ -1,75 +0,0 @@
-name: "Tracker: Increment Reported Version"
-
-on:
- pull_request:
- paths:
- - 'tracker/src/**'
- - 'tracker/package.json'
- - 'tracker/package-lock.json'
-
-jobs:
- tracker-increment-reported-version:
- runs-on: ubuntu-latest
- permissions:
- contents: write
- steps:
- - name: Checkout code
- uses: actions/checkout@v4
- with:
- repository: ${{ github.event.pull_request.head.repo.full_name }}
- ref: ${{ github.event.pull_request.head.ref }}
- token: ${{ secrets.PLAUSIBLE_BOT_GITHUB_TOKEN }}
- fetch-depth: 0
-
- - name: Checkout master for comparison
- uses: actions/checkout@v4
- with:
- ref: master
- path: master-branch
-
- - name: Install jq
- run: sudo apt-get install jq -y
-
- - name: Compare and increment tracker_script_version
- id: increment
- run: |
- cd tracker
- # Get current version from PR branch
- PR_VERSION=$(jq '.tracker_script_version' package.json)
-
- # Get version from master, default to 0 if not present
- MASTER_VERSION=$(jq '.tracker_script_version // 0' ../master-branch/tracker/package.json)
-
- echo "PR tracker_script_version: $PR_VERSION"
- echo "Master tracker_script_version: $MASTER_VERSION"
-
- # Calculate new version
- NEW_VERSION=$((PR_VERSION + 1))
-
- # Check version conditions
- if [ $PR_VERSION -lt $MASTER_VERSION ]; then
- echo "::error::PR tracker tracker_script_version ($PR_VERSION) is less than master ($MASTER_VERSION) and cannot be incremented."
- echo "::error::Rebase or merge master into your PR to fix this."
- exit 1
- elif [ $NEW_VERSION -eq $((MASTER_VERSION + 1)) ]; then
- echo "Incrementing version from $PR_VERSION to $NEW_VERSION"
- jq ".tracker_script_version = $NEW_VERSION" package.json > package.json.tmp
- mv package.json.tmp package.json
- echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT
- echo "changed=true" >> $GITHUB_OUTPUT
- else
- echo "Already incremented tracker_script_version in PR, skipping."
- echo "version=$PR_VERSION" >> $GITHUB_OUTPUT
- echo "changed=false" >> $GITHUB_OUTPUT
- fi
-
- - name: Commit and push changes
- uses: EndBug/add-and-commit@v9
- if: steps.increment.outputs.changed == 'true'
- with:
- message: 'chore: Bump tracker_script_version to ${{ steps.increment.outputs.version }}'
- github_token: ${{ secrets.PLAUSIBLE_BOT_GITHUB_TOKEN }}
- add: |
- - tracker/package.json
- # Uncomment this once they're whitelisted by CLA agent
- # default_author: github_actions
diff --git a/.github/workflows/tracker.yml b/.github/workflows/tracker.yml
index 30848b4cfb29..af929babfddc 100644
--- a/.github/workflows/tracker.yml
+++ b/.github/workflows/tracker.yml
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
pull_request:
paths:
- - 'tracker/**'
+ - "tracker/**"
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
@@ -14,15 +14,68 @@ jobs:
test:
timeout-minutes: 15
runs-on: ubuntu-latest
+ strategy:
+ fail-fast: false
+ matrix:
+ shardIndex: [1, 2, 3, 4]
+ shardTotal: [4]
steps:
- - uses: actions/checkout@v4
- - uses: actions/setup-node@v4
- with:
- node-version: 16
- - name: Install dependencies
- run: npm --prefix ./tracker ci
- - name: Install Playwright Browsers
- working-directory: ./tracker
- run: npx playwright install --with-deps
- - name: Run Playwright tests
- run: npm --prefix ./tracker test
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ with:
+ node-version: 23.2.0
+ cache: 'npm'
+ cache-dependency-path: tracker/package-lock.json
+ - name: Install dependencies
+ run: npm --prefix ./tracker ci
+ - name: Cache Playwright browsers
+ uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
+ id: playwright-cache
+ with:
+ path: |
+ ~/.cache/ms-playwright
+ ~/.cache/ms-playwright-github
+ key: playwright-${{ runner.os }}-${{ hashFiles('tracker/package-lock.json') }}
+ restore-keys: |
+ playwright-${{ runner.os }}-
+ - name: Install Playwright system dependencies
+ working-directory: ./tracker
+ run: npx playwright install-deps
+ - name: Install Playwright Browsers
+ if: steps.playwright-cache.outputs.cache-hit != 'true'
+ working-directory: ./tracker
+ run: npx playwright install
+ - name: Run Playwright tests
+ run: npm --prefix ./tracker test -- --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }} --reporter=blob
+ - name: Upload blob report to GitHub Actions Artifacts
+ if: ${{ !cancelled() }}
+ uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
+ with:
+ name: blob-report-${{ matrix.shardIndex }}
+ path: tracker/blob-report
+ retention-days: 1
+ merge-sharded-test-report:
+ if: ${{ !cancelled() }}
+ needs: [test]
+ timeout-minutes: 5
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
+ - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6.3.0
+ with:
+ node-version: 23.2.0
+ cache: 'npm'
+ cache-dependency-path: tracker/package-lock.json
+ - name: Install dependencies
+ run: npm --prefix ./tracker ci
+
+ - name: Download blob reports from GitHub Actions Artifacts
+ uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7.0.0
+ with:
+ path: all-blob-reports
+ pattern: blob-report-*
+ merge-multiple: true
+
+ - name: Merge into list report
+ working-directory: ./tracker
+ run: npx playwright merge-reports --reporter list ../all-blob-reports
diff --git a/.gitignore b/.gitignore
index 2e11afb9084b..7d8b7ba3f1a7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -43,7 +43,14 @@ npm-debug.log
# Stored hash of source tracker files used in development environment
# to detect changes in /tracker/src and avoid unnecessary compilation.
-/tracker/dev-compile/last-hash.txt
+/tracker/compiler/last-hash.txt
+# Temporary file used by analyze-sizes.js
+/tracker/compiler/.analyze-sizes.json
+
+# Tracker npm module files that are generated by the compiler for the NPM package
+/tracker/npm_package/plausible.js*
+/tracker/npm_package/plausible.cjs*
+/tracker/npm_package/plausible.d.cts
# test coverage directory
/assets/coverage
@@ -84,8 +91,11 @@ plausible-report.xml
/priv/geodb/*.mmdb.gz
# Auto-generated tracker files
-/priv/tracker/js/*.js
+/priv/tracker/js/plausible*.js*
+/priv/tracker/installation_support/*.js
# Docker volumes
.clickhouse_db_vol*
plausible_db*
+
+.claude
diff --git a/.tool-versions b/.tool-versions
index 862023bb74a2..d1ef1f421bca 100644
--- a/.tool-versions
+++ b/.tool-versions
@@ -1,3 +1,3 @@
-erlang 27.3.1
-elixir 1.18.3-otp-27
+erlang 27.3.4.6
+elixir 1.19.4-otp-27
nodejs 23.2.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index d5fc43bee041..fa4292529979 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,17 +1,102 @@
# Changelog
+
All notable changes to this project will be documented in this file.
## Unreleased
-### Added
+### Added
+
+- Allow querying `views_per_visit` with a time dimension in Stats API
+- Add `bounce_rate` to page-filtered Top Stats even when imports are included, but render a metric warning about imported data not included in `bounce_rate` tooltip.
+- Add `time_on_page` to page-filtered Top Stats even when imports are included, unless legacy time on page is in view.
+- Adds team_id to query debug metadata (saved in system.query_log log_comment column)
+- Add "Unknown" option to Countries shield, for when the country code is unrecognized
+- Add "Last 24 Hours" to dashboard time range picker and Stats API v2
+
+### Removed
+
+### Changed
+
+- Keybind hints are hidden on smaller screens
+- Site index is sortable alphanumerically and by traffic
+
+### Fixed
+
+- Fixed Stats API timeseries returning time buckets falling outside the queried range
+- Fixed issue with all non-interactive events being counted as interactive
+- Fixed countries map countries staying highlighted on Chrome
+
+## v3.2.0 - 2026-01-16
+
+### Added
+
+- A visitor percentage breakdown is now shown on all reports, both on the dashboard and in the detailed breakdown
+- Shared links can now be limited to a particular segment of the data
+
+### Removed
+
+### Changed
+
+- Segment filters are visible to anyone who can view the dashboard with that segment applied, including personal segments on public dashboards
+- When accessing a link to a shared password-protected dashboard subpage (e.g. `.../pages`), the viewer will be redirected to that subpage after providing the password
+
+### Fixed
-### Removed
+- To make internal stats API requests for password-protected shared links, shared link auth cookie must be set in the requests
+- Fixed issue with site guests in Editor role and team members in Editor role not being able to change the domain of site
+- Fixed direct dashboard links that use legacy dashboard filters containing URL encoded special characters (e.g. character `ĂȘ` in the legacy filter `?page=%C3%AA`)
+- Fix bug with tracker script config cache that made requests for certain cached scripts give error 500
-### Changed
+## v3.1.0 - 2025-11-13
+
+### Added
+
+- Custom events can now be marked as non-interactive in events API and tracker script: events marked as non-interactive are not counted towards bounce rate
+- Ability to leave team via Team Settings > Leave Team
+- Stats APIv2 now supports `include.trim_relative_date_range` - this option allows trimming empty values after current time for `day`, `month` and `year` date_range values
+- Properties are now included in full site exports done via Site Settings > Imports & Exports
+- Google Search Console integration settings: properties can be dynamically sought
+- Weekly/monthly e-mail reports now contain top goal conversions
+- Newly created sites are offered a new dynamic tracking script and snippet that's specific to the site
+- Old sites that go to "Review installation" flow are offered the new script and snippet, along with a migration guide from legacy snippets, legacy snippets continue to function as before
+- The new tracker script allows configuring `transformRequest` function to change event payloads before they're sent
+- The new tracker script allows configuring `customProperties` function hook to derive custom props for events on the fly
+- The new tracker script supports tracking form submissions if enabled
+- The new tracker script automatically updates to respect site domain if it's changed in "Change domain" flow
+- The new tracker script automatically updates to respect the following configuration options available in "New site" flows and "Review installation" flows: whether to track outbound links, file downloads, form submissions
+- The new tracker script allows overriding almost all options by changing the snippet on the website, with the function `plausible.init({ ...your overrides... })` - this can be unique page-by-page
+- A new `@plausible-analytics/tracker` ESM module is available on NPM - it has near-identical configuration API and identical tracking logic as the script and it receives bugfixes and updates concurrently with the new tracker script
+- Ability to enforce enabling 2FA by all team members
+
+### Removed
+
+### Changed
+
+- A session is now marked as a bounce if it has less than 2 pageviews and no interactive custom events
+- All dropmenus on dashboard are navigable with Tab (used to be a mix between tab and arrow keys), and no two dropmenus can be open at once on the dashboard
+- Special path-based events like "404" don't need `event.props.path` to be explicitly defined when tracking: it is set to be the same as `event.pathname` in event ingestion; if it is explicitly defined, it is not overridden for backwards compatibility
+- Main graph no longer shows empty values after current time for `day`, `month` and `year` periods
+- Include `bounce_rate` metric in Entry Pages breakdown
+- Dark mode theme has been refined with darker color scheme and better visual hierarchy
+- Creating shared links now happens in a modal
+
+### Fixed
+
+- Make clicking Compare / Disable Comparison in period picker menu close the menu
+- Do not log page views for hidden pages (prerendered pages and new tabs), until pages are viewed
+- Password-authenticated shared links now carry over dashboard params properly
+- Realtime and hourly graphs of visit duration, views per visit no longer overcount due to long-lasting sessions, instead showing each visit when they occurred
+- Fixed realtime and hourly graphs of visits overcounting
+- When reporting only `visitors` and `visits` per hour, count visits in each hour they were active in
+- Fixed unhandled tracker-related exceptions on link clicks within svgs
+- Remove Subscription and Invoices menu from CE
+- Fix email sending error "Mua.SMTPError" 503 Bad sequence of commands
+- Make button to include / exclude imported data visible on Safari
## v3.0.0 - 2025-04-11
### Added
+
- Ability to sort by and compare the `exit_rate` metric in the dashboard Exit Pages > Details report
- Add top 3 pages into the traffic spike email
- Two new shorthand time periods `28d` and `91d` available on both dashboard and in public API
@@ -60,6 +145,7 @@ All notable changes to this project will be documented in this file.
- Always set site and team member limits to unlimited for Community Edition
- Stats API now supports more `date_range` shorthand options like `30d`, `3mo`.
- Stop showing Plausible footer when viewing stats, except when viewing a public dashboard or unembedded shared link dashboard.
+- Changed Plugins API Token creation flow to only display token once it's created.
### Fixed
@@ -112,6 +198,7 @@ All notable changes to this project will be documented in this file.
## v2.1.3 - 2024-09-26
### Fixed
+
- Change cookie key to resolve login issue plausible/analytics#4621
- Set secure attribute on cookies when BASE_URL has HTTPS scheme plausible/analytics#4623
- Don't track custom events in CE plausible/analytics#4627
@@ -119,6 +206,7 @@ All notable changes to this project will be documented in this file.
## v2.1.2 - 2024-09-24
### Added
+
- UI to edit goals along with display names
- Support contains filter for goals
- UI to edit funnels
@@ -137,11 +225,13 @@ All notable changes to this project will be documented in this file.
- Make details views on dashboard sortable
### Removed
+
- Deprecate `ECTO_IPV6` and `ECTO_CH_IPV6` env vars in CE plausible/analytics#4245
- Remove support for importing data from no longer available Universal Analytics
- Soft-deprecate `DATABASE_SOCKET_DIR` plausible/analytics#4202
### Changed
+
- Support Unix sockets in `DATABASE_URL` plausible/analytics#4202
- Realtime and hourly graphs now show visits lasting their whole duration instead when specific events occur
- Increase hourly request limit for API keys in CE from 600 to 1000000 (practically removing the limit) plausible/analytics#4200
@@ -186,6 +276,7 @@ All notable changes to this project will be documented in this file.
## v2.1.0 - 2024-05-23
### Added
+
- Hostname Allow List in Site Settings
- Pages Block List in Site Settings
- Add `conversion_rate` to Stats API Timeseries and on the main graph
@@ -232,6 +323,7 @@ All notable changes to this project will be documented in this file.
- Add custom events support to CSV export and import
### Removed
+
- Removed the nested custom event property breakdown UI when filtering by a goal in Goal Conversions
- Removed the `prop_names` returned in the Stats API `event:goal` breakdown response
- Removed the `prop-breakdown.csv` file from CSV export
@@ -240,6 +332,7 @@ All notable changes to this project will be documented in this file.
- Remove `DISABLE_AUTH` deprecation warning plausible/analytics#3904
### Changed
+
- A visits `entry_page` and `exit_page` is only set and updated for pageviews, not custom events
- Limit the number of Goal Conversions shown on the dashboard and render a "Details" link when there are more entries to show
- Show Outbound Links / File Downloads / 404 Pages / Cloaked Links instead of Goal Conversions when filtering by the corresponding goal
@@ -252,6 +345,7 @@ All notable changes to this project will be documented in this file.
- default `MAILER_ADAPTER` has been changed to `Bamboo.Mua` plausible/analytics#4538
### Fixed
+
- Creating many sites no longer leads to cookie overflow
- Ignore sessions without pageviews for `entry_page` and `exit_page` breakdowns
- Using `VersionedCollapsingMergeTree` to store visit data to avoid rare race conditions that led to wrong visit data being shown
@@ -280,6 +374,7 @@ All notable changes to this project will be documented in this file.
## v2.0.0 - 2023-07-12
### Added
+
- Call to action for tracking Goal Conversions and an option to hide the section from the dashboard
- Add support for `with_imported=true` in Stats API aggregate endpoint
- Ability to use '--' instead of '=' sign in the `tagged-events` classnames
@@ -293,6 +388,7 @@ All notable changes to this project will be documented in this file.
- Allow optional IPv6 for clickhouse repo plausible/analytics#2970
### Fixed
+
- Fix tracker bug - call callback function even when event is ignored
- Make goal-filtered CSV export return only unique_conversions timeseries in the 'visitors.csv' file
- Stop treating page filter as an entry page filter
@@ -313,6 +409,7 @@ All notable changes to this project will be documented in this file.
- Fix a bug where the country name was not shown when [filtering through the map](https://github.com/plausible/analytics/issues/3086)
### Changed
+
- Treat page filter as entry page filter for `bounce_rate`
- Reject events with long URIs and data URIs plausible/analytics#2536
- Always show direct traffic in sources reports plausible/analytics#2531
@@ -323,6 +420,7 @@ All notable changes to this project will be documented in this file.
- Disable registration in self-hosted setups by default plausible/analytics#3014
### Removed
+
- Remove Firewall plug and `IP_BLOCKLIST` environment variable
- Remove the ability to collapse the main graph plausible/analytics#2627
- Remove `custom_dimension_filter` feature flag plausible/analytics#2996
@@ -330,12 +428,14 @@ All notable changes to this project will be documented in this file.
## v1.5.1 - 2022-12-06
### Fixed
+
- Return empty list when breaking down by event:page without events plausible/analytics#2530
- Fallback to empty build metadata when failing to parse $BUILD_METADATA plausible/analytics#2503
## v1.5.0 - 2022-12-02
### Added
+
- Set a different interval on the top graph plausible/analytics#1574 (thanks to @Vigasaurus for this feature)
- A `tagged-events` script extension for out-of-the-box custom event tracking
- The ability to escape `|` characters with `\` in Stats API filter values
@@ -376,6 +476,7 @@ All notable changes to this project will be documented in this file.
- Fix ownership transfer invitation link in self-hosted deployments
### Fixed
+
- Plausible script does not prevent default if it's been prevented by an external script [plausible/analytics#1941](https://github.com/plausible/analytics/issues/1941)
- Hash part of the URL can now be used when excluding pages with `script.exclusions.hash.js`.
- UI fix where multi-line text in pills would not be underlined properly on small screens.
@@ -394,6 +495,7 @@ All notable changes to this project will be documented in this file.
- Ensure newlines from settings files are trimmed [plausible/analytics#2480](https://github.com/plausible/analytics/pull/2480)
### Changed
+
- `script.file-downloads.outbound-links.js` only sends an outbound link event when an outbound download link is clicked
- Plausible script now uses callback navigation (instead of waiting for 150ms every time) when sending custom events
- Cache the tracking script for 24 hours
@@ -406,16 +508,19 @@ All notable changes to this project will be documented in this file.
- Add fallback icon for when DDG favicon cannot be fetched [PR#2279](https://github.com/plausible/analytics#2279)
### Security
+
- Add Content-Security-Policy header to favicon path
## v1.4.1 - 2021-11-29
### Fixed
+
- Fixes database error when pathname contains a question mark
## v1.4.0 - 2021-10-27
### Added
+
- New parameter `metrics` for the `/api/v1/stats/timeseries` endpoint plausible/analytics#952
- CSV export now includes pageviews, bounce rate and visit duration in addition to visitors plausible/analytics#952
- Send stats to multiple dashboards by configuring a comma-separated list of domains plausible/analytics#968
@@ -433,6 +538,7 @@ All notable changes to this project will be documented in this file.
- Add ability to view more than 100 custom goal properties plausible/analytics#1382
### Fixed
+
- Fix weekly report time range plausible/analytics#951
- Make sure embedded dashboards can run when user has blocked third-party cookies plausible/analytics#971
- Sites listing page will paginate if the user has a lot of sites plausible/analytics#994
@@ -449,14 +555,17 @@ All notable changes to this project will be documented in this file.
- Respect the `path` component of BASE_URL to allow subfolder installatons
### Removed
+
- Removes AppSignal monitoring package
### Changes
+
- Disable email verification by default. Added a configuration option `ENABLE_EMAIL_VERIFICATION=true` if you want to keep the old behaviour
## [1.3] - 2021-04-14
### Added
+
- Stats API [currently in beta] plausible/analytics#679
- Ability to view and filter by entry and exit pages, in addition to regular page hits plausible/analytics#712
- 30 day and 6 month keybindings (`T` and `S`, respectively) plausible/analytics#709
@@ -467,6 +576,7 @@ All notable changes to this project will be documented in this file.
- Add name/label to shared links plausible/analytics#910
### Fixed
+
- Capitalized date/time selection keybinds not working plausible/analytics#709
- Invisible text on Google Search Console settings page in dark mode plausible/analytics#759
- Disable analytics tracking when running Cypress tests
@@ -478,8 +588,9 @@ All notable changes to this project will be documented in this file.
## [1.2] - 2021-01-26
### Added
+
- Ability to add event metadata plausible/analytics#381
-- Add tracker module to automatically track outbound links plausible/analytics#389
+- Add tracker module to automatically track outbound links plausible/analytics#389
- Display weekday on the visitor graph plausible/analytics#175
- Collect and display browser & OS versions plausible/analytics#397
- Simple notifications around traffic spikes plausible/analytics#453
@@ -492,6 +603,7 @@ All notable changes to this project will be documented in this file.
- Keybindings for selecting dates/ranges plausible/analytics#630
### Changed
+
- Use alpine as base image to decrease Docker image size plausible/analytics#353
- Ignore automated browsers (Phantom, Selenium, Headless Chrome, etc)
- Display domain's favicon on the home page
@@ -508,6 +620,7 @@ All notable changes to this project will be documented in this file.
- Changed caret/chevron color in datepicker and filters dropdown
### Fixed
+
- Do not error when activating an already activated account plausible/analytics#370
- Ignore arrow keys when modifier keys are pressed plausible/analytics#363
- Show correct stats when goal filter is combined with source plausible/analytics#374
@@ -521,20 +634,24 @@ All notable changes to this project will be documented in this file.
- Various UI/UX issues plausible/analytics#503
### Security
+
- Do not run the plausible Docker container as root plausible/analytics#362
## [1.1.1] - 2020-10-14
### Fixed
+
- Revert Dockerfile change that introduced a regression
## [1.1.0] - 2020-10-14
### Added
+
- Linkify top pages [plausible/analytics#91](https://github.com/plausible/analytics/issues/91)
-- Filter by country, screen size, browser and operating system [plausible/analytics#303](https://github.com/plausible/analytics/issues/303)
+- Filter by country, screen size, browser and operating system [plausible/analytics#303](https://github.com/plausible/analytics/issues/303)
### Fixed
+
- Fix issue with creating a PostgreSQL database when `?ssl=true` [plausible/analytics#347](https://github.com/plausible/analytics/issues/347)
- Do no disclose current URL to DuckDuckGo's favicon service [plausible/analytics#343](https://github.com/plausible/analytics/issues/343)
- Updated UAInspector database to detect newer devices [plausible/analytics#309](https://github.com/plausible/analytics/issues/309)
@@ -542,9 +659,11 @@ All notable changes to this project will be documented in this file.
## [1.0.0] - 2020-10-06
### Added
+
- Collect and present link tags (`utm_medium`, `utm_source`, `utm_campaign`) in the dashboard
### Changed
+
- Replace configuration parameters `CLICKHOUSE_DATABASE_{HOST,NAME,USER,PASSWORD}` with a single `CLICKHOUSE_DATABASE_URL` [plausible/analytics#317](https://github.com/plausible/analytics/pull/317)
- Disable subscriptions by default
- Remove `CLICKHOUSE_DATABASE_POOLSIZE`, `DATABASE_POOLSIZE` and `DATABASE_TLS_ENABLED` parameters. Use query parameters in `CLICKHOUSE_DATABASE_URL` and `DATABASE_URL` instead.
diff --git a/Dockerfile b/Dockerfile
index 19145a3e045a..950a429b6e79 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,8 +1,10 @@
# we can not use the pre-built tar because the distribution is
# platform specific, it makes sense to build it in the docker
+ARG ALPINE_VERSION=3.22.2
+
#### Builder
-FROM hexpm/elixir:1.18.3-erlang-27.3.1-alpine-3.21.3 AS buildcontainer
+FROM hexpm/elixir:1.19.4-erlang-27.3.4.6-alpine-${ALPINE_VERSION} AS buildcontainer
ARG MIX_ENV=ce
@@ -20,7 +22,7 @@ RUN mkdir /app
WORKDIR /app
# install build dependencies
-RUN apk add --no-cache git "nodejs-current=23.2.0-r1" yarn npm python3 ca-certificates wget gnupg make gcc libc-dev brotli
+RUN apk add --no-cache git "nodejs-current=23.11.1-r0" yarn npm python3 ca-certificates wget gnupg make gcc libc-dev brotli
COPY mix.exs ./
COPY mix.lock ./
@@ -54,7 +56,7 @@ COPY rel rel
RUN mix release plausible
# Main Docker Image
-FROM alpine:3.21.3
+FROM alpine:${ALPINE_VERSION}
LABEL maintainer="plausible.io "
ARG BUILD_METADATA={}
@@ -86,3 +88,4 @@ EXPOSE 8000
ENV DEFAULT_DATA_DIR=/var/lib/plausible
VOLUME /var/lib/plausible
CMD ["run"]
+
diff --git a/Makefile b/Makefile
index d755b1a1c349..167706e68ac8 100644
--- a/Makefile
+++ b/Makefile
@@ -1,4 +1,10 @@
-.PHONY: help install server clickhouse clickhouse-prod clickhouse-stop postgres postgres-client postgres-prod postgres-stop
+.PHONY: help install server clickhouse clickhouse-prod clickhouse-stop clickhouse-postgres-remote postgres postgres-client postgres-prod postgres-stop
+
+require = \
+ $(foreach 1,$1,$(__require))
+__require = \
+ $(if $(value $1),, \
+ $(error Provide required parameter: $1$(if $(value 2), ($(strip $2)))))
help:
@perl -nle'print $& if m{^[a-zA-Z_-]+:.*?## .*$$}' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
@@ -15,30 +21,34 @@ install: ## Run the initial setup
server: ## Start the web server
mix phx.server
-CH_FLAGS ?= --detach -p 8123:8123 -p 9000:9000 --ulimit nofile=262144:262144 --name plausible_clickhouse
+CH_FLAGS ?= --detach -p 8123:8123 -p 9000:9000 --ulimit nofile=262144:262144 --name plausible_clickhouse --env CLICKHOUSE_SKIP_USER_SETUP=1
clickhouse: ## Start a container with a recent version of clickhouse
- docker run $(CH_FLAGS) --network host --volume=$$PWD/.clickhouse_db_vol:/var/lib/clickhouse clickhouse/clickhouse-server:latest-alpine
+ docker run $(CH_FLAGS) --network host --volume=$$PWD/.clickhouse_db_vol:/var/lib/clickhouse --volume=$$PWD/.clickhouse_config:/etc/clickhouse-server/config.d clickhouse/clickhouse-server:latest-alpine
clickhouse-client: ## Connect to clickhouse
docker exec -it plausible_clickhouse clickhouse-client -d plausible_events_db
clickhouse-prod: ## Start a container with the same version of clickhouse as the one in prod
- docker run $(CH_FLAGS) --volume=$$PWD/.clickhouse_db_vol_prod:/var/lib/clickhouse clickhouse/clickhouse-server:24.12.2.29-alpine
+ docker run $(CH_FLAGS) --volume=$$PWD/.clickhouse_db_vol_prod:/var/lib/clickhouse clickhouse/clickhouse-server:25.11.5.8-alpine
clickhouse-stop: ## Stop and remove the clickhouse container
docker stop plausible_clickhouse && docker rm plausible_clickhouse
+clickhouse-postgres-remote: ## Create postgres_remote database in ClickHouse for querying PostgreSQL
+ $(eval POSTGRES_IP := $(shell docker inspect plausible_db --format '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}'))
+ @docker exec plausible_clickhouse clickhouse-client --query "DROP DATABASE IF EXISTS postgres_remote; CREATE DATABASE postgres_remote ENGINE = PostgreSQL('$(POSTGRES_IP):5432', 'plausible_dev', 'postgres', 'postgres');"
+
PG_FLAGS ?= --detach -e POSTGRES_PASSWORD="postgres" -p 5432:5432 --name plausible_db
postgres: ## Start a container with a recent version of postgres
- docker run $(PG_FLAGS) --volume=plausible_db:/var/lib/postgresql/data postgres:latest
+ docker run $(PG_FLAGS) --volume=plausible_db:/var/lib/postgresql/docker postgres:latest
postgres-client: ## Connect to postgres
docker exec -it plausible_db psql -U postgres -d plausible_dev
postgres-prod: ## Start a container with the same version of postgres as the one in prod
- docker run $(PG_FLAGS) --volume=plausible_db_prod:/var/lib/postgresql/data postgres:15
+ docker run $(PG_FLAGS) --volume=plausible_db_prod:/var/lib/postgresql/docker postgres:18
postgres-stop: ## Stop and remove the postgres container
docker stop plausible_db && docker rm plausible_db
@@ -56,3 +66,50 @@ minio: ## Start a transient container with a recent version of minio (s3)
minio-stop: ## Stop and remove the minio container
docker stop plausible_minio
+
+sso:
+ $(call require, integration_id)
+ @echo "Setting up local IdP service..."
+ @docker run --name=idp \
+ -p 8080:8080 \
+ -e SIMPLESAMLPHP_SP_ENTITY_ID=http://localhost:8000/sso/$(integration_id) \
+ -e SIMPLESAMLPHP_SP_ASSERTION_CONSUMER_SERVICE=http://localhost:8000/sso/saml/consume/$(integration_id) \
+ -v $$PWD/extra/fixture/authsources.php:/var/www/simplesamlphp/config/authsources.php -d kenchan0130/simplesamlphp
+
+ @sleep 2
+
+ @echo "Use the following IdP configuration:"
+ @echo ""
+ @echo "Sign-in URL: http://localhost:8080/simplesaml/saml2/idp/SSOService.php"
+ @echo ""
+ @echo "Entity ID: http://localhost:8080/simplesaml/saml2/idp/metadata.php"
+ @echo ""
+ @echo "PEM Certificate:"
+ @curl http://localhost:8080/simplesaml/module.php/saml/idp/certs.php/idp.crt 2>/dev/null
+ @echo ""
+ @echo ""
+ @echo "Following accounts are configured:"
+ @echo "- user@plausible.test / plausible"
+ @echo "- user1@plausible.test / plausible"
+ @echo "- user2@plausible.test / plausible"
+
+sso-stop:
+ docker stop idp
+ docker remove idp
+
+generate-corefile:
+ $(call require, domain_id)
+ domain_id=$(domain_id) envsubst < $(PWD)/extra/fixture/Corefile.template > $(PWD)/extra/fixture/Corefile.gen.$(domain_id)
+
+mock-dns: generate-corefile
+ $(call require, domain_id)
+ docker run --rm -p 5354:53/udp -v $(PWD)/extra/fixture/Corefile.gen.$(domain_id):/Corefile coredns/coredns:latest -conf Corefile
+
+loadtest-server:
+ @echo "Ensure your OTP installation is built with --enable-lock-counter"
+ MIX_ENV=load ERL_FLAGS="-emu_type lcnt +Mdai max" iex -S mix do phx.digest + phx.server
+
+loadtest-client:
+ @echo "Set your limits for file descriptors/ephemeral ports high... Test begins shortly"
+ @sleep 5
+ k6 run test/load/script.js
diff --git a/README.md b/README.md
index 80e6efb12f70..45d99a163fbf 100644
--- a/README.md
+++ b/README.md
@@ -30,10 +30,10 @@ Here's what makes Plausible a great Google Analytics alternative and why we're t
- **Lightweight**: Plausible Analytics works by loading a script on your website, like Google Analytics. Our script is [small](https://plausible.io/lightweight-web-analytics), making your website quicker to load. You can also send events directly to our [events API](https://plausible.io/docs/events-api).
- **Email or Slack reports**: Keep an eye on your traffic with weekly and/or monthly email or Slack reports. You can also get traffic spike notifications.
- **Invite team members and share stats**: You have the option to be transparent and open your web analytics to everyone. Your website stats are private by default but you can choose to make them public so anyone with your custom link can view them. You can [invite team members](https://plausible.io/docs/users-roles) and assign user roles too.
-- **Define key goals and track conversions**: Create custom events with custom dimensions to track conversions and attribution to understand and identify the trends that matter. Track ecommerce revenue, outbound link clicks, file downloads and 404 error pages. Increase conversions using funnel analysis.
+- **Define key goals and track conversions**: Create custom events with custom dimensions to track conversions and attribution to understand and identify the trends that matter. Track ecommerce revenue, outbound link clicks, form completions, file downloads and 404 error pages. Increase conversions using funnel analysis.
- **Search keywords**: Integrate your dashboard with Google Search Console to get the most accurate reporting on your search keywords.
- **SPA support**: Plausible is built with modern web frameworks in mind and it works automatically with any pushState based router on the frontend. We also support frameworks that use the URL hash for routing. See [our documentation](https://plausible.io/docs/hash-based-routing).
-- **Smooth transition from Google Analytics**: There's a realtime dashboard, entry pages report and integration with Search Console. You can track your paid campaigns and conversions. You can invite team members. You can even [import your historical Google Analytics stats](https://plausible.io/docs/google-analytics-import). Learn how to [get the most out of your Plausible experience](https://plausible.io/docs/your-plausible-experience) and join thousands who have already migrated from Google Analytics.
+- **Smooth transition from Google Analytics**: There's a realtime dashboard, entry pages report and integration with Search Console. You can track your paid campaigns and conversions. You can invite team members. You can even [import your historical Google Analytics stats](https://plausible.io/docs/google-analytics-import) and there's [a Google Tag Manager template](https://plausible.io/gtm-template) too. Learn how to [get the most out of your Plausible experience](https://plausible.io/docs/your-plausible-experience) and join thousands who have already migrated from Google Analytics.
Interested to learn more? [Read more on our website](https://plausible.io), learn more about the team and the goals of the project on [our about page](https://plausible.io/about) or explore [the documentation](https://plausible.io/docs).
@@ -63,10 +63,10 @@ Plausible is [open source web analytics](https://plausible.io/open-source-websit
| ------------- | ------------- | ------------- |
| **Infrastructure management** | Easy and convenient. It takes 2 minutes to start counting your stats with a worldwide CDN, high availability, backups, security and maintenance all done for you by us. We manage everything so you donât have to worry about anything and can focus on your stats. | You do it all yourself. You need to get a server and you need to manage your infrastructure. You are responsible for installation, maintenance, upgrades, server capacity, uptime, backup, security, stability, consistency, loading time and so on.|
| **Release schedule** | Continuously developed and improved with new features and updates multiple times per week. | [It's a long term release](https://plausible.io/blog/building-open-source) published twice per year so latest features and improvements won't be immediately available.|
-| **Premium features** | All features available as listed in [our pricing plans](https://plausible.io/#pricing). | Selected premium features such as funnels and ecommerce revenue goals are not available as we aim to ensure a [protective barrier around our cloud offering](https://plausible.io/blog/community-edition).|
-| **Bot filtering** | Advanced bot filtering for more accurate stats. Our algorithm detects and excludes non-human traffic patterns. We also exclude known bots by the User-Agent header and filter out traffic from data centers and referrer spam domains. | Basic bot filtering that targets the most common non-human traffic based on the User-Agent header and referrer spam domains.|
+| **Premium features** | All features available as listed in [our pricing plans](https://plausible.io/#pricing). | Premium features (marketing funnels, ecommerce revenue goals, SSO and sites API) are not available in order to help support [the project's long-term sustainability](https://plausible.io/blog/community-edition).|
+| **Bot filtering** | Advanced bot filtering for more accurate stats. Our algorithm detects and excludes non-human traffic patterns. We also exclude known bots by the User-Agent header and filter out traffic from data centers and referrer spam domains. We exclude ~32K data center IP ranges (i.e. a lot of bot IP addresses) by default. | Basic bot filtering that targets the most common non-human traffic based on the User-Agent header and referrer spam domains.|
| **Server location** | All visitor data is exclusively processed on EU-owned cloud infrastructure. We keep your site data on a secure, encrypted and green energy powered server in Germany. This ensures that your site data is protected by the strict European Union data privacy laws and ensures compliance with GDPR. Your website data never leaves the EU. | You have full control and can host your instance on any server in any country that you wish. Host it on a server in your basement or host it with any cloud provider wherever you want, even those that are not GDPR compliant.|
-| **Data portability** | You see all your site stats and metrics on our modern-looking, simple to use and fast loading dashboard. You can only see the stats aggregated in the dashboard. You can download the stats using the [CSV export](https://plausible.io/docs/export-stats), [stats API](https://plausible.io/docs/stats-api) or tools such as the [Data Studio Connector](https://plausible.io/docs/integration-guides#google-data-studio). | Do you want access to the raw data? Self-hosting gives you that option. You can take the data directly from the ClickHouse database. |
+| **Data portability** | You see all your site stats and metrics on our modern-looking, simple to use and fast loading dashboard. You can only see the stats aggregated in the dashboard. You can download the stats using the [CSV export](https://plausible.io/docs/export-stats), [stats API](https://plausible.io/docs/stats-api) or the [Looker Studio Connector](https://plausible.io/docs/looker-studio). | Do you want access to the raw data? Self-hosting gives you that option. You can take the data directly from the ClickHouse database. The Looker Studio Connector is not available. |
| **Premium support** | Real support delivered by real human beings who build and maintain Plausible. | Premium support is not included. CE is community supported only.|
| **Costs** | There's a cost associated with providing an analytics service so we charge a subscription fee. We choose the subscription business model rather than the business model of surveillance capitalism. Your money funds further development of Plausible. | You need to pay for your server, CDN, backups and whatever other cost there is associated with running the infrastructure. You never have to pay any fees to us. Your money goes to 3rd party companies with no connection to us.|
diff --git a/assets/.stylelintrc.json b/assets/.stylelintrc.json
index 799642a7b84b..51fbcf990f81 100644
--- a/assets/.stylelintrc.json
+++ b/assets/.stylelintrc.json
@@ -3,7 +3,7 @@
"ignoreFiles": ["./node_modules/**/*.*"],
"rules": {
"import-notation": "string",
- "at-rule-no-unknown": [true, { "ignoreAtRules": ["apply", "screen"] }],
+ "at-rule-no-unknown": [true, { "ignoreAtRules": ["apply", "screen", "plugin", "source", "theme", "utility", "custom-variant"] }],
"at-rule-empty-line-before": [
"always",
{ "except": ["blockless-after-same-name-blockless"], "ignoreAtRules": ["apply"] }
diff --git a/assets/css/app.css b/assets/css/app.css
index eb78a69a13a9..bdff4680350d 100644
--- a/assets/css/app.css
+++ b/assets/css/app.css
@@ -1,15 +1,109 @@
-@import 'tailwindcss/base';
-@import 'flatpickr/dist/flatpickr.min.css';
-@import './modal.css';
-@import './loader.css';
-@import './tooltip.css';
-@import './flatpickr-colors.css';
-@import './chartjs.css';
-@import 'tailwindcss/components';
-@import 'tailwindcss/utilities';
+@import 'tailwindcss' source(none);
+@import 'flatpickr/dist/flatpickr.min.css' layer(components);
+@import './modal.css' layer(components);
+@import './loader.css' layer(components);
+@import './tooltip.css' layer(components);
+@import './flatpickr-colors.css' layer(components);
+
+@plugin "@tailwindcss/forms";
+
+@source "../css";
+@source "../js";
+@source "../../lib/plausible_web";
+@source "../../extra/lib/plausible_web";
+
+/* Tailwind v3 compatibility: restore v3 default border and ring styling */
+
+@layer base {
+ *,
+ ::after,
+ ::before,
+ ::backdrop,
+ ::file-selector-button {
+ border-color: var(--color-gray-200, currentColor);
+ }
-[x-cloak] {
- display: none;
+ button:not(:disabled),
+ [role='button']:not(:disabled) {
+ cursor: pointer;
+ }
+
+ *:focus-visible {
+ @apply ring-2 ring-indigo-500 ring-offset-2 dark:ring-offset-gray-900 outline-none;
+ }
+
+ :focus:not(:focus-visible) {
+ @apply outline-none;
+ }
+}
+
+@layer components {
+ /* Replace Tailwind form plugin's focus with focus-visible */
+ [type='checkbox']:focus,
+ [type='radio']:focus {
+ outline: none;
+ box-shadow: none;
+ }
+
+ [type='checkbox']:focus-visible,
+ [type='radio']:focus-visible {
+ @apply ring-2 ring-indigo-500 ring-offset-2 outline-none;
+ }
+}
+
+@theme {
+ /* Color aliases from tailwind.config.js */
+
+ /* yellow: colors.amber - Map yellow to amber colors */
+ --color-yellow-50: var(--color-amber-50);
+ --color-yellow-100: var(--color-amber-100);
+ --color-yellow-200: var(--color-amber-200);
+ --color-yellow-300: var(--color-amber-300);
+ --color-yellow-400: var(--color-amber-400);
+ --color-yellow-500: var(--color-amber-500);
+ --color-yellow-600: var(--color-amber-600);
+ --color-yellow-700: var(--color-amber-700);
+ --color-yellow-800: var(--color-amber-800);
+ --color-yellow-900: var(--color-amber-900);
+ --color-yellow-950: var(--color-amber-950);
+
+ /* green: colors.emerald - Map green to emerald colors */
+ --color-green-50: var(--color-emerald-50);
+ --color-green-100: var(--color-emerald-100);
+ --color-green-200: var(--color-emerald-200);
+ --color-green-300: var(--color-emerald-300);
+ --color-green-400: var(--color-emerald-400);
+ --color-green-500: var(--color-emerald-500);
+ --color-green-600: var(--color-emerald-600);
+ --color-green-700: var(--color-emerald-700);
+ --color-green-800: var(--color-emerald-800);
+ --color-green-900: var(--color-emerald-900);
+ --color-green-950: var(--color-emerald-950);
+
+ /* gray: colors.zinc - Map gray to zinc colors */
+ --color-gray-50: var(--color-zinc-50);
+ --color-gray-100: var(--color-zinc-100);
+ --color-gray-200: var(--color-zinc-200);
+ --color-gray-300: var(--color-zinc-300);
+ --color-gray-400: var(--color-zinc-400);
+ --color-gray-500: var(--color-zinc-500);
+ --color-gray-600: var(--color-zinc-600);
+ --color-gray-700: var(--color-zinc-700);
+ --color-gray-800: var(--color-zinc-800);
+ --color-gray-900: var(--color-zinc-900);
+ --color-gray-950: var(--color-zinc-950);
+
+ /* Custom gray shades from config (override some zinc values) */
+ --color-gray-75: rgb(247 247 248);
+ --color-gray-150: rgb(236 236 238);
+ --color-gray-750: rgb(50 50 54);
+ --color-gray-825: rgb(35 35 38);
+ --color-gray-850: rgb(34 34 38);
+
+ /* Set v3 default ring behavior */
+ --default-ring-width: 2px;
+ --default-ring-color: var(--color-indigo-500);
+ --animate-pulse: pulse 1.5s cubic-bezier(0.4, 0, 0.6, 1) infinite;
}
@media print {
@@ -19,28 +113,27 @@
}
}
-.button {
- @apply inline-flex justify-center px-4 py-2 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md leading-5 transition hover:bg-indigo-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-indigo-500;
+@utility container {
+ margin-inline: auto;
+ padding-inline: 1rem;
}
-.button[disabled] {
- @apply bg-gray-400 dark:bg-gray-600;
-}
+@custom-variant dark (&:where(.dark, .dark *));
+@custom-variant phx-click-loading (.phx-click-loading&, .phx-click-loading &);
+@custom-variant phx-hook-loading (.phx-hook-loading&, .phx-hook-loading &);
+@custom-variant phx-submit-loading (.phx-submit-loading&, .phx-submit-loading &);
+@custom-variant phx-change-loading (.phx-change-loading&, .phx-change-loading &);
-.button-outline {
- @apply text-indigo-600 bg-transparent border border-indigo-600;
-}
-
-.button-outline:hover {
- @apply text-white;
+[x-cloak] {
+ display: none;
}
-.button-sm {
- @apply px-4 py-2 text-sm;
+.button {
+ @apply inline-flex justify-center px-3.5 py-2.5 text-sm font-medium text-white bg-indigo-600 border border-transparent rounded-md leading-5 transition hover:bg-indigo-700;
}
-.button-md {
- @apply px-4 py-2;
+.button[disabled] {
+ @apply bg-gray-400 dark:bg-gray-600;
}
html {
@@ -54,30 +147,12 @@ body {
overflow-x: hidden;
}
-blockquote {
- @apply px-4 py-2 my-4 border-l-4 border-gray-500;
-}
-
-@screen xl {
+@media (width >= 1280px) {
.container {
max-width: 70rem;
}
}
-.pricing-table {
- height: 920px;
-}
-
-@screen md {
- .pricing-table {
- height: auto;
- }
-}
-
-.light-text {
- color: #f0f4f8;
-}
-
.transition {
transition: all 0.1s ease-in;
}
@@ -147,41 +222,6 @@ blockquote {
}
}
-.just-text h1,
-.just-text h2,
-.just-text h3 {
- margin-top: 1em;
- margin-bottom: 0.5em;
-}
-
-.just-text p {
- margin-top: 0;
- margin-bottom: 1rem;
-}
-
-.dropdown-content::before {
- top: -16px;
- right: 8px;
- left: auto;
- border: 8px solid transparent;
- border-bottom-color: rgb(27 31 35 / 15%);
-}
-
-.dropdown-content::before,
-.dropdown-content::after {
- position: absolute;
- display: inline-block;
- content: '';
-}
-
-.dropdown-content::after {
- top: -14px;
- right: 9px;
- left: auto;
- border: 7px solid transparent;
- border-bottom-color: #fff;
-}
-
.feather {
height: 1em;
width: 1em;
@@ -196,43 +236,12 @@ blockquote {
display: inline;
}
-.table-striped tbody tr:nth-child(odd) {
- background-color: #f1f5f8;
-}
-
-.dark .table-striped tbody tr:nth-child(odd) {
- background-color: rgb(37 47 63);
-}
-
-.dark .table-striped tbody tr:nth-child(even) {
- background-color: rgb(26 32 44);
-}
-
-.stats-item {
- min-height: 436px;
-}
-
-@screen md {
- .stats-item {
- margin-left: 6px;
- margin-right: 6px;
- width: calc(50% - 6px);
- position: relative;
- min-height: initial;
- height: 27.25rem;
- }
-
- .stats-item-header {
- height: inherit;
- }
+.table-striped tbody tr:nth-child(odd) td {
+ background-color: var(--color-gray-75);
}
-.stats-item:first-child {
- margin-left: 0;
-}
-
-.stats-item:last-child {
- margin-right: 0;
+.dark .table-striped tbody tr:nth-child(odd) td {
+ background-color: var(--color-gray-850);
}
.fade-enter {
@@ -244,24 +253,6 @@ blockquote {
transition: opacity 100ms ease-in;
}
-.datamaps-subunit {
- cursor: pointer;
-}
-
-.fullwidth-shadow::before {
- @apply absolute top-0 w-screen h-full bg-gray-50 dark:bg-gray-850;
-
- box-shadow: 0 4px 2px -2px rgb(0 0 0 / 6%);
- content: '';
- z-index: -1;
- left: calc(-1 * calc(50vw - 50%));
- background-color: inherit;
-}
-
-.dark .fullwidth-shadow::before {
- box-shadow: 0 4px 2px -2px rgb(200 200 200 / 10%);
-}
-
iframe[hidden] {
display: none;
}
@@ -270,54 +261,8 @@ iframe[hidden] {
@apply cursor-default bg-gray-100 dark:bg-gray-300 pointer-events-none;
}
-#chartjs-tooltip {
- background-color: rgba(25 30 56);
- position: absolute;
- font-size: 14px;
- font-style: normal;
- padding: 10px 12px;
- pointer-events: none;
- border-radius: 5px;
- z-index: 100;
-}
-
-.active-prop-heading {
- /* Properties related to text-decoration are all here in one place. TailwindCSS does support underline but that's about it. */
- text-decoration-line: underline;
- text-decoration-color: #4338ca; /* tailwind's indigo-700 */
- text-decoration-thickness: 2px;
-}
-
-@media (prefers-color-scheme: dark) {
- .active-prop-heading {
- text-decoration-color: #6366f1; /* tailwind's indigo-500 */
- }
-}
-
/* This class is used for styling embedded dashboards. Do not remove. */
/* stylelint-disable */
/* prettier-ignore */
.date-option-group { }
/* stylelint-enable */
-
-.popper-tooltip {
- background-color: rgba(25 30 56);
-}
-
-.tooltip-arrow,
-.tooltip-arrow::before {
- position: absolute;
- width: 10px;
- height: 10px;
- background: inherit;
-}
-
-.tooltip-arrow {
- visibility: hidden;
-}
-
-.tooltip-arrow::before {
- visibility: visible;
- content: '';
- transform: rotate(45deg) translateY(1px);
-}
diff --git a/assets/css/chartjs.css b/assets/css/chartjs.css
deleted file mode 100644
index 569d39dd96f7..000000000000
--- a/assets/css/chartjs.css
+++ /dev/null
@@ -1,10 +0,0 @@
-#chartjs-tooltip {
- background-color: rgb(25 30 56);
- position: absolute;
- font-size: 14px;
- font-style: normal;
- padding: 10px 12px;
- pointer-events: none;
- border-radius: 5px;
- z-index: 100;
-}
diff --git a/assets/css/loader.css b/assets/css/loader.css
index 2c041f35e13b..2f658fb3eef4 100644
--- a/assets/css/loader.css
+++ b/assets/css/loader.css
@@ -5,8 +5,8 @@
}
.loading.sm {
- width: 25px;
- height: 25px;
+ width: 20px;
+ height: 20px;
}
.loading div {
@@ -25,8 +25,8 @@
}
.loading.sm div {
- width: 25px;
- height: 25px;
+ width: 20px;
+ height: 20px;
}
@keyframes spin {
diff --git a/assets/css/modal.css b/assets/css/modal.css
index 500ffab9a079..7fa04121b426 100644
--- a/assets/css/modal.css
+++ b/assets/css/modal.css
@@ -28,37 +28,10 @@
position: fixed;
inset: 0;
background: rgb(0 0 0 / 60%);
- z-index: 99;
+ z-index: 999;
overflow: auto;
}
-.modal__container {
- background-color: #fff;
- padding: 1rem 2rem;
- border-radius: 4px;
- margin: 50px auto;
- box-sizing: border-box;
- min-height: 509px;
- transition: height 200ms ease-in;
-}
-
-.modal__close {
- position: fixed;
- color: #b8c2cc;
- font-size: 48px;
- font-weight: bold;
- top: 12px;
- right: 24px;
-}
-
-.modal__close::before {
- content: '\2715';
-}
-
-.modal__content {
- margin-bottom: 2rem;
-}
-
@keyframes mm-fade-in {
from {
opacity: 0;
diff --git a/assets/css/storybook.css b/assets/css/storybook.css
index e78c04fc7d86..2764e94fa378 100644
--- a/assets/css/storybook.css
+++ b/assets/css/storybook.css
@@ -1,10 +1,8 @@
-@import 'tailwindcss/base';
-@import 'tailwindcss/components';
-@import 'tailwindcss/utilities';
+@import 'tailwindcss' source(none);
/*
* Put your component styling within the Tailwind utilities layer.
- * See the https://hexdocs.pm/phoenix_storybook/sandboxing.html guide for more info.
+* See the https://hexdocs.pm/phoenix_storybook/sandboxing.html guide for more info.
*/
@layer utilities {
diff --git a/assets/jest.config.json b/assets/jest.config.json
index 37c13e8d4113..97f7025e3163 100644
--- a/assets/jest.config.json
+++ b/assets/jest.config.json
@@ -8,6 +8,7 @@
},
"setupFilesAfterEnv": [
"/test-utils/extend-expect.ts",
+ "/test-utils/jsdom-mocks.ts",
"/test-utils/reset-state.ts"
],
"transform": {
diff --git a/assets/js/app.js b/assets/js/app.js
index 4224829b81c8..f73084a96689 100644
--- a/assets/js/app.js
+++ b/assets/js/app.js
@@ -115,7 +115,7 @@ if (embedButton) {
embedLink.searchParams.set('background', background)
}
- embedCode.value = `
+ embedCode.value = `