diff --git a/docs/main/administration-guide/administration-guide-index.mdx b/docs/main/administration-guide/administration-guide-index.mdx index 4523d8c35654..8f08ade4f927 100644 --- a/docs/main/administration-guide/administration-guide-index.mdx +++ b/docs/main/administration-guide/administration-guide-index.mdx @@ -6,11 +6,11 @@ Welcome to the Mattermost Administration Guide. This guide is organized into sec Whether you’re configuring server settings, managing users, monitoring performance, or ensuring compliance, this guide provides all the information you need. Use the navigation below to access detailed instructions and best practices for each topic. - [Self-hosted billing](/administration-guide/manage/admin/self-hosted-billing) - Billing and payment options for Mattermost self-hosted deployments. -- [Cloud workspace management](/administration-guide/cloud-workspace-management) - Learn how to manage cloud workspaces in Mattermost. +- [Cloud workspace management](/administration-guide/manage/cloud-workspace-management) - Learn how to manage cloud workspaces in Mattermost. - [Server maintenance](/administration-guide/manage/admin/server-maintenance) - Learn about Mattermost server maintenance and best practices. - [Server configuration](/administration-guide/manage/admin/server-configuration) - Learn about server configuration and settings. - [User provisioning](/administration-guide/manage/admin/user-provisioning) - Learn about user provisioning and management. - [User management](/administration-guide/manage/admin/user-management) - Learn about user management and best practices. - [Monitoring and performance](/administration-guide/manage/admin/monitoring-and-performance) - Learn about monitoring and performance optimization. -- [Compliance](/administration-guide/compliance-with-mattermost) - Learn about compliance and security best practices. +- [Compliance](/administration-guide/comply/comply-index) - Learn about compliance and security best practices. - [Migration](/administration-guide/manage/admin/migration) - Learn about migrating to Mattermost. diff --git a/docs/main/administration-guide/compliance-with-mattermost.mdx b/docs/main/administration-guide/comply/comply-index.mdx similarity index 98% rename from docs/main/administration-guide/compliance-with-mattermost.mdx rename to docs/main/administration-guide/comply/comply-index.mdx index 348cc87e2f71..f12188746903 100644 --- a/docs/main/administration-guide/compliance-with-mattermost.mdx +++ b/docs/main/administration-guide/comply/comply-index.mdx @@ -1,5 +1,6 @@ --- title: "Compliance with Mattermost" +sidebar_label: "Comply" --- Mattermost is purpose-built to help enterprises keep sensitive data safe and compliant in the strictest, most complex environments. Mattermost Enterprise Edition includes features designed to make compliance with all relevant regulations and internal policies easy to achieve and maintain. diff --git a/docs/main/administration-guide/configure/email-templates.mdx b/docs/main/administration-guide/configure/email-templates.mdx index 24f6c4ab9dd2..7451f1d348a0 100644 --- a/docs/main/administration-guide/configure/email-templates.mdx +++ b/docs/main/administration-guide/configure/email-templates.mdx @@ -244,7 +244,8 @@ The email templates are located in the Mattermost server directory in the `templ Button -Button for email verification | api.templates.verify_body.button +Button for email verification +api.templates.verify_body.button diff --git a/docs/main/administration-guide/configure/experimental-configuration-settings.mdx b/docs/main/administration-guide/configure/experimental-configuration-settings.mdx index a22429aa8f17..1cfbd3f8f367 100644 --- a/docs/main/administration-guide/configure/experimental-configuration-settings.mdx +++ b/docs/main/administration-guide/configure/experimental-configuration-settings.mdx @@ -13,10 +13,10 @@ Review and manage the following [experimental](/administration-guide/manage/feat -System admins managing a self-hosted Mattermost deployment can edit the `config.json` file as described in the following tables. Each configuration value below includes a JSON path to access the value programmatically in the `config.json` file using a JSON-aware tool. For example, the `EmailBatchingBufferSize` value is under `EmailSettings`. +System admins managing a self-hosted Mattermost deployment can edit the `config.json` file as described in the following tables. Each configuration value below includes a JSON path to access the value programmatically in the `config.json` file using a JSON-aware tool. For example, the `LinkMetadataTimeoutMilliseconds` value is under `ExperimentalSettings`. -- If using a tool such as [jq](https://stedolan.github.io/jq/), you'd enter: `cat config/config.json | jq '.EmailSettings.EmailBatchingBufferSize'` -- When working with the `config.json` file manually, look for an object such as `EmailSettings`, then within that object, find the key `EmailBatchingBufferSize`. +- If using a tool such as [jq](https://stedolan.github.io/jq/), you'd enter: `cat config/config.json | jq '.ExperimentalSettings.LinkMetadataTimeoutMilliseconds'` +- When working with the `config.json` file manually, look for an object such as `ExperimentalSettings`, then within that object, find the key `LinkMetadataTimeoutMilliseconds`. @@ -56,51 +56,6 @@ Adds a configurable timeout for requests made to return link metadata. If the me -### Email batching buffer size - -Specify the maximum number of notifications batched into a single email. - - - -- We recommend increasing the buffer size from the default value if you see the following error in the Mattermost logs: `Email batching job's receiving buffer was full. Please increase the EmailBatchingBufferSize. Falling back to sending immediate mail.` Increasing this value will ensure emails are queued up, without impacting server performance. -- Notifications will be sent instantly if the queue of emails exceeds the [email batching interval](#email-batching-interval) configured. - - - - --- - - - - - -
This feature's config.json setting is EmailBatchingBufferSize: 256`` with numerical input.
- -### Email batching interval - -Specify the maximum frequency, in seconds, which the batching job checks for new notifications. - - - -- We recommend decreasing the email batching interval from the default value if you see the following error in the Mattermost logs: `Email batching job's receiving buffer was full. Please increase the EmailBatchingBufferSize. Falling back to sending immediate mail.`. -- Longer batching intervals may increase performance. -- Notifications will be sent instantly if the [queue of emails](#email-batching-buffer-size) exceeds the email batching interval configured. - - - - --- - - - - - -
This feature's config.json setting is EmailBatchingInterval": 30 with numerical input.
- ### Enable automatic replies **True**: Users can enable Automatic Replies in **Settings \> Notifications**. Users set a custom message that will be automatically sent in response to Direct Messages. @@ -202,23 +157,6 @@ If the team URL of the primary team is `https://example.mattermost.com/myteam/`, -### Use channel name in email notifications - -**True**: Channel and team name appears in email notification subject lines. Useful for servers using only one team. - -**False**: Only team name appears in email notification subject line. - - --- - - - - - -
This feature's config.json setting is "UseChannelInEmailNotifications": false with options true and false.
- ### Disable data refetching on browser refocus This setting disables re-fetching of channel and channel members on browser focus. diff --git a/docs/main/administration-guide/configure/install-boards.mdx b/docs/main/administration-guide/configure/install-boards.mdx index d46f9d94442d..6e127cd7c7d5 100644 --- a/docs/main/administration-guide/configure/install-boards.mdx +++ b/docs/main/administration-guide/configure/install-boards.mdx @@ -37,4 +37,4 @@ See the [navigate boards](/end-user-guide/project-management/navigate-boards) do ## Work with boards -See the [project and task management](/end-user-guide/project-task-management) end user documentation to learn how to align, define, organize, track, and manage work across teams. +See the [project and task management](/end-user-guide/project-management/project-management-index) end user documentation to learn how to align, define, organize, track, and manage work across teams. diff --git a/docs/main/administration-guide/configure/optimize-your-workspace.mdx b/docs/main/administration-guide/configure/optimize-your-workspace.mdx index 51a254c9896c..6edb7ddfc667 100644 --- a/docs/main/administration-guide/configure/optimize-your-workspace.mdx +++ b/docs/main/administration-guide/configure/optimize-your-workspace.mdx @@ -31,37 +31,18 @@ The following optimization areas can alert you to workspace suggestions, warning - + -
-

Optimization category

-
-
-
=======================
-
-

Mattermost release

-
-
-
-

Suggestions, Warnings, or Problems Detected

-
-
-
==========================================================================================================
-
-

Are you on the latest Mattermost release?

-
-
-
-

Additional Information |

-
-
-
======================================================================================================================================================================+
-
-

You're notified when updates are available. | See the Upgrade Mattermost product documentation for details on upgrading your workspace. |

-
-
+Optimization category +Suggestions, Warnings, or Problems Detected +Additional Information + + +Mattermost release +Are you on the latest Mattermost release? +

You're notified when updates are available.

See the Upgrade Mattermost product documentation for details on upgrading your workspace.

Configuration issues @@ -70,46 +51,22 @@ The following optimization areas can alert you to workspace suggestions, warning
  • Session Length: The default value may not provide an optimal user experience.
  • File Storage: Write access to the configured file storage location is required.
  • -
    -
    See the product documentation to learn more: |
    -
    -
    -
    -
    - +

    See the product documentation to learn more:

    Workspace access Is the Mattermost workspace accessible to users? -If your web server settings don't pass a live URL test, your workspace may not be accessible to others. | See the Web server configuration settings product documentation to learn more: +If your web server settings don't pass a live URL test, your workspace may not be accessible to others. See the Web server configuration settings product documentation to learn more. Search performance As your user base grows, is search getting slower? -See the Enterprise search product documentation to learn more. | +See the Enterprise search product documentation to learn more. Data privacy Do you need more control and insights into your data? -
    -
    See the product documentation to learn more: |
    -
    -
    -
    -
    - +

    See the product documentation to learn more:

    User authentication @@ -117,16 +74,7 @@ The following optimization areas can alert you to workspace suggestions, warning
  • AD/LDAP: As your user base grows, would you benefit from easier onboarding, automated deactivations, and role assignments?
  • Guest accounts: Do you want to control user access to channels and teams with guest accounts?
  • -
    -
    See the product documentation to learn more: |
    -
    -
    -
    -
    - +

    See the product documentation to learn more:

    diff --git a/docs/main/administration-guide/configure/site-configuration-settings.mdx b/docs/main/administration-guide/configure/site-configuration-settings.mdx index e576861be1ca..18380f53e900 100644 --- a/docs/main/administration-guide/configure/site-configuration-settings.mdx +++ b/docs/main/administration-guide/configure/site-configuration-settings.mdx @@ -819,11 +819,13 @@ Access the following configuration settings in the System Console by going to ** -++ - + +
    This setting determines whether a user can open a direct message channel with anyone on the Mattermost server or only to members of the same team. This setting only affects the options presented in the user interface. It does not affect permissions on the backend server. | - System Config path: Site Configuration > Users and Teams |
    - config.json setting: TeamSettings > RestrictDirectMessage|
    - Any user on the Mattermost server: (Default) Users can send a direct message to any user through the Direct Messages > More menu. config.json setting: "any" | - Environment variable: MM_TEAMSETTINGS_RESTRICTDIRECTMESSAGE |
                                                                           |
    • Any member of the team: The Direct Messages > More menu only allows direct messages to users on the same team. Pressing Ctrl K on Windows or Linux, or K on Mac, only lists other users on the team currently being viewed. A user who is a member of multiple teams can only send direct messages to the team that is being viewed. However, the user can receive messages from other teams, regardless of the team currently being viewed. config.json setting: "team" |

    This setting determines whether a user can open a direct message channel with anyone on the Mattermost server or only to members of the same team. This setting only affects the options presented in the user interface. It does not affect permissions on the backend server.

    • Any user on the Mattermost server: (Default) Users can send a direct message to any user through the Direct Messages > More menu. config.json setting: "any"
    • Any member of the team: The Direct Messages > More menu only allows direct messages to users on the same team. Pressing Ctrl K on Windows or Linux, or K on Mac, only lists other users on the team currently being viewed. A user who is a member of multiple teams can only send direct messages to the team that is being viewed. However, the user can receive messages from other teams, regardless of the team currently being viewed. config.json setting: "team"
    • System Config path: Site Configuration > Users and Teams
    • config.json setting: TeamSettings > RestrictDirectMessage
    • Environment variable: MM_TEAMSETTINGS_RESTRICTDIRECTMESSAGE
    @@ -832,11 +834,13 @@ Access the following configuration settings in the System Console by going to ** -++ - + +
    This setting determines how names appear in posts and under the Direct Messages list. | - System Config path: Site Configuration > Users and Teams | Users can change this setting in their interface under Settings > Display > | - ``config.json`` setting: ``TeamSettings`` > ``TeammateNameDisplay`` > ``username`` | Teammate Name Display, unless this setting is locked by a system admin | - Environment variable: MM_TEAMSETTINGS_TEAMMATENAMEDISPLAY | via the Lock teammate name display for all users configuration setting. | | | | - Show username: (Default for self-hosted deployments) Displays usernames. | | config.json option: "username". | | - Show nickname if one exists...: Displays the user's nickname. If the user doesn't have a | | nickname, their full name is displayed. If the user doesn't have a full name, their username | | is displayed. config.json option: "nickname_full_name". | | - Show first and last name: (Default for Cloud deployments) Displays user's full name. | | If the user doesn't have a full name, their username is displayed. Recommended when using | | SAML or | LDAP if first name and last name | attributes are configured. config.json option: "full_name". | |

    This setting determines how names appear in posts and under the Direct Messages list. Users can change this setting in their interface under Settings > Display > Teammate Name Display, unless this setting is locked by a system admin via the Lock teammate name display for all users configuration setting.

    • Show username: (Default for self-hosted deployments) Displays usernames. config.json option: "username".
    • Show nickname if one exists...: Displays the user's nickname. If the user doesn't have a nickname, their full name is displayed. If the user doesn't have a full name, their username is displayed. config.json option: "nickname_full_name".
    • Show first and last name: (Default for Cloud deployments) Displays user's full name. If the user doesn't have a full name, their username is displayed. Recommended when using SAML or LDAP if first name and last name attributes are configured. config.json option: "full_name".
    • System Config path: Site Configuration > Users and Teams
    • config.json setting: TeamSettings > TeammateNameDisplay > username
    • Environment variable: MM_TEAMSETTINGS_TEAMMATENAMEDISPLAY
    @@ -1145,6 +1149,54 @@ Cloud admins can't modify this configuration setting. +### Email batching buffer size + + ++++ + + + + + + +

    Specify the maximum number of notifications batched into a single email.

    Numerical input. Default is 256.

    • System Config path: Site Configuration > Notifications
    • config.json setting: EmailSettings > EmailBatchingBufferSize > 256
    • Environment variable: MM_EMAILSETTINGS_EMAILBATCHINGBUFFERSIZE
    + + + +- Cloud admins can't modify this configuration setting. +- This setting only applies when [email batching](#enable-email-batching) is enabled. +- We recommend increasing the buffer size from the default value if you see the following error in the Mattermost logs: `Email batching job's receiving buffer was full. Please increase the EmailBatchingBufferSize. Falling back to sending immediate mail.` Increasing this value will ensure emails are queued up, without impacting server performance. +- Notifications are sent immediately when the receiving buffer is full. + + + +### Email batching interval + + ++++ + + + + + + +

    Specify the maximum frequency, in seconds, which the batching job checks for new notifications.

    Numerical input in seconds. Default is 30.

    • System Config path: Site Configuration > Notifications
    • config.json setting: EmailSettings > EmailBatchingInterval > 30
    • Environment variable: MM_EMAILSETTINGS_EMAILBATCHINGINTERVAL
    + + + +- Cloud admins can't modify this configuration setting. +- This setting only applies when [email batching](#enable-email-batching) is enabled. +- Longer batching intervals may increase performance. +- Notifications are sent immediately when the receiving buffer is full. If that occurs, increase [Email batching buffer size](#email-batching-buffer-size). + + + ### Email notification contents @@ -1162,6 +1214,21 @@ Cloud admins can't modify this configuration setting. +### Use channel name in email notifications + + ++++ + + + + + + +
    • true: Channel and team name appears in email notification subject lines. Useful for servers using only one team.
    • false: (Default) Only team name appears in email notification subject line.
    • System Config path: Site Configuration > Notifications
    • config.json setting: EmailSettings > UseChannelInEmailNotifications > false
    • Environment variable: MM_EMAILSETTINGS_USECHANNELINEMAILNOTIFICATIONS
    + ### Notification display name diff --git a/docs/main/administration-guide/manage/admin/server-maintenance.mdx b/docs/main/administration-guide/manage/admin/server-maintenance.mdx index eaafc2de050d..06cfc3073df1 100644 --- a/docs/main/administration-guide/manage/admin/server-maintenance.mdx +++ b/docs/main/administration-guide/manage/admin/server-maintenance.mdx @@ -8,7 +8,7 @@ Whether you’re installing a license key, performing backups, upgrading the ser - [Install a license key](/administration-guide/manage/admin/installing-license-key) - Learn how to install a license key for Mattermost. - [Generate a support packet](/administration-guide/manage/admin/generating-support-packet) - Learn how to generate a support packet for Mattermost. - [Backup and disaster recovery](/deployment-guide/backup-disaster-recovery) - Learn about backup and disaster recovery for Mattermost. -- [Upgrade Mattermost server](/administration-guide/upgrade-mattermost) - Learn how to upgrading Mattermost server. +- [Upgrade Mattermost server](/administration-guide/upgrade/upgrade-index) - Learn how to upgrading Mattermost server. - [Secure Mattermost](/security-guide/secure-mattermost) - Learn about securing Mattermost server. - [Mattermost error codes](/administration-guide/manage/admin/error-codes) - Learn about Mattermost error codes and troubleshooting. - [Logging](/administration-guide/manage/logging) - Learn how to customize logging options based on business practices and needs. diff --git a/docs/main/administration-guide/cloud-workspace-management.mdx b/docs/main/administration-guide/manage/cloud-workspace-management.mdx similarity index 100% rename from docs/main/administration-guide/cloud-workspace-management.mdx rename to docs/main/administration-guide/manage/cloud-workspace-management.mdx diff --git a/docs/main/administration-guide/upgrade-mattermost.mdx b/docs/main/administration-guide/upgrade/upgrade-index.mdx similarity index 98% rename from docs/main/administration-guide/upgrade-mattermost.mdx rename to docs/main/administration-guide/upgrade/upgrade-index.mdx index a2db11c14c66..efcdf25863c7 100644 --- a/docs/main/administration-guide/upgrade-mattermost.mdx +++ b/docs/main/administration-guide/upgrade/upgrade-index.mdx @@ -1,5 +1,6 @@ --- title: "Upgrade Mattermost" +sidebar_label: "Upgrade" --- diff --git a/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-ddil-operations.mdx b/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-ddil-operations.mdx index 6d22131d7bfc..ab0396b6ad78 100644 --- a/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-ddil-operations.mdx +++ b/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-ddil-operations.mdx @@ -38,9 +38,9 @@ This hybrid deployment architecture provides optimal collaboration in both conne - **Mattermost Mobile Apps:** Access Mattermost via [iPhone and Android apps](/deployment-guide/mobile/mobile-app-deployment), with support for [ID-only push notifications](/deployment-guide/mobile/host-your-own-push-proxy-service) to ensure compliance with data sovereignty requirements. *(Optional when connectivity permits - not shown)* - **Mattermost Deployment:** Mattermost deployed for sovereign tactical collaboration on local infrastructure, such as [Azure Local](https://learn.microsoft.com/en-us/azure/azure-local/manage/disconnected-operations-overview), supporting data residency regulations and [disconnected operations](/deployment-guide/reference-architecture/deployment-scenarios/air-gapped-deployment). See [reference architecture](/deployment-guide/reference-architecture/scale/server-architecture) documentation for Mattermost deployment configurations based on expected scale. - **Mattermost Server:** Core application server handling tactical collaboration workloads, including: - - [Messaging Collaboration](/end-user-guide/messaging-collaboration): DDIL-ready 1:1, group messaging, and structured channel collaboration with [rich integration capabilities](/integrations-guide/integrations-guide-index) and [enterprise-grade search](/deployment-guide/reference-architecture/scale/scaling-for-enterprise#enterprise-search). - - [Workflow Automation](/end-user-guide/workflow-automation): Playbooks provide structure, monitoring and automation for repeatable processes built-in to your local Mattermost deployment. - - [Project Tracking](/end-user-guide/project-task-management): Boards enables project management capabilities built-in to your local Mattermost deployment. + - [Messaging Collaboration](/end-user-guide/collaborate/collaborate-index): DDIL-ready 1:1, group messaging, and structured channel collaboration with [rich integration capabilities](/integrations-guide/integrations-guide-index) and [enterprise-grade search](/deployment-guide/reference-architecture/scale/scaling-for-enterprise#enterprise-search). + - [Workflow Automation](/end-user-guide/workflow-automation/workflow-automation-index): Playbooks provide structure, monitoring and automation for repeatable processes built-in to your local Mattermost deployment. + - [Project Tracking](/end-user-guide/project-management/project-management-index): Boards enables project management capabilities built-in to your local Mattermost deployment. - [AI Agents](/administration-guide/configure/agents-admin-guide): AI Agents run against a local LLM hosted within your tactical network. - [Audio & Screenshare](/deployment-guide/calls/calls-deployment-guide): Calls offers native real-time self-hosted audio calls and screen sharing within your tactical network. - **Proxy Server:** The [proxy server](/deployment-guide/server/setup-nginx-proxy) handles HTTP(S) routing within the cluster, directing traffic between the server and clients accessing Mattermost services. NGINX is recommended for load balancing with support for WebSocket connections, health check endpoints, and sticky sessions. The proxy layer provides SSL termination and distributes client traffic across application servers. diff --git a/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-mission-partner.mdx b/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-mission-partner.mdx index 8a367455b5ed..a409aab506f5 100644 --- a/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-mission-partner.mdx +++ b/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-mission-partner.mdx @@ -54,9 +54,9 @@ The deployment architecture includes the following components: - **Microsoft 365 Desktop Apps:** For partnered organizations using Microsoft 365 services, Teams and Outlook can be deployed with the [embedded Mattermost application](/integrations-guide/mattermost-mission-collaboration-for-m365) for cross-domain partner collaboration within a familiar interface. *(Optional)* - **Mattermost Deployments:** Mattermost deployed for sovereign collaboration on private cloud or local infrastructure, such as [Azure](/deployment-guide/server/deploy-kubernetes) or [Azure Local](https://learn.microsoft.com/en-us/azure/azure-local/manage/disconnected-operations-overview), to maintain compliance with STIG, FedRAMP, and NIST 800-53 standards. See [reference architecture](/deployment-guide/reference-architecture/scale/server-architecture) documentation for Mattermost deployment configurations based on expected scale. - **Mattermost Server:** Core application server handling collaboration workloads, including: - - [Messaging Collaboration](/end-user-guide/messaging-collaboration): Sovereign 1:1, group messaging, and structured channel collaboration. - - [Workflow Automation](/end-user-guide/workflow-automation): Playbooks provide structure, monitoring and automation for repeatable processes built-in to your sovereign Mattermost deployment. - - [Project Tracking](/end-user-guide/project-task-management): Boards enables project management capabilities built-in to your local Mattermost deployment. Boards enables project management capabilities built-in to your sovereign Mattermost deployment. + - [Messaging Collaboration](/end-user-guide/collaborate/collaborate-index): Sovereign 1:1, group messaging, and structured channel collaboration. + - [Workflow Automation](/end-user-guide/workflow-automation/workflow-automation-index): Playbooks provide structure, monitoring and automation for repeatable processes built-in to your sovereign Mattermost deployment. + - [Project Tracking](/end-user-guide/project-management/project-management-index): Boards enables project management capabilities built-in to your local Mattermost deployment. Boards enables project management capabilities built-in to your sovereign Mattermost deployment. - [AI Agents](/administration-guide/configure/agents-admin-guide): AI Agents run against Azure OpenAI endpoints or a self-hosted LLM that is OpenAI-compatible. - [Audio & Screenshare](/deployment-guide/calls/calls-deployment-guide): Calls offers native real-time self-hosted audio calls and screen sharing within your own network. - **Proxy Server:** The [proxy server](/deployment-guide/server/setup-nginx-proxy) handles HTTP(S) routing within the cluster, directing traffic between the server and clients accessing Mattermost services, including requests from users in [connected organizations](/administration-guide/onboard/connected-workspaces). NGINX is recommended for load balancing with support for WebSocket connections, health check endpoints, and sticky sessions. The proxy layer provides SSL termination and distributes client traffic across application servers. diff --git a/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-sovereign-collaboration.mdx b/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-sovereign-collaboration.mdx index 20a8673c25b7..03e6bf15e110 100644 --- a/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-sovereign-collaboration.mdx +++ b/docs/main/deployment-guide/reference-architecture/deployment-scenarios/deploy-sovereign-collaboration.mdx @@ -33,9 +33,9 @@ The deployment architecture includes the following components: - **Mattermost Mobile Apps:** Access Mattermost via [iPhone and Android apps](/deployment-guide/mobile/mobile-app-deployment), with support for [ID-only push notifications](/deployment-guide/mobile/host-your-own-push-proxy-service) to ensure compliance with data sovereignty requirements. *(Optional - not shown)* - **Mattermost Deployment:** Mattermost deployed for sovereign collaboration on enterprise-controlled infrastructure or private cloud, such as [Azure](/deployment-guide/server/deploy-kubernetes) or [Azure Local](https://learn.microsoft.com/en-us/azure/azure-local/manage/disconnected-operations-overview), to maintain compliance with STIG, FedRAMP, and NIST 800-53 standards. See [reference architecture](/deployment-guide/reference-architecture/scale/server-architecture) documentation for Mattermost deployment configurations based on expected scale. - **Mattermost Server:** Core application server handling collaboration workloads, including: - - [Messaging Collaboration](/end-user-guide/messaging-collaboration): DDIL-ready 1:1, group messaging, and structured channel collaboration with [rich integration capabilities](/integrations-guide/integrations-guide-index) and [enterprise-grade search](/deployment-guide/reference-architecture/scale/scaling-for-enterprise#enterprise-search). - - [Workflow Automation](/end-user-guide/workflow-automation): Playbooks provide structure, monitoring and automation for repeatable processes built-in to your sovereign Mattermost deployment. - - [Project Tracking](/end-user-guide/project-task-management): Boards enables project management capabilities built-in to your sovereign Mattermost deployment. + - [Messaging Collaboration](/end-user-guide/collaborate/collaborate-index): DDIL-ready 1:1, group messaging, and structured channel collaboration with [rich integration capabilities](/integrations-guide/integrations-guide-index) and [enterprise-grade search](/deployment-guide/reference-architecture/scale/scaling-for-enterprise#enterprise-search). + - [Workflow Automation](/end-user-guide/workflow-automation/workflow-automation-index): Playbooks provide structure, monitoring and automation for repeatable processes built-in to your sovereign Mattermost deployment. + - [Project Tracking](/end-user-guide/project-management/project-management-index): Boards enables project management capabilities built-in to your sovereign Mattermost deployment. - [AI Agents](/administration-guide/configure/agents-admin-guide): AI Agents run against Azure OpenAI endpoints or a self-hosted LLM that is OpenAI-compatible. - [Audio & Screenshare](/deployment-guide/calls/calls-deployment-guide): Calls offers native real-time self-hosted audio calls and screen sharing within your own network. - **Proxy Server:** The [proxy server](/deployment-guide/server/setup-nginx-proxy) handles HTTP(S) routing within the cluster, directing traffic between the server and clients accessing Mattermost services. NGINX is recommended for load balancing with support for WebSocket connections, health check endpoints, and sticky sessions. The proxy layer provides SSL termination and distributes client traffic across application servers. diff --git a/docs/main/deployment-guide/server/orchestration.mdx b/docs/main/deployment-guide/server/orchestration.mdx index 77025c6144b8..f8f6b13dad55 100644 --- a/docs/main/deployment-guide/server/orchestration.mdx +++ b/docs/main/deployment-guide/server/orchestration.mdx @@ -1,6 +1,8 @@ --- title: "Deployment Solution Programs" --- +import useBaseUrl from '@docusaurus/useBaseUrl'; + Mattermost's **Deployment Solutions Programs** help IT administrators understand how Mattermost is being offered in third-party deployment solutions, including other open source projects as well as in commercial solutions. This is an optional program for third-party developers to increase awareness about their work and to enable Mattermost to refer its communities to different solutions. @@ -34,57 +36,33 @@ To summarize the commitment level of different solutions: - - - - - - - - - + + + - - - - - - - - - + + + - - - - - - - - + + - - - - - - - - + + - +
    Installation`checkmark <mm-subst:checkmark>`__ checkmark checkmark




    RequiredRequiredRequired
    Minimum Documentation`checkmark <mm-subst:checkmark>`__ checkmark checkmark




    RequiredRequiredRequired
    Security Updates `checkmark <mm-subst:checkmark>`__ checkmark




    RequiredRequired
    Branding `checkmark <mm-subst:checkmark>`__ checkmark




    RequiredRequired
    Upgrade checkmarkRequired
    diff --git a/docs/main/end-user-guide/access/client-availability.mdx b/docs/main/end-user-guide/access/client-availability.mdx index 27d58ec65994..784917e5f35e 100644 --- a/docs/main/end-user-guide/access/client-availability.mdx +++ b/docs/main/end-user-guide/access/client-availability.mdx @@ -9,422 +9,534 @@ The following tables highlight the end user features of Mattermost and their sup ## Messages - +
    -----++++ - + - - - - - + + + + + + - + + + + - + + + + - + + + + - - + + + + - + + + + - + + + + - + + + + - + + + + - + + + - + + + + - - + + + + - + + + + - + + + + - + + + +
    -

    Feature

    -
    -

    Web

    -
    Desktop
    -

    Mobile

    -
    FeatureWebDesktopMobile
    Threaded discussions | checkmark | checkmark | checkmark |Threaded discussionsSupportedSupportedSupported
    Format messages with Markdown | checkmark | checkmark | Partial Support |Format messages with MarkdownSupportedSupportedPartial Support
    Emojis | checkmark | checkmark | checkmark |EmojisSupportedSupportedSupported
    Emoji reactions_ | checkmark | checkmark | checkmark |Emoji reactionsSupportedSupported
    File sharing | checkmark | checkmark | checkmark |File sharingSupportedSupportedSupported
    @mentions | checkmark | checkmark | checkmark |@mentionsSupportedSupportedSupported
    Search hashtags | checkmark | checkmark | checkmark |Search hashtagsSupportedSupportedSupported
    Search modifiers | checkmark | checkmark | checkmark |Search modifiersSupportedSupportedSupported
    Search highlightingcheckmark | checkmark | checkmark |SupportedSupportedSupported
    Pin and save messages | checkmark | checkmark | checkmark |Pin and save messagesSupportedSupportedSupported
    Preview image linkscheckmark | checkmark | checkmark |Preview image linksSupportedSupportedSupported
    Preview websites | checkmark | checkmark | checkmarkPreview websitesSupportedSupportedSupported
    Notifications | checkmark | checkmark | checkmark |NotificationsSupportedSupportedSupported
    Bookmark channels | checkmark | checkmark | checkmark |Bookmark channelsSupportedSupportedSupported
    ## Channels - +
    -----++++ - + - - - - - + + + + + + - + + + + - - + + + + - - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + +
    -

    Feature

    -
    -

    Web

    -
    Desktop
    -

    Mobile

    -
    FeatureWebDesktopMobile
    Create a new channel | checkmark | checkmark | checkmark |Create a new channelSupportedSupportedSupported
    Join a channel
    - -
    Join a channelSupportedSupportedSupported
    Leave a channel
    - -
    Leave a channelSupportedSupportedSupported
    Favorite a channel | checkmark | checkmark | checkmark |Favorite a channelSupportedSupportedSupported
    Mute a channel | checkmark | checkmark | checkmark |Mute a channelSupportedSupportedSupported
    Manage members | checkmark | checkmark | checkmark |Manage membersSupportedSupportedSupported
    Add members | checkmark | checkmark | checkmarkAdd membersSupportedSupportedSupported
    Rename channels | checkmark | checkmark | checkmark |Rename channelsSupportedSupportedSupported
    Deactivate members | checkmark | checkmark | |Deactivate membersSupportedSupported
    ## Teams - +
    -----++++ - + - - - - - + + + + + + - + + + + - + + + + - + + + + - + + + + - - + + + + - - + + + + - + + + + - + + + + - + + + + - - + + + +
    -

    Feature

    -
    -

    Web

    -
    Desktop
    -

    Mobile

    -
    FeatureWebDesktopMobile
    Multi-team support for notifications | checkmark | checkmark | checkmarkMulti-team support for notificationsSupportedSupportedSupported
    Team switching | checkmark | checkmark | checkmark |Team switchingSupportedSupportedSupported
    Team-based theming | checkmark | checkmark | checkmark |Team-based themingSupportedSupportedSupported
    Team settings | checkmark | checkmark | |Team settingsSupportedSupported
    Join existing teamcheckmark | checkmark | checkmark |Join existing teamSupportedSupportedSupported
    Create a new teamcheckmark | checkmark | |Create a new teamSupportedSupported
    Share an invite link | checkmark | checkmark | checkmark |Share an invite linkSupportedSupportedSupported
    Invite people | checkmark | checkmark | checkmark |Invite peopleSupportedSupportedSupported
    Manage team members | checkmark | checkmark | |Manage team membersSupportedSupported
    Leave team
    - -
    Leave teamSupportedSupportedSupported
    ## Collaborative playbooks - +
    ------++++ - + - - - - - + + + + + + - + + + +
    -

    Feature

    -
    -

    Web

    -
    DesktopMobileFeatureWebDesktopMobile
    Collaborative playbooks | checkmark | checkmark |Collaborative playbooksSupportedSupported
    ## Calls - +
    -----++++ - + - - - - - + + + + + + - + + + + - + + + + - + + + + - + + + + - + + + +
    -

    Feature

    -
    -

    Web

    -
    Desktop
    -

    Mobile

    -
    FeatureWebDesktopMobile
    Join call | checkmark | checkmark | checkmark |Join callSupportedSupportedSupported
    Share screen | checkmark | checkmark | |Share screenSupportedSupported
    Chat in thread | checkmark | checkmark | checkmarkChat in threadSupportedSupportedSupported
    React with emoji | checkmark | checkmark | checkmark |React with emojiSupportedSupportedSupported
    Record a call | checkmark | checkmark | checkmark |Record a callSupportedSupportedSupported
    ## Integrations - +
    -----++++ - + - - - - - + + + + + + - + + + + - + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + +
    -

    Feature

    -
    -

    Web

    -
    Desktop
    -

    Mobile

    -
    FeatureWebDesktopMobile
    Slash commands | checkmark | checkmark | Partial Support |Slash commandsSupportedSupportedPartial Support
    Server-side plugins | checkmark | checkmark | checkmarkServer-side pluginsSupportedSupportedSupported
    Interactive dialogscheckmark | checkmark | |SupportedSupported
    OAuth 2.0checkmark | checkmark | checkmark |SupportedSupportedSupported
    Incoming webhookscheckmark | checkmark | checkmark |SupportedSupportedSupported
    Outgoing webhookscheckmark | checkmark | checkmark |SupportedSupportedSupported
    Mattermost BlocksIncluded | Included | Included |IncludedIncludedIncluded
    Message attachmentscheckmark | checkmark | checkmark |SupportedSupportedSupported
    Message buttonscheckmark | checkmark | checkmark |SupportedSupportedSupported
    Message menuscheckmark | checkmark | checkmark |SupportedSupportedSupported
    Message actionscheckmark | checkmark | |SupportedSupported
    Right-hand sidebarcheckmark | checkmark | |SupportedSupported
    ## Authentication - +
    ----+++ - + - - - - - + + + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + +
    -

    Feature

    -
    -

    Web

    -
    Desktop
    -

    Mobile

    -
    FeatureWebDesktopMobile
    Email password login | checkmark | checkmark | checkmarkEmail password loginSupportedSupportedSupported
    AD/LDAP | checkmark | checkmark | checkmark |AD/LDAPSupportedSupportedSupported
    SAML SSO | checkmark | checkmark | checkmark |SAML SSOSupportedSupportedSupported
    GitLab SSO | checkmark | checkmark | checkmark |GitLab SSOSupportedSupportedSupported
    Entra ID SSO | checkmark | checkmark | checkmark |Entra ID SSOSupportedSupportedSupported
    Google SSO | checkmark | checkmark | checkmark |Google SSOSupportedSupportedSupported
    ## Other - +
    -----++++ - + - - - - - + + + + + + - + + + + - + + + + - + + + + - + + + +
    -

    Feature

    -
    -

    Web

    -
    Desktop
    -

    Mobile

    -
    FeatureWebDesktopMobile
    Localization for 22 languages | checkmark | checkmark | checkmark |Localization for 22 languagesSupportedSupportedSupported
    Custom user interface themes | checkmark | checkmark | checkmarkCustom user interface themesSupportedSupportedSupported
    User profile settings | checkmark | checkmark | checkmarkUser profile settingsSupportedSupportedSupported
    Channel notification settings | checkmark | checkmark | checkmark |Channel notification settingsSupportedSupportedSupported
    diff --git a/docs/main/end-user-guide/messaging-collaboration.mdx b/docs/main/end-user-guide/collaborate/collaborate-index.mdx similarity index 98% rename from docs/main/end-user-guide/messaging-collaboration.mdx rename to docs/main/end-user-guide/collaborate/collaborate-index.mdx index eb426666ae90..443a4b59587d 100644 --- a/docs/main/end-user-guide/messaging-collaboration.mdx +++ b/docs/main/end-user-guide/collaborate/collaborate-index.mdx @@ -1,5 +1,6 @@ --- title: "Messaging Collaboration" +sidebar_label: "Collaborate" --- diff --git a/docs/main/end-user-guide/collaborate/format-messages.mdx b/docs/main/end-user-guide/collaborate/format-messages.mdx index f7b1bfab194f..882af2060737 100644 --- a/docs/main/end-user-guide/collaborate/format-messages.mdx +++ b/docs/main/end-user-guide/collaborate/format-messages.mdx @@ -20,42 +20,43 @@ The message formatting toolbar offers the following formatting options: - + -
    -

    Formatting option

    -
    -
    -
    ==================================================================================
    -
    -

    Bold, italicize, or strike out text

    -
    -
    -
    -

    Icon |

    -
    -
    -
    =========================+
    -
    -

    bold-icon | italics-icon | strikeout-icon |

    -
    -
    +Formatting option +Icon + + +Bold, italicize, or strike out text + +Bold +Italicize +Strikeout + Add headings, links, or attachments -headings-icon | copy-link-icon | attachments-icon | + +Headings +Copy link +Attachments + Format a numbered list, a bulleted list, quoted text, or text as code -numbered-icon | bullets-icon | quotes-icon | code-icon | + +Numbered list +Bulleted list +Quoted text +Code + -Add emojis or GIFs | emoji-icon | +Add emojis or GIFs Emoji -Set message priority | message-priority-icon +Set message priority Message priority @@ -195,7 +196,31 @@ Renders as: Creating a fixed-width code block is recommended for pasting multi-line blocks of code or other text output because it's easier to read with fixed-width font alignment. Examples include block text snippets, ASCII tables, and log files. Rendered code blocks include a **Copy** option to copy the contents of the code block. -This can be accomplished by placing three backticks ```` ` on the line directly above and directly below your code: .. code-block:: text `` this is my code block `.. tip:: Type three backticks```, press \`Shift \\_\_ Enter on Windows or Linux, or on Mac, ``, press Shift Enter on Windows or Linux, or on Mac again, then type three more backticks ```` `. Or by indenting each line by four spaces: .. code-block:: text this is my code block ^^^^ 4x spaces Syntax highlighting ^^^^^^^^^^^^^^^^^^^^ To add syntax highlighting, type the language to be highlighted after the ````\` at the beginning of the code block. Mattermost also offers four different code themes (GitHub, Solarized Dark, Solarized Light, and Monokai) that can be changed in **Settings \> Display \> Theme \> Custom Theme \> Center Channel Styles**. +This can be accomplished by placing three backticks (```` ``` ````) on the line directly above and directly below your code: + +````text +``` +this is my +code block +``` +```` + + + +Type three backticks (```` ``` ````), press Shift Enter on Windows or Linux, or on Mac, ``, press Shift Enter on Windows or Linux, or on Mac again, then type three more backticks (```` ``` ````). + + + +Or by indenting each line by four spaces: + +```text + this is my + code block +``` + +### Syntax highlighting + +To add syntax highlighting, type the language to be highlighted after the ```` ``` ```` at the beginning of the code block. Mattermost also offers four different code themes (GitHub, Solarized Dark, Solarized Light, and Monokai) that can be changed in **Settings \> Display \> Theme \> Custom Theme \> Center Channel Styles**. Supported languages and their aliases include: diff --git a/docs/main/end-user-guide/collaborate/keyboard-accessibility.mdx b/docs/main/end-user-guide/collaborate/keyboard-accessibility.mdx index 520caa912cba..c86673e3fc78 100644 --- a/docs/main/end-user-guide/collaborate/keyboard-accessibility.mdx +++ b/docs/main/end-user-guide/collaborate/keyboard-accessibility.mdx @@ -7,26 +7,39 @@ Navigational keyboard shortcuts help you use Mattermost in a web browser or the -++ + + + + + + - + + - + + - + + - + + - + + - + +
    Keyboard shortcutDescription

    Keyboard shortcut | Description |

    ==============================================+==================================================================================+ | Desktop App: F6 || Move focus to the next section | | Browser: Ctrl F6 || |

    Desktop App: F6
    Browser: Ctrl F6
    Move focus to the next section
    Desktop App: Shift F6 || Move focus to the previous section |
    Browser: Ctrl Shift F6 |
    Desktop App: Shift F6
    Browser: Ctrl Shift F6
    Move focus to the previous section
    Tab | Move focus to the next element |TabMove focus to the next element
    Shift Tab | Move focus to the previous element |Shift TabMove focus to the previous element
    or | Move focus between messages in the post list or sections in the channel sidebar | or Move focus between messages in the post list or sections in the channel sidebar
    Enter | Take action on the focused element |EnterTake action on the focused element
    diff --git a/docs/main/end-user-guide/collaborate/keyboard-shortcuts.mdx b/docs/main/end-user-guide/collaborate/keyboard-shortcuts.mdx index 6089ab0daad3..936188143493 100644 --- a/docs/main/end-user-guide/collaborate/keyboard-shortcuts.mdx +++ b/docs/main/end-user-guide/collaborate/keyboard-shortcuts.mdx @@ -22,67 +22,88 @@ Mattermost keyboard shortcuts help you make a more efficient use of your keyboar The following keyboard shortcuts for channels are supported in all [supported browsers](/deployment-guide/software-hardware-requirements#software-requirements) and in the Mattermost desktop app. --- + + + + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + +
    On Windows & LinuxOn MacDescription
    -

    On Windows & Linux | On Mac | Description |

    -
    -
    -
    =====================================+==============================+==================================================================================+
    -
    -

    Alt | | Previous channel or direct message in the channel sidebar. |

    -
    -
    Alt Previous channel or direct message in the channel sidebar.
    Alt | | Next channel or direct message in the channel sidebar. |Alt Next channel or direct message in the channel sidebar.
    Alt Shift | | Previous channel or direct message in the channel sidebar with unread messages.Alt Shift Previous channel or direct message in the channel sidebar with unread messages.
    Alt Shift | | Next channel or direct message in the channel sidebar with unread messages.Alt Shift Next channel or direct message in the channel sidebar with unread messages.
    Alt + select channel | + select channel | Mark the last post in the channel as unread. |Alt + select channel + select channelMark the last post in the channel as unread.
    -
    Ctrl K | K | - If text isn't selected: Open the Find Channels dialog. |
    -
    -
                                 | - If text is selected: Create a hyperlink in the format \[linktext\](URL). |
    -
    -
    Ctrl K K
    • If text isn't selected: Open the Find Channels dialog.
    • If text is selected: Create a hyperlink in the format [linktext](URL).
    Ctrl Shift K | K | Open the Direct Messages dialog.Ctrl Shift K KOpen the Direct Messages dialog.
    Ctrl Shift A | A | Open the Settings dialog.Ctrl Shift A AOpen the Settings dialog.
    Ctrl Shift M | M | Open recent mentions.Ctrl Shift M MOpen recent mentions.
    Ctrl Shift L | L | Set focus to center channel input field.Ctrl Shift L LSet focus to center channel input field.
    Ctrl . | . | Open or close the right-hand sidebar. |Ctrl . .Open or close the right-hand sidebar.
    Ctrl Shift . | . | Expand or shrink the width of the open right-hand sidebar.Ctrl Shift . .Expand or shrink the width of the open right-hand sidebar.
    Ctrl Shift F | F | Move focus to the Search field and search the current channel.Ctrl Shift F FMove focus to the Search field and search the current channel.
    Ctrl Shift U | U | Find unread channels or search through all channels.Ctrl Shift U UFind unread channels or search through all channels.
    Ctrl Shift I | I | Open or close Channel Info details in the right-hand sidebar.Ctrl Shift I IOpen or close Channel Info details in the right-hand sidebar.
    @@ -91,21 +112,19 @@ The following keyboard shortcuts for channels are supported in all [supported br The following keyboard shortcuts are supported in all [supported browsers](/deployment-guide/software-hardware-requirements#software-requirements) and in the Mattermost desktop app. - --- +
    + + + + + + + - + + +
    On Windows & LinuxOn MacDescription
    -

    On Windows & Linux | On Mac | Description |

    -
    -
    -
    =======================+====================+=================+
    -
    -

    Ctrl U | U | Upload a file.

    -
    -
    Ctrl U UUpload a file.
    @@ -115,41 +134,53 @@ The following keyboard shortcuts are supported in all [supported browsers](/depl The following keyboard shortcuts are supported in all [supported browsers](/deployment-guide/software-hardware-requirements#software-requirements) and in the Mattermost desktop app. --- + + + + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + +
    On Windows & LinuxOn MacDescription
    -

    On Windows & Linux | On Mac | Description |

    -
    -
    -
    ===============================================+============================================+===========================================================+
    -
    -

    Ctrl (in empty input field) | (in empty input field) | Reprint previous message or slash command you entered. |

    -
    -
    Ctrl (in empty input field) (in empty input field)Reprint previous message or slash command you entered.
    Ctrl (in empty input field) | (in empty input field) | Reprint next message or slash command you entered. |Ctrl (in empty input field) (in empty input field)Reprint next message or slash command you entered.
    Shift (in empty input field) | (in empty input field) | Reply to the most recent message in the current channel. |Shift (in empty input field) (in empty input field)Reply to the most recent message in the current channel.
    (in empty input field) | (in empty input field) | Edit your last message in the current channel. | (in empty input field) (in empty input field)Edit your last message in the current channel.
    @[character] Tab | @[character] Tab | Autocomplete @username beginning with [character]. |@[character] Tab@[character] TabAutocomplete @username beginning with [character].
    \~[character] Tab | \~[character] Tab | Autocomplete channel beginning with [character]. |~[character] Tab~[character] TabAutocomplete channel beginning with [character].
    :[character] Tab | :[character] Tab | Autocomplete emoji beginning with [character]. |:[character] Tab:[character] TabAutocomplete emoji beginning with [character].
    Ctrl Shift \ | \ | React to last message in channel or thread.Ctrl Shift \ \React to last message in channel or thread.
    @@ -159,26 +190,28 @@ The following keyboard shortcuts are supported in all [supported browsers](/depl The following keyboard shortcuts are supported in all [supported browsers](/deployment-guide/software-hardware-requirements#software-requirements) and in the Mattermost desktop app. --- + + + + + + + - + + + - + + + - + + +
    On Windows & LinuxOn MacDescription
    -

    On Windows & Linux | On Mac | Description |

    -
    -
    -
    ===================================+=============================+========================+
    -
    -

    Ctrl B | B | Bold text. |

    -
    -
    Ctrl B BBold text.
    Ctrl I | I | Italicize text. |Ctrl I IItalicize text.
    Ctrl Alt K | K | Format text as a link.Ctrl Alt K KFormat text as a link.
    @@ -188,32 +221,38 @@ The following keyboard shortcuts are supported in all [supported browsers](/depl The following keyboard shortcuts work in all [supported browsers](/deployment-guide/software-hardware-requirements#software-requirements) and in the Mattermost desktop app. --- + + + + + + + - + + + - + + + - + + + - + + + - + + +
    On Windows & LinuxOn MacDescription
    -

    On Windows & Linux | On Mac | Description |

    -
    -
    -
    ==============================================+==========================================+==================================================+
    -
    -

    Alt | [ | Go to the previous channel in your history.

    -
    -
    Alt [Go to the previous channel in your history.
    Alt | ] | Go to the next channel in your history.Alt ]Go to the next channel in your history.
    Shift (in input field) | (in input field) | Highlight text to the previous line.Shift (in input field) (in input field)Highlight text to the previous line.
    Shift (in input field) | (in input field) | Highlight text to the next line.Shift (in input field) (in input field)Highlight text to the next line.
    Shift Enter (in input field) | Enter (in input field) | Create a new line.Shift Enter (in input field) Enter (in input field)Create a new line.
    @@ -229,38 +268,48 @@ Though Mattermost keyboard shortcuts support standard languages and keyboard lay The following keyboard shortcuts are supported in all [supported browsers](/deployment-guide/software-hardware-requirements#software-requirements) and in the Mattermost desktop app when [calls](/end-user-guide/collaborate/make-calls) are enabled. --- + + + + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + +
    On Windows & LinuxOn MacDescription
    -

    On Windows & Linux | On Mac | Description |

    -
    -
    -
    ===========================================================+==================================================+=========================================================================================+
    -
    -

    Ctrl Alt S | S | Start or join call in current channel. |

    -
    -
    Ctrl Alt S SStart or join call in current channel.
    Ctrl Shift Space | Space | Mute or unmute. |Ctrl Shift Space SpaceMute or unmute.
    Ctrl Shift Y | Y | Raise or lower hand. |Ctrl Shift Y YRaise or lower hand.
    Ctrl Shift E | E | Share or unshare screen. |Ctrl Shift E EShare or unshare screen.
    Alt P or Ctrl Shift P | P or P | Show or hide participants list.Alt P or Ctrl Shift P P or PShow or hide participants list.
    Ctrl Shift L | L | Leave current call. |Ctrl Shift L LLeave current call.
    Space | Space | Hold to unmute (push to talk) Note: works in the expanded view/popout window only. |SpaceSpaceHold to unmute (push to talk) Note: works in the expanded view/popout window only.
    @@ -275,38 +324,48 @@ The following navigation keyboard shortcuts are supported only in the Mattermost Mattermost desktop app v6.0 introduces the ability to keep multiple workspaces open at the same time and work across them without constant switching contexts. The following keyboard shortcuts help you navigate multiple views: --- + + + + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + +
    On Windows & LinuxOn macOSDescription
    -

    On Windows & Linux | On macOS | Description |

    -
    -
    -
    ======================================+===============================+=================================================================================================+
    -
    -

    Ctrl N | N | Open a new window for the current server |

    -
    -
    Ctrl N NOpen a new window for the current server
    Ctrl T | T | Open a new tab for the current server |Ctrl T TOpen a new tab for the current server
    Ctrl Tab | Tab | Go to the next tab |Ctrl Tab TabGo to the next tab
    Ctrl Shift Tab | Tab | Go to the previous tabCtrl Shift Tab TabGo to the previous tab
    Ctrl 1-9 | 1-9 | Jump to a specific tab based on its position |Ctrl 1-9 1-9Jump to a specific tab based on its position
    Ctrl W | W | Close current tab when multiple tabs are open |Ctrl W WClose current tab when multiple tabs are open
    Ctrl Shift W | W | Close main windowCtrl Shift W WClose main window
    @@ -317,47 +376,53 @@ Mattermost desktop app v6.0 introduces the ability to keep multiple workspaces o Mattermost desktop app v5.0 introduces additional ways to navigate your Mattermost interface, including server selections, as well as tabs for channels, collaborative playbooks, and boards. --- + + + + + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + + - + + +
    On Windows & LinuxOn macOSDescription
    -

    On Windows & Linux | On macOS | Description |

    -
    -
    -
    ======================================+===============================+=================================================================================================+
    -
    -

    Ctrl F | F | Move focus to the Search field and search the current channel. |

    -
    -
    Ctrl F FMove focus to the Search field and search the current channel.
    -
    Ctrl Shift S | S | Open the Servers selector, press or to navigate between
    -
    -
                                  | servers, then press Enter on Windows or Linux, or on macOS, to select a server. |
    -
    -
    Ctrl Shift S SOpen the Servers selector, press or to navigate between servers, then press Enter on Windows or Linux, or on macOS, to select a server.
    Ctrl Shift 1 || 1 || Navigate to the first server in the Servers list. |
    -Ctrl Shift 2 || 2 || Replace the number with the server's position within the server in the list. |
    Ctrl Shift 1
    Ctrl Shift 2
    1
    2
    Navigate to the first server in the Servers list.
    Replace the number with the server's position within the server in the list.
    Ctrl Tab | Tab | Navigate to the next product tab based on the current product selected. |Ctrl Tab TabNavigate to the next product tab based on the current product selected.
    Ctrl Shift Tab | Tab | Navigate to the previous product tab based on the current product selected. |Ctrl Shift Tab TabNavigate to the previous product tab based on the current product selected.
    Ctrl 1 | 1 | Navigate to the Channels tab. |Ctrl 1 1Navigate to the Channels tab.
    Ctrl 2 | 2 | Navigate to the Boards tab. |Ctrl 2 2Navigate to the Boards tab.
    Ctrl 3 | 3 | Navigate to the Playbooks tab. |Ctrl 3 3Navigate to the Playbooks tab.
    @@ -368,34 +433,38 @@ Mattermost desktop app v5.0 introduces additional ways to navigate your Mattermo Mattermost desktop app v4.7 and earlier releases support the following navigation keyboard shortcuts: --- + + + + + + + - + + + - + + + - + + + - + + + - + + +
    On Windows & LinuxOn macOSDescription
    -

    On Windows & Linux | On macOS | Description |

    -
    -
    -
    ======================================+==============================+================================================================================+
    -
    -

    Ctrl F | F | Move focus to the Search field and search the current channel. |

    -
    -
    Ctrl F FMove focus to the Search field and search the current channel.
    Ctrl 1 || 1 | Navigate to the first server in the Servers list. |
    -Ctrl 2 || 2 | Replace the number with the server's tab position. |
    -Ctrl 3 || 3 | |
    Ctrl 1
    Ctrl 2
    Ctrl 3
    1
    2
    3
    Navigate to the first server in the Servers list.
    Replace the number with the server's tab position.
    Ctrl Tab | Tab | Navigate to the next server tab based on the current server selected. |Ctrl Tab TabNavigate to the next server tab based on the current server selected.
    Ctrl Shift Tab | Tab | Navigate to the previous server tab based on the current server selected.Ctrl Shift Tab TabNavigate to the previous server tab based on the current server selected.
    Alt | | Next channel or direct message in the channel sidebar. |Alt Next channel or direct message in the channel sidebar.
    @@ -409,23 +478,23 @@ Mattermost desktop app v4.7 and earlier releases support the following navigatio The following display keyboard shortcuts work in all [supported browsers](/deployment-guide/software-hardware-requirements#software-requirements) and in the Mattermost desktop app. --- + + + + + + + - + + + - + + +
    On Windows & LinuxOn macOSDescription
    -

    On Windows & Linux | On macOS | Description |

    -
    -
    -
    =====================================+==============================+========================================+
    -
    -

    Ctrl Shift + | + | Increase font size (zoom in).

    -
    -
    Ctrl Shift + +Increase font size (zoom in).
    Ctrl Shift - | - | Decrease font size (zoom out).Ctrl Shift - -Decrease font size (zoom out).
    diff --git a/docs/main/end-user-guide/collaborate/team-keyboard-shortcuts.mdx b/docs/main/end-user-guide/collaborate/team-keyboard-shortcuts.mdx index b11b4dfb4636..71770d12aaa3 100644 --- a/docs/main/end-user-guide/collaborate/team-keyboard-shortcuts.mdx +++ b/docs/main/end-user-guide/collaborate/team-keyboard-shortcuts.mdx @@ -19,23 +19,28 @@ The following keyboard shortcuts are supported in all [supported browsers](/depl + + +On Windows & Linux +On Mac +Description + + -
    -

    On Windows & Linux | On Mac | Description |

    -
    -
    -
    ===========================================+======================================+==============================================+
    -
    -

    Ctrl Alt | | Navigate to the previous team. |

    -
    -
    +Ctrl Alt + +Navigate to the previous team. -Ctrl Alt | | Navigate to the next team. | +Ctrl Alt + +Navigate to the next team. -Ctrl Alt 1-9 | 1-9 | Navigate to a specific team. +Ctrl Alt 1-9 + 1-9 +Navigate to a specific team. diff --git a/docs/main/end-user-guide/end-user-guide-index.mdx b/docs/main/end-user-guide/end-user-guide-index.mdx index 01f80812f1a4..91c16137e31a 100644 --- a/docs/main/end-user-guide/end-user-guide-index.mdx +++ b/docs/main/end-user-guide/end-user-guide-index.mdx @@ -6,12 +6,12 @@ If you're using Mattermost to connect and collaborate, build repeatable, automat In this documentation, you'll learn about using Mattermost. Your Mattermost system admin has deployed Mattermost for your organization. A live Mattermost instance is ready for you to log into using your user credentials. Your Mattermost workspace is where you'll send and receive messages, see activity notifications, create, run, and participate in playbook runs, and where you'll customize look and feel through workspace preferences. - [Access Your Workspace](/end-user-guide/access/access-your-workspace) - Learn how to access Mattermost using web, desktop, or mobile apps, and authenticate with your credentials. -- [Messaging Collaboration](/end-user-guide/messaging-collaboration) - Learn how to use Mattermost to connect and collaborate with your teammates. -- [Workflow Automation](/end-user-guide/workflow-automation) - Learn how to use Mattermost Playbooks to build repeatable processes, move faster, and make fewer mistakes with checklist-based automations. +- [Messaging Collaboration](/end-user-guide/collaborate/collaborate-index) - Learn how to use Mattermost to connect and collaborate with your teammates. +- [Workflow Automation](/end-user-guide/workflow-automation/workflow-automation-index) - Learn how to use Mattermost Playbooks to build repeatable processes, move faster, and make fewer mistakes with checklist-based automations. - [Audio and Screensharing](/end-user-guide/collaborate/audio-and-screensharing) - Learn about Mattermost's self-hosted audio calls plugin with screen sharing and the many video conferencing integrations Mattermost supports. -- [Project and Task Management](/end-user-guide/project-task-management) - Learn how to use Mattermost Boards to coordinate operational work with Kanban-style planning. -- [AI Agents](/end-user-guide/agents) - Learn how to use AI agents to help you make decisions, find information, and automate repetative tasks. -- [Customize Your Preferences](/end-user-guide/preferences) - Learn how to make Mattermost match the way you prefer to work. +- [Project and Task Management](/end-user-guide/project-management/project-management-index) - Learn how to use Mattermost Boards to coordinate operational work with Kanban-style planning. +- [AI Agents](/end-user-guide/agents) - Learn how to use AI agents to help you make decisions, find information, and automate repetitive tasks. +- [Customize Your Preferences](/end-user-guide/preferences/preferences-index) - Learn how to make Mattermost match the way you prefer to work. ![An example of the Mattermost screen that includes teams, the channel sidebar, an active conversation in the center pane, reply threads in the right-hand pane.](/images/Channels_Hero.png) diff --git a/docs/main/end-user-guide/preferences/manage-your-profile.mdx b/docs/main/end-user-guide/preferences/manage-your-profile.mdx index 07fdea74a132..ccf706238105 100644 --- a/docs/main/end-user-guide/preferences/manage-your-profile.mdx +++ b/docs/main/end-user-guide/preferences/manage-your-profile.mdx @@ -11,42 +11,39 @@ Your system admin may also manage profile fields for accounts that sign in with ---++ - + - - - + + + + - + + - - + - - -

    Profile setting

    =====================

    Full, first, and last name

    Description

    ================================================================================================================

    Your name appears in the direct messages member list and team management modal. By default, you'll receive mention notifications when someone types your first name in a message.

    Profile settingDescription
    Full, first, and last nameYour name appears in the direct messages member list and team management modal. By default, you'll receive mention notifications when someone types your first name in a message.
    Username

    Usernames are unique identifiers appearing next to all posts. Usernames must begin with a letter, and contain between 3 to 22 lowercase characters made up of numbers, letters, and the symbols ., -, and _.

    Pick something easy for teammates to recognize and recall. By default, you'll receive mention notifications when someone types your username. Changing your username won't change your existing @mentions in sent messages.

    Nickname(Optional) Nicknames appear in the direct messages member list and team management modal. | Your nickname can be up to 64 characters long. | | You won't be notified when someone types your nickname unless you add your nickname to the list | of Keywords that Trigger Notifications as a notification preference |. | From Mattermost v10.8, nicknames display in | threaded discussions when available. |(Optional) Nicknames appear in the direct messages member list and team management modal. Your nickname can be up to 64 characters long.

    You won't be notified when someone types your nickname unless you add your nickname to the list of Keywords that Trigger Notifications as a notification preference.

    From Mattermost v10.8, nicknames display in threaded discussions when available.
    Position (Optional) Position can be used to describe your role or job title. Your position appears in the profile popup that displays when you select a user's name in the center channel or right-hand sidebar.
    Email Email is used for signing in, notifications, and password reset.
    Profile picture

    Profile pictures appear next to all posts, and you can select your profile picture to access your profile settings. To change your profile picture:

    Using the web or the desktop app

    1. Select Edit next to the Profile Picture option.
    2. Choose Select, pick the profile image you want to use, and select Save.

    Using the mobile app

    1. Tap your current profile picture.
    2. Take a photo using your device, or select an image to use.

    For best results, choose an image that's at least 128 x 128 pixels in size. Supported image formats include: BMP, JPG, JPEG, and PNG. GIF isn't supported.

    diff --git a/docs/main/end-user-guide/preferences.mdx b/docs/main/end-user-guide/preferences/preferences-index.mdx similarity index 98% rename from docs/main/end-user-guide/preferences.mdx rename to docs/main/end-user-guide/preferences/preferences-index.mdx index 6620bee194e8..22441249a935 100644 --- a/docs/main/end-user-guide/preferences.mdx +++ b/docs/main/end-user-guide/preferences/preferences-index.mdx @@ -1,5 +1,6 @@ --- title: "Customize your preferences" +sidebar_label: "Preferences" --- diff --git a/docs/main/end-user-guide/preferences/set-your-status-availability.mdx b/docs/main/end-user-guide/preferences/set-your-status-availability.mdx index ce0a0cc2a6d1..4fe8f717517a 100644 --- a/docs/main/end-user-guide/preferences/set-your-status-availability.mdx +++ b/docs/main/end-user-guide/preferences/set-your-status-availability.mdx @@ -3,6 +3,7 @@ title: "Set your status and availability" --- import Tabs from '@theme/Tabs'; import TabItem from '@theme/TabItem'; +import useBaseUrl from '@docusaurus/useBaseUrl'; @@ -50,20 +51,54 @@ To set your availability, select your profile picture, then specify your availab -++ + + + + + + - + + - + + - + + - + +
    AvailabilityDescription

    Availability | Description |

    ==================+======================================================================================================================================+
    online | Online: |
                                                                                                                                         |
    - Set automatically for you when you're active on Mattermost using a browser, the desktop app, or the mobile app. |
    - When using the desktop app, any mouse or keyboard activity keeps your availability set to Online. |
    - By default, notifications are sent to the browser, the desktop app, and the mobile app. |
    Online status icon Online + +- Set automatically for you when you're active on Mattermost using a browser, the desktop app, or the mobile app. +- When using the desktop app, any mouse or keyboard activity keeps your availability set to **Online**. +- By default, notifications are sent to the browser, the desktop app, and the mobile app. + +
    away | Away: |
                                                                                                                                         |
    - Set automatically for you when you've been inactive for more than 5 minutes. System admins can change this |
      value using an experimental configuation setting called |
      `user status away timeout <mm-ref:administration-guide%2Fconfigure%2Fexperimental-configuration-settings%3Auser%20status%20away%20timeout>`\_\_. |
                                                                                                                                         |
    - You're inactive in Mattermost when you're not: typing in or navigating between channels, switching to |
      another browser tab, or when you've minimized or moved the browser window to the background. |
                                                                                                                                         |
    - You can manually set yourself as Away any time. |
    - By default, notifications are sent to your Mattermost mobile app. |
    Away status icon Away + +- Set automatically for you when you've been inactive for more than 5 minutes. System admins can change this value using an experimental configuration setting called `user status away timeout`. +- You're inactive in Mattermost when you're not: typing in or navigating between channels, switching to another browser tab, or when you've minimized or moved the browser window to the background. +- You can manually set yourself as **Away** any time. +- By default, notifications are sent to your Mattermost mobile app. + +
    dnd | Do Not Disturb: |
                                                                                                                                         |
    - Set your availability as Do Not Disturb any time you don't want notifications for a period of time. |
    Do Not Disturb status icon Do Not Disturb + +- Set your availability as **Do Not Disturb** any time you don't want notifications for a period of time. + +
    offline | Offline:
                                                                                                                                         |
    - Set automatically for you when you exit the Mattermost desktop app or close the browser window, sleep or |
      lock your computer, or on mobile when you change apps, close the Mattermost mobile app, or lock your |
      mobile device screen. |
    - You can manually set yourself as Offline any time. |
    - By default, notifications are sent to your Mattermost mobile app. |
    Offline status icon Offline + +- Set automatically for you when you exit the Mattermost desktop app or close the browser window, sleep or lock your computer, or on mobile when you change apps, close the Mattermost mobile app, or lock your mobile device screen. +- You can manually set yourself as **Offline** any time. +- By default, notifications are sent to your Mattermost mobile app. + +
    diff --git a/docs/main/end-user-guide/project-task-management.mdx b/docs/main/end-user-guide/project-management/project-management-index.mdx similarity index 98% rename from docs/main/end-user-guide/project-task-management.mdx rename to docs/main/end-user-guide/project-management/project-management-index.mdx index 5800517304ad..40b1e7d8ee98 100644 --- a/docs/main/end-user-guide/project-task-management.mdx +++ b/docs/main/end-user-guide/project-management/project-management-index.mdx @@ -1,5 +1,6 @@ --- title: "Project and Task Management" +sidebar_label: "Project Management" --- diff --git a/docs/main/end-user-guide/project-management/share-and-collaborate.mdx b/docs/main/end-user-guide/project-management/share-and-collaborate.mdx index 22ff856dea5c..a8a39c55364f 100644 --- a/docs/main/end-user-guide/project-management/share-and-collaborate.mdx +++ b/docs/main/end-user-guide/project-management/share-and-collaborate.mdx @@ -1,6 +1,8 @@ --- title: "Share and collaborate" --- +import useBaseUrl from '@docusaurus/useBaseUrl'; + ## Board permissions @@ -13,47 +15,84 @@ If your boards workspace looks different, you may be on an earlier version of bo --+++++ - + - - + + + + + + + + + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + + - + + + +

    Board permissions

    Modify permissions

    Admin | Editor | Commenter | Viewer |

    =============+=============+================+==============+

    checkmark | | | |

    Board permissionsAdminEditorCommenterViewer
    Modify permissionsYes
    Share a public boardcheckmark | | | |Yes
    Delete boardcheckmark | | | |Yes
    Rename boardcheckmark | checkmark | | |YesYes
    Add, edit, and delete viewscheckmark | checkmark | | |YesYes
    Add, edit, and delete cardscheckmark | checkmark | | |YesYes
    Comment, delete my commentscheckmark | checkmark | checkmark | |YesYesYes
    Delete any commentcheckmark | | | |Yes
    Viewcheckmark | checkmark | checkmark | checkmarkYesYesYesYes
    diff --git a/docs/main/end-user-guide/workflow-automation/interact-with-playbooks.mdx b/docs/main/end-user-guide/workflow-automation/interact-with-playbooks.mdx index edb945073d2d..a67239e90ddb 100644 --- a/docs/main/end-user-guide/workflow-automation/interact-with-playbooks.mdx +++ b/docs/main/end-user-guide/workflow-automation/interact-with-playbooks.mdx @@ -24,7 +24,7 @@ Available slash commands include: ## Playbooks on the go -Learn how to [access and work with playbooks on the go](https://docs.mattermost.com/end-user-guide/workflow-automation.html#itab--Mobile--0_1-Mobile) with the Mattermost mobile app. +Learn how to [access and work with playbooks on the go](/end-user-guide/workflow-automation/work-with-runs#playbook-runs-on-mobile) with the Mattermost mobile app. ## API documentation diff --git a/docs/main/end-user-guide/workflow-automation.mdx b/docs/main/end-user-guide/workflow-automation/workflow-automation-index.mdx similarity index 100% rename from docs/main/end-user-guide/workflow-automation.mdx rename to docs/main/end-user-guide/workflow-automation/workflow-automation-index.mdx diff --git a/docs/main/for/end-user.mdx b/docs/main/for/end-user.mdx index 4b80d66dfaf4..0e921e39ce83 100644 --- a/docs/main/for/end-user.mdx +++ b/docs/main/for/end-user.mdx @@ -25,8 +25,8 @@ You use Mattermost day-to-day to message, collaborate, and get work done. This p {title: 'Send and format messages', to: '/end-user-guide/collaborate/send-messages', description: 'Markdown, code blocks, mentions, attachments.'}, {title: 'Organize with threads', to: '/end-user-guide/collaborate/organize-conversations', description: 'Keep parallel conversations untangled.'}, {title: 'Calls and screen sharing', to: '/end-user-guide/collaborate/audio-and-screensharing', description: 'Audio, screen share, transcription.'}, - {title: 'Playbooks', to: '/end-user-guide/workflow-automation', description: 'Checklists, runs, retrospectives.'}, - {title: 'Boards', to: '/end-user-guide/project-task-management', description: 'Kanban tasks linked to channels.'}, + {title: 'Playbooks', to: '/end-user-guide/workflow-automation/workflow-automation-index', description: 'Checklists, runs, retrospectives.'}, + {title: 'Boards', to: '/end-user-guide/project-management/project-management-index', description: 'Kanban tasks linked to channels.'}, {title: 'Keyboard shortcuts', to: '/end-user-guide/collaborate/keyboard-shortcuts', description: 'Move at the speed of muscle memory.'}, ]} /> diff --git a/docs/main/for/sre.mdx b/docs/main/for/sre.mdx index 8d0ea1344f83..50f86ddb7ed3 100644 --- a/docs/main/for/sre.mdx +++ b/docs/main/for/sre.mdx @@ -14,7 +14,7 @@ You own Mattermost's deployment, scaling, and operations. This page is a curated ## Start here diff --git a/docs/main/index.mdx b/docs/main/index.mdx index a6e82f2690c7..387755183746 100644 --- a/docs/main/index.mdx +++ b/docs/main/index.mdx @@ -48,7 +48,7 @@ Built on an extensible open-core architecture, Mattermost offers an array of col ### Messaging collaboration -[Mattermost Channels](/end-user-guide/messaging-collaboration) enables secure, real-time and asynchronous communication across web, desktop, and mobile—powering mission-critical collaboration and Chat Operations (ChatOps) workflows across connected, hybrid, and air-gapped environments. Channels feature the following capabilities: +[Mattermost Channels](/end-user-guide/collaborate/collaborate-index) enables secure, real-time and asynchronous communication across web, desktop, and mobile—powering mission-critical collaboration and Chat Operations (ChatOps) workflows across connected, hybrid, and air-gapped environments. Channels feature the following capabilities: - [Public](/end-user-guide/collaborate/channel-types#public-channels) and [private](/end-user-guide/collaborate/channel-types#private-channels) channels, [direct messages](/end-user-guide/collaborate/channel-types#direct-message-channels), and [threaded conversations](/end-user-guide/collaborate/organize-conversations) for structured operational coordination. - [Role-based access controls](/end-user-guide/collaborate/learn-about-roles) and [audit logs](/administration-guide/manage/logging#audit-logging) to support need-to-know enforcement. @@ -63,7 +63,7 @@ See the [Client availability](/end-user-guide/access/client-availability) docume ### Workflow automation -[Mattermost Playbooks](/end-user-guide/workflow-automation) standardizes and automates mission workflows such as incident response, shift changeovers, and operational checklists—reducing human error and improving procedural consistency. Playbooks feature: +[Mattermost Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) standardizes and automates mission workflows such as incident response, shift changeovers, and operational checklists—reducing human error and improving procedural consistency. Playbooks feature: - Structured [checklists](/end-user-guide/workflow-automation/work-with-playbooks#make-checklists) with assigned [tasks and due dates](/end-user-guide/workflow-automation/work-with-tasks#tasks-and-due-dates) to operationalize standard and incident or emergency operating procedures. - Automated [status updates and real-time notifications](/end-user-guide/workflow-automation/notifications-and-updates) in linked channels to keep stakeholders informed of workflow progress or blockers. @@ -87,7 +87,7 @@ See the [Client availability](/end-user-guide/access/client-availability) docume ### Project and task management -[Mattermost Boards](/end-user-guide/project-task-management) enables you to coordinate operational work with Kanban-style planning that integrates directly into messaging workflows—enabling transparency, prioritization, and accountability across teams with the following capabilities: +[Mattermost Boards](/end-user-guide/project-management/project-management-index) enables you to coordinate operational work with Kanban-style planning that integrates directly into messaging workflows—enabling transparency, prioritization, and accountability across teams with the following capabilities: - Provides visual task [boards](/end-user-guide/project-management/work-with-boards) with drag-and-drop [cards](/end-user-guide/project-management/work-with-cards) and customizable workflows, supporting contextual awareness and [role-based task visibility](/end-user-guide/project-management/share-and-collaborate#board-permissions). - Delivers real-time updates and synchronization with [linked Mattermost channels](/end-user-guide/project-management/navigate-boards#link-a-board-to-a-channel). diff --git a/docs/main/integrations-guide/integrations-guide-index.mdx b/docs/main/integrations-guide/integrations-guide-index.mdx index dafe882c753e..8d30d1e141bb 100644 --- a/docs/main/integrations-guide/integrations-guide-index.mdx +++ b/docs/main/integrations-guide/integrations-guide-index.mdx @@ -139,9 +139,9 @@ Learn more about additional [no-code automation options](/integrations-guide/no- ### Mattermost Playbooks -[Mattermost Playbooks](/end-user-guide/workflow-automation) lets you define and execute repeatable processes without any coding. Playbooks are often used for incident response, onboarding checklists, or any workflow that involves multiple steps, owners, and notifications. Playbooks have integration points you can use to trigger actions, and they can work in conjunction with plugins, making them a powerful no-code automation tool for orchestrating both human and system actions. +[Mattermost Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) lets you define and execute repeatable processes without any coding. Playbooks are often used for incident response, onboarding checklists, or any workflow that involves multiple steps, owners, and notifications. Playbooks have integration points you can use to trigger actions, and they can work in conjunction with plugins, making them a powerful no-code automation tool for orchestrating both human and system actions. -Learn more about using [Playbooks](/end-user-guide/workflow-automation). +Learn more about using [Playbooks](/end-user-guide/workflow-automation/workflow-automation-index). ## Frequently Asked Questions diff --git a/docs/main/integrations-guide/popular-integrations.mdx b/docs/main/integrations-guide/popular-integrations.mdx index 5680180b5c90..a6e71c5b42c0 100644 --- a/docs/main/integrations-guide/popular-integrations.mdx +++ b/docs/main/integrations-guide/popular-integrations.mdx @@ -1,119 +1,43 @@ --- title: "Popular Pre-Built Integrations" --- +import useBaseUrl from '@docusaurus/useBaseUrl'; + Accelerate your operational and technical workflows by connecting Mattermost with your mission-critical tools through pre-built integrations. ## Mattermost Integrations Designed for teams that need reliability, auditability, and ownership of their collaboration stack, the following Mattermost collaboration integrations keep your data all inside your secure Mattermost ecosystem. Reduce tool sprawl, strengthen security, improve compliance posture, all with a seamless user experience. - --- - - - - - - - - - - - - - - -
    -

    Mattermost Integration | How to Get It | What It Does |

    -
    -
    -
    ================================================================================================+===================================================================+============================================================================================================+
    -
    -

    Mattermost Channel Export | product-list > App Marketplace | Exports channel history and data for compliance purposes.

    -
    -
    Mattermost Legal Hold | Manual Upload | Keeps a copy of messages and files so they cannot be deleted for legal or compliance needs. |
    Mattermost Metrics | product-list > App Marketplace | Shows numbers about system performance when people use Mattermost.
    Mattermost User Survey | product-list > App Marketplace | Collects feedback by asking questions directly in Mattermost channels.
    +| Mattermost Integration | How to Get It | What It Does | +| --- | --- | --- | +| [Mattermost Channel Export](/administration-guide/comply/export-mattermost-channel-data) | Product menu icon > **App Marketplace** | Exports channel history and data for compliance purposes. | +| [Mattermost Legal Hold](/administration-guide/comply/legal-hold) | Manual Upload | Keeps a copy of messages and files so they cannot be deleted for legal or compliance needs. | +| [Mattermost Metrics](/administration-guide/scale/collect-performance-metrics) | Product menu icon > **App Marketplace** | Shows numbers about system performance when people use Mattermost. | +| [Mattermost User Survey](/administration-guide/configure/manage-user-surveys) | Product menu icon > **App Marketplace** | Collects feedback by asking questions directly in Mattermost channels. | ## Microsoft Integrations If your organization relies on Microsoft tools, Mattermost offers deep integrations with M365, Teams, Outlook, and Calendar to keep collaboration seamless and secure. These integrations allow you to enhance and extend Microsoft tools while maintaining Mattermost as your central hub. By connecting Mattermost with Microsoft apps, you reduce context switching, strengthen reliability through secure fallback options, and ensure your workflows stay efficient across both platforms. - --- - - - - - - - - - - - - - - -
    -

    Microsoft Integration | Where to Get It | What It Does |

    -
    -
    -
    ==================================================================================+======================================+===============================================================================================+
    -
    -

    Mattermost Embedded for M365, Teams, and | Manual Upload | Embed Mattermost in Microsoft apps. | Outlook | | Use Outlook and Teams together with Mattermost for secure backup and extra features. |

    -
    -
    Microsoft Calendar | product-list > App Marketplace | Sends your Microsoft Calendar reminders into Mattermost so you don’t miss events.
    Microsoft Teams Meetings | product-list > App Marketplace | Lets you start or join Microsoft Teams meetings directly from Mattermost.
    Microsoft Teams Sync | product-list > App Marketplace | Copies messages from Microsoft Teams into Mattermost (one-way) to keep conversations in sync.
    +| Microsoft Integration | Where to Get It | What It Does | +| --- | --- | --- | +| [Mattermost Embedded for M365, Teams, and Outlook](/integrations-guide/mattermost-mission-collaboration-for-m365) | Manual Upload | Embed Mattermost in Microsoft apps. Use Outlook and Teams together with Mattermost for secure backup and extra features. | +| [Microsoft Calendar](/integrations-guide/microsoft-calendar) | Product menu icon > **App Marketplace** | Sends your Microsoft Calendar reminders into Mattermost so you don't miss events. | +| [Microsoft Teams Meetings](/integrations-guide/microsoft-teams-meetings) | Product menu icon > **App Marketplace** | Lets you start or join Microsoft Teams meetings directly from Mattermost. | +| [Microsoft Teams Sync](/integrations-guide/microsoft-teams-sync) | Product menu icon > **App Marketplace** | Copies messages from Microsoft Teams into Mattermost (one-way) to keep conversations in sync. | The following integrations bring key external tools (DevOps, ITSM, monitoring, and meetings) into Mattermost, so teams can see updates, take action, and collaborate faster without leaving their secure chat environment. - ------ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    -

    Third-Party Integration | Where to Get It | What It Does |

    -
    -
    -
    =========================================================================+===================================================================+=========================================================================================================================+
    -
    -

    GitHub | product-list > App Marketplace | Shows GitHub updates, like new pull requests, commits, or issues, in Mattermost channels.

    -
    -
    GitLab | product-list > App Marketplace | Sends GitLab activity (code pushes, merge requests, CI/CD pipeline updates) into Mattermost channels.
    Jira | product-list > App Marketplace | Lets you see, create, and update Jira issues inside Mattermost.
    PexipMattermost MarketplaceLets you start and join Pexip video meetings from Mattermost.
    ServiceNow | product-list > App Marketplace | Sends ServiceNow updates (like tickets or incidents) into Mattermost and lets you take action from chat.
    SplunkMattermost MarketplaceSends Splunk alerts and search results directly into Mattermost channels.
    Zoom | product-list > App Marketplace | Lets you start and join Zoom meetings from inside Mattermost.
    +| Third-Party Integration | Where to Get It | What It Does | +| --- | --- | --- | +| [GitHub](/integrations-guide/github) | Product menu icon > **App Marketplace** | Shows GitHub updates, like new pull requests, commits, or issues, in Mattermost channels. | +| [GitLab](/integrations-guide/gitlab) | Product menu icon > **App Marketplace** | Sends GitLab activity (code pushes, merge requests, CI/CD pipeline updates) into Mattermost channels. | +| [Jira](/integrations-guide/jira) | Product menu icon > **App Marketplace** | Lets you see, create, and update Jira issues inside Mattermost. | +| [Pexip](https://mattermost.com/marketplace/pexip-video-connect/) | [Mattermost Marketplace](https://mattermost.com/marketplace/) | Lets you start and join Pexip video meetings from Mattermost. | +| [ServiceNow](/integrations-guide/servicenow) | Product menu icon > **App Marketplace** | Sends ServiceNow updates (like tickets or incidents) into Mattermost and lets you take action from chat. | +| [Splunk](https://mattermost.com/marketplace/splunk-2/) | [Mattermost Marketplace](https://mattermost.com/marketplace/) | Sends Splunk alerts and search results directly into Mattermost channels. | +| [Zoom](/integrations-guide/zoom) | Product menu icon > **App Marketplace** | Lets you start and join Zoom meetings from inside Mattermost. | ## More on Marketplace diff --git a/docs/main/product-overview/faq-license.mdx b/docs/main/product-overview/faq-license.mdx index a35ab17deced..efd23b1c9cb6 100644 --- a/docs/main/product-overview/faq-license.mdx +++ b/docs/main/product-overview/faq-license.mdx @@ -14,18 +14,30 @@ The following outlines the licensing, terms of use and privacy policies across M - + -

    Software

    ====================================================

    Mattermost Team Edition (Open Source)

    -

    License | Terms and Conditions | Privacy Policy |

    ============================================================================================================================+===============================================================================================================================+=============================================================================================+
    Open Source MIT License. | Mattermost Trademark Policy | Mattermost Server Privacy Policy |
                                                                                                                                  | with GDPR Data Processing Addendum. |

    Open Source Add-ons available under Apache v2 and other licenses. | Mattermost Terms of Use |

    ----------------------------------------------------------------------------------------------------------------------------+-------------------------------------------------------------------------------------------------------------------------------+ |
    Commercial Enterprise Edition License. | No subscription terms apply when operating without a subscription | |
                                                                                                                                  | |

    You are welcome to use the Enterprise Edition of Mattermost free of charge in perpetuity when the subscription feature | | | are not enabled by a license key. +-------------------------------------------------------------------------------------------------------------------------------+ | | Self Managed Subscription Terms | | If you choose to purchase a subscription for paid features, terms and conditions are offered | | | as part of the subscription purchase (see “Terms”). | Enterprise Edition Subscription Terms for Purchase by Resale | | | | | | Cloud Subscription Agreement | |

    +Software +License +Terms and Conditions +Privacy Policy + + +Mattermost Team Edition (Open Source) +Open Source MIT License.
    Open Source Add-ons available under Apache v2 and other licenses. +Mattermost Trademark Policy
    Mattermost Terms of Use +Mattermost Server Privacy Policy with GDPR Data Processing Addendum. Mattermost Enterprise Edition with no subscription +Commercial Enterprise Edition License.
    You are welcome to use the Enterprise Edition of Mattermost free of charge in perpetuity when the subscription features are not enabled by a license key. +No subscription terms apply when operating without a subscription Mattermost Enterprise Edition with subscription +If you choose to purchase a subscription for paid features, terms and conditions are offered as part of the subscription purchase (see “Terms”). +Self Managed Subscription Terms
    Enterprise Edition Subscription Terms for Purchase by Resale
    Cloud Subscription Agreement diff --git a/docs/main/product-overview/faq-mattermost-source-available-license.mdx b/docs/main/product-overview/faq-mattermost-source-available-license.mdx index 2fa5b64926a1..9cc4cd60b399 100644 --- a/docs/main/product-overview/faq-mattermost-source-available-license.mdx +++ b/docs/main/product-overview/faq-mattermost-source-available-license.mdx @@ -27,7 +27,7 @@ As we add enterprise-only functionality, we will update the license on affected ## To which repositories does this apply? -We plan to apply this license to the enterprise directories of our [Jira](https://github.com/mattermost/mattermost-plugin-jira), [Microsoft Calendar Integration](https://github.com/mattermost/mattermost-plugin-mscalendar), and [Microsoft Teams Meetings](https://github.com/mattermost/mattermost-plugin-msteams-meetings) plugins. We also intend to release [Collaborative playbooks](/end-user-guide/workflow-automation) and [Channel Export](https://github.com/mattermost/mattermost-plugin-channel-export) plugins under the Mattermost Source Available License. New, Mattermost-authored plugins will generally be released under the Mattermost Source Available License. When we update the licenses, we will release a new version and note the change in the `README.md` file of the GitHub repository and any release notes. +We plan to apply this license to the enterprise directories of our [Jira](https://github.com/mattermost/mattermost-plugin-jira), [Microsoft Calendar Integration](https://github.com/mattermost/mattermost-plugin-mscalendar), and [Microsoft Teams Meetings](https://github.com/mattermost/mattermost-plugin-msteams-meetings) plugins. We also intend to release [Collaborative playbooks](/end-user-guide/workflow-automation/workflow-automation-index) and [Channel Export](https://github.com/mattermost/mattermost-plugin-channel-export) plugins under the Mattermost Source Available License. New, Mattermost-authored plugins will generally be released under the Mattermost Source Available License. When we update the licenses, we will release a new version and note the change in the `README.md` file of the GitHub repository and any release notes. We expect to keep plugins without an enterprise component under our open source license. No licensing changes are planned to non-plugin repositories, such as [mattermost](https://github.com/mattermost/mattermost) or [mattermost webapp](https://github.com/mattermost/mattermost/tree/master/webapp). diff --git a/docs/main/product-overview/mattermost-v10-changelog.mdx b/docs/main/product-overview/mattermost-v10-changelog.mdx index 8d5a0bad446a..478ae0888a66 100644 --- a/docs/main/product-overview/mattermost-v10-changelog.mdx +++ b/docs/main/product-overview/mattermost-v10-changelog.mdx @@ -17,12 +17,12 @@ Platform and OS scope reflects reported and tested environments and may not repr ## Release v10.12 - [Feature Release](https://docs.mattermost.com/product-overview/release-policy.html#release-types) \{#release-v10-12-feature-release} - **10.12.4, released 2025-11-21** - + **Critical Fixes** - Mattermost v10.12.4 contains a Critical severity level security fix in the Jira plugin. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Pre-packaged Jira plugin version [v4.4.1](https://github.com/mattermost/mattermost-plugin-jira/releases/tag/v4.4.1). - Mattermost v10.12.4 contains no database or functional changes. - **10.12.3, released 2025-11-17** @@ -32,12 +32,12 @@ Platform and OS scope reflects reported and tested environments and may not repr - Fixed a configuration retention issue where even active configuration got deleted. - Mattermost v10.12.3 contains no database or functional changes. - **10.12.2, released 2025-10-28** - + **Critical Fixes** - Mattermost v10.12.2 contains Critical severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Pre-packaged Boards plugin [v9.1.7](https://github.com/mattermost/mattermost-plugin-boards/releases/tag/v9.1.7). - Mattermost v10.12.2 contains no database or functional changes. - **10.12.1, released 2025-10-15** @@ -85,12 +85,12 @@ If you upgrade from a release earlier than v10.10, please read the other [Import ## Release v10.11 - [Extended Support Release](https://docs.mattermost.com/product-overview/release-policy.html#release-types) \{#release-v10-11-extended-support-release} - + **Known Issue** - Customers should not upgrade from ``>=10.11.17`` to ``<=11.7.2`` due to a bug that causes an issue with database migration numbers. The bug is fixed in v11.7.3. - + - **10.11.23, released 2026-08-13** - Mattermost v10.11.23 contains low to high severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release is recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). @@ -180,12 +180,12 @@ If you upgrade from a release earlier than v10.10, please read the other [Import - Fixed an issue where rate limiting was missing from the login endpoint (5 requests/second, 10 burst). - Mattermost v10.11.12 contains no database or functional changes. - **10.11.11, released 2026-02-13** - + **Breaking Changes** - Photoshop Document (PSD) files are now no longer inline previewed, they are treated as regular file attachments. - + - Mattermost v10.11.11 contains low to high severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release is recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - Pre-packaged Boards plugin version [v9.2.2](https://github.com/mattermost/mattermost-plugin-boards/releases/tag/v9.2.2). - Fixed an issue where the channel URL got updated when the channel display name was changed. @@ -216,12 +216,12 @@ If you upgrade from a release earlier than v10.10, please read the other [Import - Fixed an issue where pressing ``Shift+Up`` in the channel textbox to reply to a thread could cause the right‑hand sidebar (RHS) reply textbox to not focus. - Mattermost v10.11.9 contains no database or functional changes. - **10.11.8, released 2025-11-21** - + **Critical Fixes** - Mattermost v10.11.8 contains a Critical severity level security fix in the Jira plugin. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Pre-packaged Jira plugin version [v4.4.1](https://github.com/mattermost/mattermost-plugin-jira/releases/tag/v4.4.1). - Mattermost v10.11.8 contains no database or functional changes. - **10.11.7, released 2025-11-17** @@ -237,12 +237,12 @@ If you upgrade from a release earlier than v10.10, please read the other [Import - Pre-packaged MS Teams Meeting plugin version [v2.3.0](https://github.com/mattermost/mattermost-plugin-msteams-meetings/releases/tag/v2.3.0). - Mattermost v10.11.6 contains no database or functional changes. - **10.11.5, released 2025-10-28** - + **Critical Fixes** - Mattermost v10.11.5 contains Critical severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Pre-packaged Boards plugin [v9.1.7](https://github.com/mattermost/mattermost-plugin-boards/releases/tag/v9.1.7). - Mattermost v10.11.5 contains no database or functional changes. - **10.11.4, released 2025-10-15** @@ -903,12 +903,12 @@ New setting options were added to ``config.json``. Below is a list of the additi - Fixed Go v1.23 incompatibility issues with plugins. - Mattermost v10.5.14 contains no database or functional changes. - **10.5.13, released 2025-10-28** - + **Critical Fixes** - Mattermost v10.5.13 contains Critical severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Pre-packaged Boards plugin [v9.1.7](https://github.com/mattermost/mattermost-plugin-boards/releases/tag/v9.1.7). - Mattermost v10.5.13 contains no database or functional changes. - **10.5.12, released 2025-10-15** diff --git a/docs/main/product-overview/mattermost-v11-changelog.mdx b/docs/main/product-overview/mattermost-v11-changelog.mdx index 29f571d2161a..412bc6781db6 100644 --- a/docs/main/product-overview/mattermost-v11-changelog.mdx +++ b/docs/main/product-overview/mattermost-v11-changelog.mdx @@ -16,12 +16,12 @@ Platform and OS scope reflects reported and tested environments and may not repr ## Release v11.10 - [Feature Release](https://docs.mattermost.com/product-overview/release-policy.html#release-types) \{#release-v11-10-feature-release} - + **Breaking Changes** - Starting with v11.10.1, the updated `glibc-openssl-fips` bundle includes a new OpenSSL build that enforces the FIPS minimum key length of 112 bits for HMAC operations. The PostgreSQL driver (lib/pq) passes the database password as the HMAC key during SCRAM authentication, so a password shorter than 112 bits, or 14 ASCII characters, now causes a panic on connect rather than a graceful failure. Before upgrading, FIPS deployments should verify that the password in `SqlSettings.DataSource` is at least 14 characters and rotate it in PostgreSQL if it is shorter. Standard, non-FIPS builds are unaffected. - + - **11.10.1, released 2026-08-25** - Mattermost v11.10.1 contains medium to high severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release is recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). @@ -223,12 +223,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-10-is-now-availa - **11.9.0, released 2026-07-16** - Original 11.9.0 release. - + **Breaking Changes** - Mattermost v11.9 changes how redirect URI allowlist patterns are matched for OAuth Dynamic Client Registration (DCR). Patterns are now evaluated per URL component (scheme, host, path, and query) rather than as a whole-string glob. See the [Important Upgrade Notes](https://docs.mattermost.com/upgrade/important-upgrade-notes.html) for more details. - + ### Upgrade Impact @@ -427,12 +427,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-9-is-now-availab - **11.8.0, released 2026-06-16** - Original 11.8.0 release. - + **Breaking Changes** - The Custom Profile Attributes property group is renamed from ``custom_profile_attributes`` to ``access_control``, and CPA fields and values are migrated from the legacy property model to the v2 model. The functionality of the CPA feature is unchanged. Plugin developers that use CPA will need to register against the new group name. - + ### Upgrade Impact @@ -589,12 +589,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-8-0-is-now-avail ## Release v11.7 - [Extended Support Release](https://docs.mattermost.com/product-overview/release-policy.html#release-types) \{#release-v11-7-extended-support-release} - + **Breaking Changes** - Starting with v11.7.10, the updated `glibc-openssl-fips` bundle includes a new OpenSSL build that enforces the FIPS minimum key length of 112 bits for HMAC operations. The PostgreSQL driver (lib/pq) passes the database password as the HMAC key during SCRAM authentication, so a password shorter than 112 bits, or 14 ASCII characters, now causes a panic on connect rather than a graceful failure. Before upgrading, FIPS deployments should verify that the password in `SqlSettings.DataSource` is at least 14 characters and rotate it in PostgreSQL if it is shorter. Standard, non-FIPS builds are unaffected. - + - **11.7.10, released 2026-08-26** - Mattermost v11.7.10 contains low to high severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release is recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). @@ -692,13 +692,13 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-8-0-is-now-avail - **11.7.0, released 2026-05-15** - Original 11.7.0 release. - + **Breaking Changes** - FIPS builds require a minimum of 14 characters for passwords, atmos/camo proxy configuration, and shared channel secrets. Shorter passwords for existing users will no longer be valid and require a password reset. Non-FIPS builds are unaffected. - v11.7 includes Agents plugin v2. Please see [this guide](/agents/docs/upgrading_to_2.0) on how to upgrade the Mattermost Agents plugin from a v1.x release to v2.0.0. It covers the supported version path, the migrations that run automatically on first start of v2.0.0, the breaking changes and default-behavior flips that admins should know about before the upgrade window, and the verification steps to confirm the upgrade succeeded. - + #### Database Schema Changes - The following schema changes are included in the v11.7 release. No database downtime is expected for this upgrade. See the [Important Upgrade Notes](https://docs.mattermost.com/upgrade/important-upgrade-notes.html) for more details. @@ -1186,12 +1186,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-5-is-now-availab ### Upgrade Impact - + **Breaking Changes** - Photoshop Document (PSD) files are now no longer inline previewed, they are treated as regular file attachments. - + #### Database Schema Changes - Added two new tables, ``Recaps`` and ``RecapChannels``. No database downtime is expected for this upgrade. See the [Important Upgrade Notes](https://docs.mattermost.com/upgrade/important-upgrade-notes.html) for more details. @@ -1271,12 +1271,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-4-is-now-availab - Fixed an issue where rate limiting was missing from the login endpoint (5 requests/second, 10 burst). - Mattermost v11.3.2 contains no database or functional changes. - **11.3.1, released 2026-02-13** - + **Breaking Changes** - Photoshop Document (PSD) files are now no longer inline previewed, they are treated as regular file attachments. - + - Mattermost v11.3.1 contains medium to high severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release is recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - Pre-packaged Boards plugin version [v9.2.2](https://github.com/mattermost/mattermost-plugin-boards/releases/tag/v9.2.2). - Pre-packaged Playbooks plugin version [v2.6.2](https://github.com/mattermost/mattermost-plugin-playbooks/releases/tag/v2.6.2). @@ -1395,12 +1395,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-3-is-now-availab - Fixed an issue where rate limiting was missing from the login endpoint (5 requests/second, 10 burst). - Mattermost v11.2.4 contains no database or functional changes. - **11.2.3, released 2026-02-13** - + **Breaking Changes** - Photoshop Document (PSD) files are now no longer inline previewed, they are treated as regular file attachments. - + - Mattermost v11.2.3 contains medium to high severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release is recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - Pre-packaged Boards plugin version [v9.2.2](https://github.com/mattermost/mattermost-plugin-boards/releases/tag/v9.2.2). - Pre-packaged Playbooks plugin version [v2.6.2](https://github.com/mattermost/mattermost-plugin-playbooks/releases/tag/v2.6.2). @@ -1526,12 +1526,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-2-is-now-availab - Fixed an issue where Chrome/Desktop App spell check on Windows often couldn't correct typos. - Mattermost v11.1.2 contains no database or functional changes. - **11.1.1, released 2025-11-21** - + **Critical Fixes** - Mattermost v11.1.1 contains a Critical severity level security fix in the Jira plugin. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Pre-packaged Jira plugin version [v4.4.1](https://github.com/mattermost/mattermost-plugin-jira/releases/tag/v4.4.1). - Fixed an issue where thread popouts did not show the current user's status. - Fixed an issue where clicking on a permalink to a reply in another thread would not navigate the main window. @@ -1540,12 +1540,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-2-is-now-availab - **11.1.0, released 2025-11-14** - Original 11.1.0 release. - + **Breaking Changes** - The version of React used by the Mattermost web app has been updated from React 17 to React 18. See more details in [this forum post](https://forum.mattermost.com/t/upgrading-the-mattermost-web-app-to-react-18-v11/25000). - + ### Upgrade Impact @@ -1623,12 +1623,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-1-is-now-availab - Mattermost v11.0.7 contains medium severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release is recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - Mattermost v11.0.7 contains no database or functional changes. - **11.0.6, released 2025-11-21** - + **Critical Fixes** - Mattermost v11.0.6 contains a Critical severity level security fix in the Jira plugin. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Pre-packaged Jira plugin version [v4.4.1](https://github.com/mattermost/mattermost-plugin-jira/releases/tag/v4.4.1). - Mattermost v11.0.6 contains no database or functional changes. - **11.0.5, released 2025-11-17** @@ -1639,12 +1639,12 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-1-is-now-availab - Fixed an issue where plugins could not receive 3rd-party authorization headers. - Mattermost v11.0.5 contains no database or functional changes. - **11.0.4, released 2025-10-28** - + **Critical Fixes** - Mattermost v11.0.4 contains Critical severity level security fixes. [Upgrading](https://docs.mattermost.com/upgrade/upgrading-mattermost-server.html) to this release as soon as possible is highly recommended. Details will be posted on our [security updates page](https://mattermost.com/security-updates/) 30 days after release as per the [Mattermost Responsible Disclosure Policy](https://mattermost.com/security-vulnerability-report/). - + - Fixed an issue where plugin configuration settings were incorrectly sanitized, causing API endpoints and plugins to receive masked values instead of actual configuration values. - Pre-packaged Boards plugin [v9.1.7](https://github.com/mattermost/mattermost-plugin-boards/releases/tag/v9.1.7). - Mattermost v11.0.4 contains no database or functional changes. @@ -1655,7 +1655,7 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-1-is-now-availab - **11.0.1, released 2025-10-16** - Original 11.0.1 release. - + **Breaking Changes** - GitLab SSO has been deprecated from Team Edition. Deployments using GitLab SSO can remain on v10.11 ESR (with 12 months of security updates), transition to our new free offering Mattermost Entry, or can explore commercial/nonprofit options. See more details in [this forum post](https://forum.mattermost.com/t/mattermost-v11-changes-in-free-offerings/25126). @@ -1686,7 +1686,7 @@ See [this blog post](https://mattermost.com/blog/mattermost-v11-1-is-now-availab - Stopped supporting manually installed plugins as per https://forum.mattermost.com/t/deprecation-notice-manual-plugin-deployment/21192 - Support for PostgreSQL v13 has been removed. The new minimum PostgreSQL version is v14+. See the [minimum supported PostgreSQL version policy](https://docs.mattermost.com/deployment-guide/software-hardware-requirements.html#minimum-postgresql-database-support-policy) documentation for details. - + ### Upgrade Impact diff --git a/docs/main/recipes/product-vulnerability-incident.mdx b/docs/main/recipes/product-vulnerability-incident.mdx index ebb1a7826baa..0bfbace28526 100644 --- a/docs/main/recipes/product-vulnerability-incident.mdx +++ b/docs/main/recipes/product-vulnerability-incident.mdx @@ -7,7 +7,7 @@ After getting access to your Mattermost instance, you’d like to set up a workf ## Solution -This guide walks through the set up of a product security incident room using [Collaborative Playbooks](/end-user-guide/workflow-automation) and [Channels](/end-user-guide/messaging-collaboration) with voice calling and screen sharing functionalities. +This guide walks through the set up of a product security incident room using [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) and [Channels](/end-user-guide/collaborate/collaborate-index) with voice calling and screen sharing functionalities. ### 1. Workspace setup diff --git a/docs/main/security-guide/mobile-security.mdx b/docs/main/security-guide/mobile-security.mdx index c306bb4fc783..0e45f16515f1 100644 --- a/docs/main/security-guide/mobile-security.mdx +++ b/docs/main/security-guide/mobile-security.mdx @@ -15,7 +15,7 @@ Administrators can remotely wipe Mattermost data from mobile devices in case of ### Compliance policies -Mattermost can be integrated with mobile device management solutions to enforce compliance policies. These policies ensure that mobile devices accessing the application adhere to security standards, such as encryption, password complexity, and device integrity. Learm more about [compliance with Mattermost](/administration-guide/compliance-with-mattermost). +Mattermost can be integrated with mobile device management solutions to enforce compliance policies. These policies ensure that mobile devices accessing the application adhere to security standards, such as encryption, password complexity, and device integrity. Learn more about [compliance with Mattermost](/administration-guide/comply/comply-index). ### Mobile access platforms diff --git a/docs/main/use-case-guide/devops-collaboration.mdx b/docs/main/use-case-guide/devops-collaboration.mdx index f38b3ceb9239..3fd028f39bce 100644 --- a/docs/main/use-case-guide/devops-collaboration.mdx +++ b/docs/main/use-case-guide/devops-collaboration.mdx @@ -16,8 +16,8 @@ Coordinating secure software delivery requires tight integration between code co **Benefits** - **Automate pipeline visibility and alerting** by integrating with CI/CD tools like GitLab, Jenkins, and GitHub Actions using the [Mattermost integrations platform](/integrations-guide/integrations-guide-index). -- **Coordinate secure releases and hotfixes** using [Collaborative Playbooks](/end-user-guide/workflow-automation) to manage rollout steps, validation gates, and team notifications. -- **Enable traceable delivery communications** through [channel-based collaboration](/end-user-guide/messaging-collaboration), ensuring build logs, changelogs, and approvals remain accessible and audit-ready. +- **Coordinate secure releases and hotfixes** using [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) to manage rollout steps, validation gates, and team notifications. +- **Enable traceable delivery communications** through [channel-based collaboration](/end-user-guide/collaborate/collaborate-index), ensuring build logs, changelogs, and approvals remain accessible and audit-ready. - **Support deployments in regulated and sovereign environments** using [self-hosted Kubernetes deployment models](/deployment-guide/server/deploy-kubernetes) for full control over CI/CD communications. ## Platform Engineering & Internal Developer Platforms (IDPs) @@ -26,8 +26,8 @@ Platform teams need streamlined, secure ways to deliver services and enable deve **Benefits** -- **Centralize platform requests and updates** in [dedicated channels](/end-user-guide/messaging-collaboration) that organize provisioning, support, and environment status discussions. -- **Automate ticket triage and escalation workflows** using [Playbooks](/end-user-guide/workflow-automation) to track response SLAs and ownership across platform operations. +- **Centralize platform requests and updates** in [dedicated channels](/end-user-guide/collaborate/collaborate-index) that organize provisioning, support, and environment status discussions. +- **Automate ticket triage and escalation workflows** using [Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) to track response SLAs and ownership across platform operations. - **Monitor infrastructure health and changes** with integrated feeds from [Prometheus, Grafana](/administration-guide/scale/deploy-prometheus-grafana-for-performance-monitoring), or custom observability tools—supporting faster feedback loops. - **Support hybrid cloud and edge operations** through [deployment flexibility](/deployment-guide/server/server-deployment-planning#deployment-options) across public, private, and disconnected environments. @@ -37,7 +37,7 @@ Real-time visibility and structured collaboration are critical during service de **Benefits** -- **Automate incident handling** with [Playbooks](/end-user-guide/workflow-automation) to track diagnostics, assign tasks, and issue updates—supporting NOC, SRE, and AppSec workflows. +- **Automate incident handling** with [Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) to track diagnostics, assign tasks, and issue updates—supporting NOC, SRE, and AppSec workflows. - **Accelerate containment and recovery** by [integrating alerting tools](/integrations-guide/integrations-guide-index#webhooks) like PagerDuty, Opsgenie, and custom webhooks into secure Mattermost channels. - **Ensure communication continuity** during outages using [high availability architecture](/deployment-guide/reference-architecture/scale/high-availability-cluster-based-deployment) and [support for disconnected environments](/deployment-guide/reference-architecture/deployment-scenarios/air-gapped-deployment). - **Enable forensic review and audit** with [logging and export capabilities](/administration-guide/manage/logging#audit-logging) that preserve all incident-related communications. @@ -49,7 +49,7 @@ Critical infrastructure DevSecOps must align with strict security, audit, and co **Benefits** - **Apply granular role-based access controls** using [advanced permissions](/administration-guide/onboard/advanced-permissions) and [channel-specific configurations](/administration-guide/manage/team-channel-members#advanced-access-controls) to protect sensitive workflows. -- **Support supply chain security coordination** by using [Playbooks](/end-user-guide/workflow-automation) to manage SBOM reviews, vendor risk analysis, and software intake workflows across internal and external teams. +- **Support supply chain security coordination** by using [Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) to manage SBOM reviews, vendor risk analysis, and software intake workflows across internal and external teams. - **Enforce secure collaboration behavior** through [custom Terms of Service](/administration-guide/comply/custom-terms-of-service), [data retention policies](/administration-guide/comply/data-retention-policy), and user authentication tied to [SSO and Entra ID](/administration-guide/onboard/sso-entraid). - **Deploy in line** with [Zero Trust](/security-guide/zero-trust) principles with [self-managed, segmented deployments](/deployment-guide/server/server-deployment-planning#deployment-options) that enforce identity, access, and policy boundaries—suitable for classified or sovereign cloud environments. diff --git a/docs/main/use-case-guide/integrated-security-operations.mdx b/docs/main/use-case-guide/integrated-security-operations.mdx index ab813327b051..1a58074ed528 100644 --- a/docs/main/use-case-guide/integrated-security-operations.mdx +++ b/docs/main/use-case-guide/integrated-security-operations.mdx @@ -17,7 +17,7 @@ SOCs are the front lines of real-time monitoring, triage, and escalation. Coordi **Benefits** -- **Accelerate triage and response workflows** with [Collaborative Playbooks](/end-user-guide/workflow-automation) that automate escalations, task assignment, and ticket updates for consistent response execution. +- **Accelerate triage and response workflows** with [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) that automate escalations, task assignment, and ticket updates for consistent response execution. - **Integrate detection pipelines and observability tools** using the [Mattermost integrations platform](/integrations-guide/integrations-guide-index) to surface alerts from SIEM, SOAR, and log analysis systems into dedicated response channels. - **Maintain operational security and compliance** through [role-based permissions](/administration-guide/onboard/advanced-permissions) and [audit logging](/administration-guide/manage/logging#audit-logging) to safeguard sensitive incident data. - **Operate in secure, classified, or hybrid environments** using Kubernetes or Linux on the infrastructure of your choice: Public cloud, organization data center, or fully air-gapped. [Explore deployment options](/deployment-guide/server/server-deployment-planning#deployment-options). @@ -29,8 +29,8 @@ CERTs serve as rapid-response teams during high-risk events, requiring tight coo **Benefits** -- **Orchestrate high-stakes incident response** through [Collaborative Playbooks](/end-user-guide/workflow-automation) tailored for malware outbreaks, data exfiltration events, and zero-day exploits. -- **Centralize and structure communication** with [channel-based collaboration](/end-user-guide/messaging-collaboration), including [file sharing](/end-user-guide/collaborate/share-files-in-messages), [threaded updates](/end-user-guide/collaborate/organize-conversations), and task-tracking across affected teams. +- **Orchestrate high-stakes incident response** through [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) tailored for malware outbreaks, data exfiltration events, and zero-day exploits. +- **Centralize and structure communication** with [channel-based collaboration](/end-user-guide/collaborate/collaborate-index), including [file sharing](/end-user-guide/collaborate/share-files-in-messages), [threaded updates](/end-user-guide/collaborate/organize-conversations), and task-tracking across affected teams. - **Enable coordination across geographies** using [multi-device access](/deployment-guide/deployment-guide-index) and [mobile EMM support](/deployment-guide/mobile/deploy-mobile-apps-using-emm-provider) for secure participation across locations and devices. - **Preserve evidentiary and compliance data** through [audit logs](/administration-guide/manage/logging#audit-logging) and configurable [exports](/administration-guide/manage/bulk-export-tool) for legal review or forensic handoff. - **Ensure data sovereignty** with flexible hosting options including EU-resident infrastructure, on-premises deployments, and air-gapped environments that maintain full control over sensitive communications. diff --git a/docs/main/use-case-guide/maximize-microsoft-investments.mdx b/docs/main/use-case-guide/maximize-microsoft-investments.mdx index 47badb9c6158..1783ea3c2976 100644 --- a/docs/main/use-case-guide/maximize-microsoft-investments.mdx +++ b/docs/main/use-case-guide/maximize-microsoft-investments.mdx @@ -40,7 +40,7 @@ Operational teams need to extend Microsoft capabilities to mission environments **Benefits** - **Enable mission-critical coordination at the edge** by [deploying Mattermost in secure, on-prem or air-gapped environments](/deployment-guide/server/server-deployment-planning#deployment-options) [integrated with Microsoft Teams and Outlook](/integrations-guide/mattermost-mission-collaboration-for-m365). -- **Fuse data and decision-making across platforms** with support for [toolchain integration](/integrations-guide/integrations-guide-index), [audio/screen share](/end-user-guide/collaborate/make-calls), and [workflow automation](/end-user-guide/workflow-automation) embedded into a dedicated Mission Operations Platform. +- **Fuse data and decision-making across platforms** with support for [toolchain integration](/integrations-guide/integrations-guide-index), [audio/screen share](/end-user-guide/collaborate/make-calls), and [workflow automation](/end-user-guide/workflow-automation/workflow-automation-index) embedded into a dedicated Mission Operations Platform. - **Maintain coalition and partner alignment** through [interoperable Connected Workspaces](/administration-guide/onboard/connected-workspaces) supporting collaboration across mission partner networks. - **Accelerate action with mission-tuned AI** using secure Azure AI and [Mattermost Copilot](/end-user-guide/agents) to summarize context, guide decisions, and automate operational tasks. - **Secure every communication path** with built-in [Zero Trust controls](/security-guide/zero-trust) and deploy on Azure or sovereign environments for maximum flexibility and compliance. diff --git a/docs/main/use-case-guide/out-of-band-incident-response.mdx b/docs/main/use-case-guide/out-of-band-incident-response.mdx index 310f6bc5cb94..cf5dfea28ccd 100644 --- a/docs/main/use-case-guide/out-of-band-incident-response.mdx +++ b/docs/main/use-case-guide/out-of-band-incident-response.mdx @@ -30,7 +30,7 @@ Outages and downtime threaten both productivity and revenue. In large enterprise **Benefits** - **Scale communication globally** with Mattermost's [high availability and horizontal scalability architecture](/deployment-guide/reference-architecture/scale/scaling-for-enterprise), supporting tens of thousands of users across enterprise, field, government, or classified environments. -- **Accelerate outage recovery** using [Collaborative Playbooks](/end-user-guide/workflow-automation) that automate response steps and ensure team accountability during time-critical events, reducing mean time to recovery (MTTR) by up to 50%. +- **Accelerate outage recovery** using [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) that automate response steps and ensure team accountability during time-critical events, reducing mean time to recovery (MTTR) by up to 50%. - **Demonstrate ROI through measurable outcomes** with built-in metrics tracking incident response times, team coordination efficiency, and compliance audit trails. ## Incident Response in Crisis Conditions @@ -41,7 +41,7 @@ Cyber breaches demand swift, coordinated action across affected teams. Every del - **Ensure secure response coordination** through [private 1:1 calling and screen sharing](/end-user-guide/collaborate/make-calls) for uninterrupted incident discussions within an isolated Mattermost environment. - **Integrate with your existing security stack** including ServiceNow, Grafana, Splunk, and other SOC tools via the [Mattermost integrations platform](/integrations-guide/integrations-guide-index). -- **Reduce mean time to resolution (MTTR)** by executing [structured incident playbooks](/end-user-guide/workflow-automation) that handle triage, task assignment, and escalation with full visibility and auditability. +- **Reduce mean time to resolution (MTTR)** by executing [structured incident playbooks](/end-user-guide/workflow-automation/workflow-automation-index) that handle triage, task assignment, and escalation with full visibility and auditability. - **Support compliance reporting** with automated documentation and audit trails helping organizations to meet NIS2, HIPAA, PCI DSS, GDPR, and government security requirements. ## Get Started diff --git a/docs/main/use-case-guide/purpose-built-collaboration.mdx b/docs/main/use-case-guide/purpose-built-collaboration.mdx index 54db19a828ed..0f64378a075e 100644 --- a/docs/main/use-case-guide/purpose-built-collaboration.mdx +++ b/docs/main/use-case-guide/purpose-built-collaboration.mdx @@ -15,7 +15,7 @@ Coordinating logistics across continents, agencies, and time zones requires a se **Benefits** -- **Enable real-time coordination** across supply chains, procurement, and field units with [channel-based messaging](/end-user-guide/messaging-collaboration) and [playbook-driven workflows](/end-user-guide/workflow-automation) that standardize communication and reduce friction. +- **Enable real-time coordination** across supply chains, procurement, and field units with [channel-based messaging](/end-user-guide/collaborate/collaborate-index) and [playbook-driven workflows](/end-user-guide/workflow-automation/workflow-automation-index) that standardize communication and reduce friction. - **Connect systems across logistics networks** by integrating ERP, fleet tracking, maintenance management, and transportation tools via [webhooks, APIs, and plugins](/integrations-guide/integrations-guide-index). - **Preserve operational continuity** during outages or disruptions using [self-hosted deployments](/deployment-guide/server/server-deployment-planning#deployment-options) and [high availability architecture](/deployment-guide/reference-architecture/scale/high-availability-cluster-based-deployment) that eliminate reliance on third-party cloud services. - **Support multilingual coordination** with [localized UI options](/end-user-guide/preferences/manage-your-display-options#language) in 20+ languages to ensure inclusive collaboration across global teams. @@ -40,10 +40,10 @@ Engineering, infrastructure, and security teams manage increasingly complex envi **Benefits** -- **Accelerate decision-making and incident response** using [Collaborative Playbooks](/end-user-guide/workflow-automation) to automate workflows for triage, patching, code releases, and security alerts. -- **Customize your collaboration environment** with [theming](/end-user-guide/preferences/customize-your-theme), [custom branding](/administration-guide/configure/custom-branding-tools), and [channel templates](/end-user-guide/messaging-collaboration) to mirror internal teams and operational domains. +- **Accelerate decision-making and incident response** using [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) to automate workflows for triage, patching, code releases, and security alerts. +- **Customize your collaboration environment** with [theming](/end-user-guide/preferences/customize-your-theme), [custom branding](/administration-guide/configure/custom-branding-tools), and [channel templates](/end-user-guide/collaborate/collaborate-index) to mirror internal teams and operational domains. - **Extend platform capabilities** with [slash commands, bots, and custom plugins](/integrations-guide/integrations-guide-index) that connect Mattermost to CI/CD systems, alerting frameworks, ticketing platforms, and internal tools. -- **Increase usability and team cohesion** with [custom emojis](/end-user-guide/collaborate/react-with-emojis-gifs#upload-custom-emojis), shared terminology, and [real-time messaging](/end-user-guide/messaging-collaboration) optimized for platform engineers, DevSecOps teams, and field service managers. +- **Increase usability and team cohesion** with [custom emojis](/end-user-guide/collaborate/react-with-emojis-gifs#upload-custom-emojis), shared terminology, and [real-time messaging](/end-user-guide/collaborate/collaborate-index) optimized for platform engineers, DevSecOps teams, and field service managers. ## Get Started diff --git a/docs/main/use-case-guide/secure-command-and-control.mdx b/docs/main/use-case-guide/secure-command-and-control.mdx index 44a58eb05ea4..2819fb70b96e 100644 --- a/docs/main/use-case-guide/secure-command-and-control.mdx +++ b/docs/main/use-case-guide/secure-command-and-control.mdx @@ -15,10 +15,10 @@ In high-stakes missions—including classified operations—real-time collaborat **Benefits** -- **Surface essential context faster for decisive action** using [threaded messaging](/end-user-guide/collaborate/organize-conversations), [file previews](/end-user-guide/collaborate/share-files-in-messages), and [channel-based discussion](/end-user-guide/messaging-collaboration) to consolidate signals and reduce noise. +- **Surface essential context faster for decisive action** using [threaded messaging](/end-user-guide/collaborate/organize-conversations), [file previews](/end-user-guide/collaborate/share-files-in-messages), and [channel-based discussion](/end-user-guide/collaborate/collaborate-index) to consolidate signals and reduce noise. - **Integrate mission tooling and automation** via the [Mattermost integrations platform](/integrations-guide/integrations-guide-index)—connecting alerting, workflow engines, and tactical systems directly into operational channels. - **Strengthen mobile communication channels** through [enterprise mobility security](/security-guide/mobile-security) that reduce reliance on personal messaging apps, control data exposure, and ensure secure, compliant access. -- **Coordinate operations with structured workflows** using [Collaborative Playbooks](/end-user-guide/workflow-automation) that standardize task execution, streamline decision-making, and maintain continuity across teams and mission roles. +- **Coordinate operations with structured workflows** using [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) that standardize task execution, streamline decision-making, and maintain continuity across teams and mission roles. - **Deploy sovereign AI for operational intelligence** using [air-gapped and private AI operations](/deployment-guide/reference-architecture/deployment-scenarios/air-gapped-deployment) to power decision support and automation in disconnected or classified settings. ## Disconnected, Intermittent, and Low-Bandwidth (DDIL) Collaboration @@ -33,7 +33,7 @@ Disconnected environments demand resilient tools that work without cloud access, - **Ensure secure mobile access on managed or BYOD devices** with [mobile security features](/deployment-guide/mobile/mobile-security-features), Zero Trust enforcement, and [ID-only push notifications](/administration-guide/configure/push-notification-server-configuration-settings#id-only-push-notifications) for sensitive alerts. - **Integrate with legacy and mission-specific systems** to maintain decision advantage in disconnected environments through [custom-built, self-hosted integrations](/integrations-guide/integrations-guide-index) tailored to your operational infrastructure. - **Maintain command resilience** using [high availability cluster-based deployment](/deployment-guide/reference-architecture/scale/scaling-for-enterprise) and [horizontal scalability](/deployment-guide/reference-architecture/scale/scaling-for-enterprise) to support operational continuity at scale. -- **Automate field workflows** with [Collaborative Playbooks](/end-user-guide/workflow-automation) that track tasks, manage field updates, and orchestrate responses under DDIL constraints. +- **Automate field workflows** with [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index) that track tasks, manage field updates, and orchestrate responses under DDIL constraints. - **Enable secure real-time collaboration with headquarters** using [Connected Workspaces](/administration-guide/onboard/connected-workspaces) to synchronize discussions, files, and reactions if connectivity is restored. ## Bring Your Own Device (BYOD) with Sensitive Information Protections diff --git a/docs/main/use-case-guide/self-sovereign-collaboration.mdx b/docs/main/use-case-guide/self-sovereign-collaboration.mdx index 07dd277aeb3d..5f512d19c636 100644 --- a/docs/main/use-case-guide/self-sovereign-collaboration.mdx +++ b/docs/main/use-case-guide/self-sovereign-collaboration.mdx @@ -39,7 +39,7 @@ Cross-agency, multinational, or coalition collaboration requires sovereignty wit - **Create secure shared workspaces** with [Connected Workspaces Channels](/administration-guide/onboard/connected-workspaces) that synchronize discussions, reactions, and file sharing across trusted organizations—without exposing internal systems. - **Control access across organizations** with [attribute-based permissions](/administration-guide/onboard/advanced-permissions) and scoped identity policies to ensure mission alignment and sensitive information segmentation. -- **Deploy sovereign AI and workflow automation** in isolated environments using [air-gapped AI operations](/end-user-guide/agents) and [Collaborative Playbooks](/end-user-guide/workflow-automation)—enabling intelligence and speed without compromising data control. +- **Deploy sovereign AI and workflow automation** in isolated environments using [air-gapped AI operations](/end-user-guide/agents) and [Collaborative Playbooks](/end-user-guide/workflow-automation/workflow-automation-index)—enabling intelligence and speed without compromising data control. ## Unified Collaboration for Secure Workflows @@ -49,10 +49,10 @@ Mattermost replaces legacy, on-premises communication systems with a modern, sov **Benefits** -- **Modernize secure messaging and team coordination** with [channel-based collaboration](/end-user-guide/messaging-collaboration), [threaded discussions](/end-user-guide/collaborate/organize-conversations), and [file sharing](/end-user-guide/collaborate/share-files-in-messages) that work across web, desktop, and mobile. +- **Modernize secure messaging and team coordination** with [channel-based collaboration](/end-user-guide/collaborate/collaborate-index), [threaded discussions](/end-user-guide/collaborate/organize-conversations), and [file sharing](/end-user-guide/collaborate/share-files-in-messages) that work across web, desktop, and mobile. - **Replace outdated platforms** like Skype for Business with a scalable, [self-hosted architecture](/deployment-guide/server/server-deployment-planning#deployment-options) that delivers enhanced user experience, compliance, and cross-organizational flexibility. - **Protect sensitive information on mobile** using [enterprise-grade mobile security](/security-guide/mobile-security) including [biometric access](/deployment-guide/mobile/mobile-security-features#biometric-authentication), [jailbreak detection](/deployment-guide/mobile/mobile-security-features#jailbreak-and-root-detection), [screenshot prevention](/deployment-guide/mobile/mobile-security-features#screenshot-and-screen-recording-prevention), and remote wipe—ensuring secure access from personal or field-issued devices. -- **Extend collaboration capabilities** using [integrated workflows and automations](/end-user-guide/workflow-automation) to replace manual coordination with policy-driven processes. +- **Extend collaboration capabilities** using [integrated workflows and automations](/end-user-guide/workflow-automation/workflow-automation-index) to replace manual coordination with policy-driven processes. - **Unify teams around a secure, customizable platform** that evolves with your mission and integrates with internal systems via [webhooks, plugins, and APIs](/integrations-guide/integrations-guide-index). ## Get Started diff --git a/docs/pdf/books/administration-guide.json b/docs/pdf/books/administration-guide.json index fca8369d5c53..a25a8a8018e6 100644 --- a/docs/pdf/books/administration-guide.json +++ b/docs/pdf/books/administration-guide.json @@ -5,9 +5,8 @@ "version": "unreleased", "spine": [ "/administration-guide/administration-guide-index", - "/administration-guide/cloud-workspace-management", - "/administration-guide/compliance-with-mattermost", - "/administration-guide/upgrade-mattermost", + "/administration-guide/comply/comply-index", + "/administration-guide/upgrade/upgrade-index", "/administration-guide/comply/compliance-export", "/administration-guide/comply/custom-terms-of-service", "/administration-guide/comply/data-retention-policy", @@ -31,6 +30,7 @@ "/administration-guide/manage/cloud-byok", "/administration-guide/manage/cloud-data-residency", "/administration-guide/manage/cloud-ip-filtering", + "/administration-guide/manage/cloud-workspace-management", "/administration-guide/manage/code-signing-custom-builds", "/administration-guide/manage/command-line-tools", "/administration-guide/manage/configure-health-check-probes", diff --git a/docs/pdf/books/end-user-guide.json b/docs/pdf/books/end-user-guide.json index 28f09921b2b4..d2442dae55a0 100644 --- a/docs/pdf/books/end-user-guide.json +++ b/docs/pdf/books/end-user-guide.json @@ -6,10 +6,10 @@ "spine": [ "/end-user-guide/agents", "/end-user-guide/end-user-guide-index", - "/end-user-guide/messaging-collaboration", - "/end-user-guide/preferences", - "/end-user-guide/project-task-management", - "/end-user-guide/workflow-automation", + "/end-user-guide/collaborate/collaborate-index", + "/end-user-guide/preferences/preferences-index", + "/end-user-guide/project-management/project-management-index", + "/end-user-guide/workflow-automation/workflow-automation-index", "/end-user-guide/access/access-your-workspace", "/end-user-guide/access/install-android-app", "/end-user-guide/access/install-desktop-app", diff --git a/docs/site/scripts/gen-documentation-sidebar.mjs b/docs/site/scripts/gen-documentation-sidebar.mjs index cbdbe8fc5a0c..967e8b295f5d 100644 --- a/docs/site/scripts/gen-documentation-sidebar.mjs +++ b/docs/site/scripts/gen-documentation-sidebar.mjs @@ -545,6 +545,7 @@ const ADMIN_MANAGE_GROUPS = { }, cloudWorkspace: { label: 'Cloud Workspace Management', + landing: 'cloud-workspace-management', items: [ 'cloud-data-export', 'cloud-data-residency', @@ -887,6 +888,10 @@ const ADMIN_SCALE_ORDER = [ // ADMIN_SCALE_ORDER) rather than nested inside a sub-group. const ADMIN_SCALE_HIDDEN = new Set([]); +const ADMIN_ROOT_ORDER = ['Configure', 'Comply', 'Onboard', 'Manage', 'Upgrade', 'Scale']; + +const ENDUSER_ROOT_ORDER = ['Access', 'Collaborate', 'Workflow Automation', 'Project Management', 'AI Agents', 'Preferences']; + // --------------------------------------------------------------------------- // Integrations Guide — manual grouping override. // --------------------------------------------------------------------------- @@ -1057,7 +1062,7 @@ function buildCategory(absDir, docsRelDir) { } const label = - (indexFile && readFm(join(absDir, indexFile), 'title')) || + (indexFile && (readFm(join(absDir, indexFile), 'sidebar_label') || readFm(join(absDir, indexFile), 'title'))) || humanize(basename(absDir)); if (!categoryLink && items.length === 0) return null; @@ -1065,6 +1070,46 @@ function buildCategory(absDir, docsRelDir) { return {type: 'category', label, collapsed: true, ...(categoryLink ? {link: categoryLink} : {}), items}; } +// The content sub-directory a category was built from (e.g. 'comply' for +// administration-guide/comply/), read off its landing page or first doc. +// Used by buildAdminGuideSidebar/buildEndUserGuideSidebar below to find the +// Configure/Manage/Onboard/Scale/Collaborate sub-category to regroup, +// independent of its (label-based) display text. +function categoryDirName(cat) { + if (cat.link && cat.link.id) return cat.link.id.split('/')[1]; + const items = cat.items || []; + const firstDoc = items.find((c) => c.type === 'doc' && c.id); + if (firstDoc) return firstDoc.id.split('/')[1]; + // Regrouped categories (e.g. Onboard) hold only sub-groups at the top level. + for (const it of items) { + if (it.type !== 'category') continue; + const nested = categoryDirName(it); + if (nested) return nested; + } + return null; +} + +// Reorder a guide's top-level categories by label. Anything not named in +// `order` keeps its relative position at the end. +function orderRootCategories(sectionCat, order, sectionLabel) { + const rank = new Map(order.map((label, i) => [label, i])); + const listed = []; + const rest = []; + for (const it of sectionCat.items) { + if (it.type === 'category' && rank.has(it.label)) listed.push(it); + else rest.push(it); + } + listed.sort((a, b) => rank.get(a.label) - rank.get(b.label)); + + const missing = order.filter((label) => !listed.some((it) => it.label === label)); + if (missing.length > 0) { + console.warn(`[sidebar] WARN: ${sectionLabel} root order names categor(y/ies) that don't exist: ${missing.join(', ')}`); + } + + sectionCat.items = [...listed, ...rest]; + return sectionCat; +} + // Pull every doc label from an auto-generated category so a manual ordering // preserves the frontmatter-derived titles. function collectLeafLabels(cat, acc = {}) { @@ -1474,14 +1519,7 @@ function buildAdminGuideSidebar(autoCat) { let foundScale = false; for (const it of autoCat.items) { if (it.type !== 'category') continue; - let dirName = null; - if (it.link && it.link.id) { - dirName = it.link.id.split('/')[1]; - } - if (!dirName && it.items) { - const firstDoc = it.items.find((c) => c.type === 'doc' && c.id); - if (firstDoc) dirName = firstDoc.id.split('/')[1]; - } + const dirName = categoryDirName(it); if (dirName === 'configure') { regroupAdminConfigure(it); foundConfigure = true; @@ -1508,6 +1546,8 @@ function buildAdminGuideSidebar(autoCat) { if (!foundScale) { console.warn('[sidebar] WARN: Administration Guide "Scale" sub-category not found — ADMIN_SCALE_GROUPS override was not applied.'); } + + orderRootCategories(autoCat, ADMIN_ROOT_ORDER, 'Administration Guide'); return autoCat; } @@ -1676,15 +1716,7 @@ function buildEndUserGuideSidebar(autoCat) { let foundCollaborate = false; for (const it of autoCat.items) { if (it.type !== 'category') continue; - let dirName = null; - if (it.link && it.link.id) { - dirName = it.link.id.split('/')[1]; - } - if (!dirName && it.items) { - const firstDoc = it.items.find((c) => c.type === 'doc' && c.id); - if (firstDoc) dirName = firstDoc.id.split('/')[1]; - } - if (dirName === 'collaborate') { + if (categoryDirName(it) === 'collaborate') { regroupCollaborate(it); foundCollaborate = true; } @@ -1693,6 +1725,7 @@ function buildEndUserGuideSidebar(autoCat) { console.warn('[sidebar] WARN: End User Guide "Collaborate" sub-category not found — COLLABORATE_GROUPS override was not applied.'); } + orderRootCategories(autoCat, ENDUSER_ROOT_ORDER, 'End User Guide'); return autoCat; } diff --git a/docs/site/sidebars/active-redirects.json b/docs/site/sidebars/active-redirects.json index 99d4c3dd727d..6b5060337873 100644 --- a/docs/site/sidebars/active-redirects.json +++ b/docs/site/sidebars/active-redirects.json @@ -1,7 +1,7 @@ { "_meta": { "source": "docs-site/scripts/migrate-main-docs/redirects.json", - "active": 691, + "active": 698, "missing_target": 217, "dropped_anchored_from": 1113, "total_internal": 2021 @@ -1301,7 +1301,7 @@ }, { "from": "/guides/playbooks", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/guides/welcome-to-mattermost", @@ -1409,7 +1409,7 @@ }, { "from": "/incident-collaboration/launching-playbooks", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/incident-collaboration/overview", @@ -1417,7 +1417,7 @@ }, { "from": "/incident-collaboration/playbook-planning", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/incident-collaboration/review-and-refine", @@ -1425,7 +1425,7 @@ }, { "from": "/incident-collaboration/running-playbooks", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/incident-collaboration/refining-and-improving", @@ -1433,7 +1433,7 @@ }, { "from": "/incident-collaboration/setting-up-playbooks", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/install/desktop-app-managed-resources", @@ -2253,11 +2253,11 @@ }, { "from": "/playbooks/setting-up-playbooks", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/playbooks/running-playbooks", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/playbooks/refining-and-improving", @@ -2265,7 +2265,7 @@ }, { "from": "/playbooks/customize-a-playbook", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/playbooks/customize-a-run", @@ -2313,7 +2313,7 @@ }, { "from": "/playbooks/get-started-with-playbooks", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/preferences/manage-your-calls-preferences", @@ -2557,11 +2557,11 @@ }, { "from": "/guides/cloud-workspace-management", - "to": "/administration-guide/cloud-workspace-management" + "to": "/administration-guide/manage/cloud-workspace-management" }, { "from": "/guides/compliance-with-mattermost", - "to": "/administration-guide/compliance-with-mattermost" + "to": "/administration-guide/comply/comply-index" }, { "from": "/guides/deployment-troubleshooting", @@ -2569,15 +2569,15 @@ }, { "from": "/guides/messaging-collaboration", - "to": "/end-user-guide/messaging-collaboration" + "to": "/end-user-guide/collaborate/collaborate-index" }, { "from": "/guides/preferences", - "to": "/end-user-guide/preferences" + "to": "/end-user-guide/preferences/preferences-index" }, { "from": "/guides/project-task-management", - "to": "/end-user-guide/project-task-management" + "to": "/end-user-guide/project-management/project-management-index" }, { "from": "/guides/secure-mattermost", @@ -2585,11 +2585,39 @@ }, { "from": "/guides/upgrade-mattermost", - "to": "/administration-guide/upgrade-mattermost" + "to": "/administration-guide/upgrade/upgrade-index" }, { "from": "/guides/workflow-automation", - "to": "/end-user-guide/workflow-automation" + "to": "/end-user-guide/workflow-automation/workflow-automation-index" + }, + { + "from": "/administration-guide/cloud-workspace-management", + "to": "/administration-guide/manage/cloud-workspace-management" + }, + { + "from": "/administration-guide/compliance-with-mattermost", + "to": "/administration-guide/comply/comply-index" + }, + { + "from": "/administration-guide/upgrade-mattermost", + "to": "/administration-guide/upgrade/upgrade-index" + }, + { + "from": "/end-user-guide/messaging-collaboration", + "to": "/end-user-guide/collaborate/collaborate-index" + }, + { + "from": "/end-user-guide/preferences", + "to": "/end-user-guide/preferences/preferences-index" + }, + { + "from": "/end-user-guide/project-task-management", + "to": "/end-user-guide/project-management/project-management-index" + }, + { + "from": "/end-user-guide/workflow-automation", + "to": "/end-user-guide/workflow-automation/workflow-automation-index" }, { "from": "/guides/about-mattermost", diff --git a/docs/site/src/components/Callout/index.tsx b/docs/site/src/components/Callout/index.tsx index 12e2f2b6797d..aedb6ea4d6fc 100644 --- a/docs/site/src/components/Callout/index.tsx +++ b/docs/site/src/components/Callout/index.tsx @@ -1,12 +1,13 @@ import React from 'react'; import styles from './styles.module.css'; -type Kind = 'note' | 'tip' | 'important' | 'warning' | 'security'; +type Kind = 'note' | 'tip' | 'important' | 'attention' | 'warning' | 'security'; const COPY: Record = { note: {label: 'Note', icon: 'i'}, tip: {label: 'Tip', icon: '+'}, important: {label: 'Important', icon: '!'}, + attention: {label: 'Attention', icon: '!'}, warning: {label: 'Warning', icon: '!'}, security: {label: 'Security', icon: 'S'}, }; @@ -23,13 +24,11 @@ export default function Callout({ const meta = COPY[kind]; return ( ); } @@ -38,5 +37,6 @@ export default function Callout({ export const Note = (p: {title?: string; children: React.ReactNode}) => ; export const Tip = (p: {title?: string; children: React.ReactNode}) => ; export const Important = (p: {title?: string; children: React.ReactNode}) => ; +export const Attention = (p: {title?: string; children: React.ReactNode}) => ; export const Warning = (p: {title?: string; children: React.ReactNode}) => ; export const Security = (p: {title?: string; children: React.ReactNode}) => ; diff --git a/docs/site/src/components/Callout/styles.module.css b/docs/site/src/components/Callout/styles.module.css index afaa48c883aa..fcd9bbf5c83b 100644 --- a/docs/site/src/components/Callout/styles.module.css +++ b/docs/site/src/components/Callout/styles.module.css @@ -1,92 +1,121 @@ -/* Branded callouts. Denim-tinted surface with a colored left bar - * that signals the kind. Title in Trade Gothic Heavy uppercase. */ +/* Branded callouts, styled to match the compact Docusaurus admonitions used + * elsewhere in the docs: a tinted panel with a left accent bar and a small + * icon + label header. */ .callout { - display: grid; - grid-template-columns: 3.25rem 1fr; - gap: 0; margin: 1.5rem 0; - border: 1px solid var(--mm-border-subtle); + padding: 0.9rem 1.1rem 1rem; + border: 1px solid var(--callout-border); + border-left: 4px solid var(--callout-accent); border-radius: 4px; - background: var(--mm-bg-surface); - overflow: hidden; - box-shadow: 0 1px 2px rgba(30, 50, 92, 0.04); + background: var(--callout-bg); + color: var(--mm-text-primary); } -/* The base rule's --mm-border-subtle resolves to denim-700 in dark mode — - * ~1:1 contrast from the page background (#0E1525), so the box had no - * visible edge. --mm-border-strong (denim-500) is a real step up. Fill - * needs no override: --mm-bg-surface above already resolves to a - * theme-appropriate value per mode. */ -[data-theme='dark'] .callout { - border-color: var(--mm-border-strong); -} - -.bar { +.header { display: flex; - align-items: flex-start; - justify-content: center; - padding-top: 0.95rem; - background: var(--mm-color-denim); - color: var(--mm-color-white); + align-items: center; + gap: 0.45rem; + margin-bottom: 0.5rem; } .icon { display: inline-flex; align-items: center; justify-content: center; - width: 1.6rem; - height: 1.6rem; - font-family: var(--mm-font-heading); - font-weight: 900; - font-size: 1rem; - border: 2px solid currentColor; - border-radius: 50%; + flex: 0 0 auto; + width: 1.05rem; + height: 1.05rem; + font-family: var(--mm-font-sans); + font-weight: 700; + font-size: 0.7rem; line-height: 1; -} - -.body { - padding: 0.85rem 1.1rem 0.95rem; + color: var(--callout-accent); + border: 1.5px solid currentColor; + border-radius: 50%; } .label { - font-family: var(--mm-font-heading); - font-size: 0.75rem; - font-weight: 900; - letter-spacing: 0.14em; + font-family: var(--mm-font-sans); + font-weight: 700; + font-size: 0.8125rem; + letter-spacing: 0.05em; text-transform: uppercase; - color: var(--mm-text-secondary); - margin-bottom: 0.35rem; + line-height: 1.2; + color: var(--callout-accent); } .content > :first-child { margin-top: 0; } .content > :last-child { margin-bottom: 0; } -.content p { margin: 0.4rem 0; line-height: 1.6; } +.content p { margin: 0.5rem 0; line-height: 1.6; } + +/* Kind variants: each sets an accent color plus a tint of the same hue for + * the panel background. */ + +.note { + --callout-accent: #2E4B87; + --callout-bg: var(--mm-denim-50); + --callout-border: var(--mm-denim-100); +} +[data-theme='dark'] .note { + --callout-accent: var(--mm-denim-200); + --callout-bg: var(--mm-denim-700); + --callout-border: var(--mm-denim-400); +} -/* Kind variants */ -.note .bar { background: var(--mm-color-denim); } -.note .label { color: var(--mm-denim-600); } -/* Every other kind below pairs its light-mode label color with a - * dark-mode override — "note" was missing one, so it fell back to this - * light-mode-only denim-600 in dark mode too: dark navy text on a dark - * navy background. */ -[data-theme='dark'] .note .label { color: var(--mm-denim-200); } +.tip { + --callout-accent: #1F5A39; + --callout-bg: #EAF5EE; + --callout-border: #C3E0CE; +} +[data-theme='dark'] .tip { + --callout-accent: #7FCC9E; + --callout-bg: #14442C; + --callout-border: #2C7A4F; +} -.tip .bar { background: #2C7A4F; } /* operational green */ -.tip .label { color: #1F5A39; } -[data-theme='dark'] .tip .label { color: #6FBF8E; } +.important { + --callout-accent: #8A6200; + --callout-bg: var(--mm-marigold-100); + --callout-border: var(--mm-marigold-300); +} +[data-theme='dark'] .important { + --callout-accent: var(--mm-marigold-300); + --callout-bg: #4A3A10; + --callout-border: #7A6220; +} -/* Important = stronger emphasis than Note, softer than Warning. Marigold accent. */ -.important .bar { background: var(--mm-color-marigold); color: var(--mm-color-black); } -.important .icon { border-color: var(--mm-color-black); } -.important .label { color: var(--mm-marigold-700); } -[data-theme='dark'] .important .label { color: var(--mm-marigold-300); } +/* Attention outranks Important: the reader loses something by skipping it. + * Brighter than Security's maroon so the two stay tellable apart. */ +.attention { + --callout-accent: #C8102E; + --callout-bg: #FDECEF; + --callout-border: #F5C6CE; +} +[data-theme='dark'] .attention { + --callout-accent: #F4A0AB; + --callout-bg: #4E0F1B; + --callout-border: #85202F; +} -/* Warning = stop sign. Bark/red treatment via custom token. */ -.warning .bar { background: var(--mm-color-bark); color: var(--mm-color-white); } -.warning .label { color: var(--mm-color-bark); } -[data-theme='dark'] .warning .label { color: var(--mm-color-sand); } +.warning { + --callout-accent: #6B4F2E; + --callout-bg: #F6F1E9; + --callout-border: #E0D5C1; +} +[data-theme='dark'] .warning { + --callout-accent: var(--mm-color-sand); + --callout-bg: #3A2F22; + --callout-border: var(--mm-color-bark); +} -.security .bar { background: #8B1F1F; } /* sec red */ -.security .label { color: #8B1F1F; } -[data-theme='dark'] .security .label { color: #E9A0A0; } +.security { + --callout-accent: #8B1F1F; + --callout-bg: #FBEDED; + --callout-border: #EFCFCF; +} +[data-theme='dark'] .security { + --callout-accent: #E9A0A0; + --callout-bg: #4A1F1F; + --callout-border: #7A3535; +} diff --git a/docs/site/src/components/CardGrid/index.tsx b/docs/site/src/components/CardGrid/index.tsx index e5ed3bfc5261..4b15ae96bd27 100644 --- a/docs/site/src/components/CardGrid/index.tsx +++ b/docs/site/src/components/CardGrid/index.tsx @@ -58,18 +58,22 @@ export default function CardGrid({ } return ( -
    - {cards.map((c, i) => ( - - {c.icon && } -
    -
    {c.title}
    -

    {c.description}

    - {c.meta && {c.meta}} -
    - - - ))} + // Wrapper establishes a size query container for the column-count + // breakpoints in styles.module.css. +
    +
    + {cards.map((c, i) => ( + + {c.icon && } +
    +
    {c.title}
    +

    {c.description}

    + {c.meta && {c.meta}} +
    + + + ))} +
    ); } diff --git a/docs/site/src/components/CardGrid/styles.module.css b/docs/site/src/components/CardGrid/styles.module.css index cadaa85ddd1d..ff5160b40109 100644 --- a/docs/site/src/components/CardGrid/styles.module.css +++ b/docs/site/src/components/CardGrid/styles.module.css @@ -1,12 +1,45 @@ +.gridContainer { + container-type: inline-size; +} + .grid { display: grid; gap: 1rem; margin: 1.5rem 0 2.5rem; } -.cols2 { grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); } -.cols3 { grid-template-columns: repeat(auto-fit, minmax(240px, 1fr)); } -.cols4 { grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); } +/* `repeat(auto-fit, minmax(200px, 1fr))` packed 4 cards into one tall, narrow + * row at typical article widths. Step the column count down explicitly + * instead, halving 4 to 2x2 and 3 to 2 before dropping to a single column. */ +.cols2 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +.cols3 { grid-template-columns: repeat(3, minmax(0, 1fr)); } +.cols4 { grid-template-columns: repeat(4, minmax(0, 1fr)); } + +@container (max-width: 1080px) { + .cols4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +@container (max-width: 820px) { + .cols3 { grid-template-columns: repeat(2, minmax(0, 1fr)); } +} +@container (max-width: 540px) { + .cols2, + .cols3, + .cols4 { grid-template-columns: minmax(0, 1fr); } +} + +/* Fallback for browsers without container query support — viewport width is + * a reasonable stand-in since the doc sidebar collapses below 997px. */ +@supports not (container-type: inline-size) { + @media (max-width: 996px) { + .cols3, + .cols4 { grid-template-columns: repeat(2, minmax(0, 1fr)); } + } + @media (max-width: 600px) { + .cols2, + .cols3, + .cols4 { grid-template-columns: minmax(0, 1fr); } + } +} .card { position: relative; diff --git a/docs/site/src/components/IMEDiagram/index.tsx b/docs/site/src/components/IMEDiagram/index.tsx index 882d986ecfe0..ce4b25feeb2d 100644 --- a/docs/site/src/components/IMEDiagram/index.tsx +++ b/docs/site/src/components/IMEDiagram/index.tsx @@ -152,16 +152,16 @@ export default function IMEDiagram() {

    - }> + }> Mattermost Channels for ChatOps and automation - }> + }> Mattermost Playbooks for automating SOPs }> Mattermost Calls for real‑time calling and screenshare - }> + }> Mattermost Boards for Kanban and work management }> diff --git a/docs/site/src/css/custom.css b/docs/site/src/css/custom.css index eacfa6d94a51..92fb7d04d41d 100644 --- a/docs/site/src/css/custom.css +++ b/docs/site/src/css/custom.css @@ -562,7 +562,48 @@ blockquote { background: var(--mm-bg-surface); } -/* === Footer === */ +/* === Content images === + * Screenshots/diagrams are committed at capture resolution (up to 1024px, + * some portrait) with no cap beyond Infima's 100%-of-column default, so + * they rendered at full source size. Cap both dimensions and let the + * browser scale proportionally against whichever binds first. + * + * `:not([class])` scopes this to authored MDX images, leaving + * component-rendered images (always CSS-module-classed) alone. An author + * can opt out with an explicit `width` or `height` attribute. */ +.markdown img:not([class]):not([width]):not([height]) { + display: block; + width: auto; + height: auto; + max-width: min(100%, 720px); + max-height: 640px; + margin-inline: auto; +} + +/* === Inline UI icons (product menu, checkmarks, keyboard glyphs, etc.) === + * `` is used throughout the docs to inline small + * UI icons (e.g. the product-switcher waffle icon) next to text. Nothing + * previously styled this class, so icons rendered at their raw SVG size + * (baseline-aligned, often 24px) and — since they're flat single-color + * PNGs/SVGs baked black with no `fill` attribute an could recolor — + * were nearly invisible against a dark background in dark mode. */ +.theme-icon { + width: 1.1em; + height: 1.1em; + vertical-align: text-bottom; + margin: 0 0.05em; +} +[data-theme='dark'] .theme-icon { + filter: invert(1); +} +/* A few icons carry their own brand color (green checkmark, colored + * presence dots/badges) rather than being flat black glyphs — inverting + * those would shift their hue instead of fixing contrast, so exempt them. */ +[data-theme='dark'] .theme-icon[src*="checkmark.svg"], +[data-theme='dark'] .theme-icon[src*="Ack-Button"], +[data-theme='dark'] .theme-icon[src$=".png"] { + filter: none; +} .footer { background-color: var(--ifm-footer-background-color); diff --git a/docs/site/src/theme/MDXComponents.tsx b/docs/site/src/theme/MDXComponents.tsx index 2c224eb5440e..42d2e5221f9d 100644 --- a/docs/site/src/theme/MDXComponents.tsx +++ b/docs/site/src/theme/MDXComponents.tsx @@ -2,7 +2,7 @@ // in any .mdx file without an import. import MDXComponents from '@theme-original/MDXComponents'; -import {Note, Tip, Important, Warning, Security} from '@site/src/components/Callout'; +import {Note, Tip, Important, Attention, Warning, Security} from '@site/src/components/Callout'; import PlanBadge from '@site/src/components/PlanBadge'; import PlanAvailability from '@site/src/components/PlanAvailability'; import EditionAvailability from '@site/src/components/EditionAvailability'; @@ -32,6 +32,7 @@ export default { Note, Tip, Important, + Attention, Warning, Security, PlanBadge, diff --git a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js index ede6b04e6497..2d1f70d9e48a 100644 --- a/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/message_forwarding/forward_message_from_public_channel_spec.js @@ -11,9 +11,11 @@ import * as TIMEOUTS from '@/fixtures/timeouts'; -const DEFAULT_CHARACTER_LIMIT = 16383; - describe('Forward Message', () => { + // The maximum message length is decided by the server (config.MaxPostSize, + // derived from PostMessageMaxBytesV2 / 4), so read it rather than hardcoding. + let maxMessageLength; + let user1; let user2; let user3; @@ -30,6 +32,12 @@ describe('Forward Message', () => { const replyMessage = 'Forward this reply'; before(() => { + // MaxPostSize is a computed value only present in the client config, + // so request that (old format) rather than the admin config. + cy.apiGetConfig(true).then(({config}) => { + maxMessageLength = parseInt(config.MaxPostSize, 10); + }); + cy.apiUpdateConfig({ ServiceSettings: { ThreadAutoFollow: true, @@ -305,7 +313,7 @@ describe('Forward Message', () => { */ const forwardPost = ({channelId, comment = '', testLongComment = false}) => { const permalink = `${Cypress.config('baseUrl')}/${testTeam.name}/pl/${testPost.id}`; - const maxPostSize = DEFAULT_CHARACTER_LIMIT - permalink.length - 1; + const maxPostSize = maxMessageLength - permalink.length - 1; const longMessage = 'M'.repeat(maxPostSize); const extraChars = 'X'; diff --git a/e2e-tests/cypress/tests/integration/channels/messaging/message_reply_too_long_spec.js b/e2e-tests/cypress/tests/integration/channels/messaging/message_reply_too_long_spec.js index 1c3c41d21c8d..f80548f23cfb 100644 --- a/e2e-tests/cypress/tests/integration/channels/messaging/message_reply_too_long_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/messaging/message_reply_too_long_spec.js @@ -11,7 +11,17 @@ // Group: @channels @messaging describe('Message Reply too long', () => { + // The maximum message length is decided by the server (config.MaxPostSize, + // derived from PostMessageMaxBytesV2 / 4), so read it rather than hardcoding. + let maxReplyLength; + before(() => { + // MaxPostSize is a computed value only present in the client config, + // so request that (old format) rather than the admin config. + cy.apiGetConfig(true).then(({config}) => { + maxReplyLength = parseInt(config.MaxPostSize, 10); + }); + // # Login as test user and visit off-topic channel cy.apiInitSetup({loginAfter: true}).then(({team}) => { cy.visit(`/${team.name}/channels/off-topic`); @@ -35,7 +45,6 @@ describe('Message Reply too long', () => { cy.get('.post-error').should('not.exist'); // # Enter too long text into RHS - const maxReplyLength = 16383; const replyTooLong = replyValid.repeat((maxReplyLength / replyValid.length) + 1); cy.uiGetReplyTextBox().invoke('val', replyTooLong).trigger('input'); diff --git a/e2e-tests/cypress/tests/integration/channels/plugins/upgrade_spec.js b/e2e-tests/cypress/tests/integration/channels/plugins/upgrade_spec.js index f7f83449cb0f..854469f3071d 100644 --- a/e2e-tests/cypress/tests/integration/channels/plugins/upgrade_spec.js +++ b/e2e-tests/cypress/tests/integration/channels/plugins/upgrade_spec.js @@ -82,7 +82,11 @@ describe('Plugin remains enabled when upgraded', () => { cy.get('input[type=file]').attachFile({fileContent, fileName: demoPlugin.filename, mimeType}); }); - // # Confirm overwrite of plugin with same name + // # Review and confirm overwrite of plugin with same name + cy.findByTestId('plugin-upload-overwrite-review').should('be.visible').within(() => { + cy.findByText('This upload upgrades the existing plugin.').should('be.visible'); + cy.findByText(`v${demoPluginOld.version} \u2192 v${demoPlugin.version}`).should('be.visible'); + }); cy.get('#confirmModalButton').should('be.visible').click(); // * Verify that the latest demo plugin is uploaded and remains active after overwrite diff --git a/server/channels/api4/plugin_test.go b/server/channels/api4/plugin_test.go index 0883f5c2b3f0..4f7a1a6f3e0b 100644 --- a/server/channels/api4/plugin_test.go +++ b/server/channels/api4/plugin_test.go @@ -8,6 +8,7 @@ import ( "context" "encoding/base64" "encoding/json" + "errors" "fmt" "io" "net/http" @@ -286,6 +287,41 @@ func TestPlugin(t *testing.T) { }) } +func TestUploadPluginConflictDetails(t *testing.T) { + mainHelper.Parallel(t) + th := Setup(t) + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.PluginSettings.Enable = true + *cfg.PluginSettings.EnableUploads = true + *cfg.ServiceSettings.EnableDeveloper = false + }) + + path, _ := fileutils.FindDir("tests") + tarData, err := os.ReadFile(filepath.Join(path, "testplugin.tar.gz")) + require.NoError(t, err) + + manifest, _, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) + require.NoError(t, err) + defer os.RemoveAll("plugins/testplugin") + require.Equal(t, "testplugin", manifest.Id) + + _, resp, err := th.SystemAdminClient.UploadPlugin(context.Background(), bytes.NewReader(tarData)) + require.Error(t, err) + CheckBadRequestStatus(t, resp) + CheckErrorID(t, err, "app.plugin.install_id.app_error") + + var appErr *model.AppError + require.True(t, errors.As(err, &appErr)) + require.Empty(t, appErr.DetailedError, "detailed error must still be wiped with EnableDeveloper disabled") + require.NotEmpty(t, appErr.Props, "conflict props must survive with EnableDeveloper disabled") + + assert.Equal(t, "testplugin", appErr.Props[model.PluginInstallConflictPropPluginID]) + assert.Equal(t, manifest.Version, appErr.Props[model.PluginInstallConflictPropExistingVersion]) + assert.Equal(t, manifest.Version, appErr.Props[model.PluginInstallConflictPropUploadedVersion]) + assert.Equal(t, model.PluginInstallConflictVersionDirectionSame, appErr.Props[model.PluginInstallConflictPropVersionDirection]) +} + func TestPluginInstallDirectoryConflict(t *testing.T) { mainHelper.Parallel(t) th := Setup(t) diff --git a/server/channels/app/app_test.go b/server/channels/app/app_test.go index a504abdbcb43..fff3c6e0fbc8 100644 --- a/server/channels/app/app_test.go +++ b/server/channels/app/app_test.go @@ -45,7 +45,7 @@ func TestUnitUpdateConfig(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/channels/app/config_test.go b/server/channels/app/config_test.go index 47eaf8bd5424..6d7d7f13de31 100644 --- a/server/channels/app/config_test.go +++ b/server/channels/app/config_test.go @@ -40,7 +40,7 @@ func TestClientConfigWithComputed(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/channels/app/enterprise_test.go b/server/channels/app/enterprise_test.go index 60e8c4a7b56b..93a1e8238c00 100644 --- a/server/channels/app/enterprise_test.go +++ b/server/channels/app/enterprise_test.go @@ -67,7 +67,7 @@ func TestSAMLSettings(t *testing.T) { mockUserStore := storemocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := storemocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := storemocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/channels/app/export_test.go b/server/channels/app/export_test.go index 22b9aa2ec73e..c48461884c4f 100644 --- a/server/channels/app/export_test.go +++ b/server/channels/app/export_test.go @@ -147,31 +147,22 @@ func TestCopyEmojiImages(t *testing.T) { Id: model.NewId(), } - // Creating a dir named `exported_emoji_test` in the root of the repo - pathToDir := "../exported_emoji_test" + tmpDir := t.TempDir() + pathToDir := tmpDir - err := os.Mkdir(pathToDir, 0777) - require.NoError(t, err) - defer os.RemoveAll(pathToDir) - - filePath := "../data/emoji/" + emoji.Id - emojiImagePath := filePath + "/image" + filePath := filepath.Join(tmpDir, "data", "emoji", emoji.Id) + emojiImagePath := filepath.Join(filePath, "image") - _, err = os.Stat(filePath) - if os.IsNotExist(err) { - err = os.MkdirAll(filePath, 0777) - require.NoError(t, err) - } + err := os.MkdirAll(filePath, 0777) + require.NoError(t, err) - // Creating a file with the name `image` to copy it to `exported_emoji_test` - _, err = os.OpenFile(filePath+"/image", os.O_RDONLY|os.O_CREATE, 0777) + _, err = os.OpenFile(emojiImagePath, os.O_RDONLY|os.O_CREATE, 0777) require.NoError(t, err) - defer os.RemoveAll(filePath) copyError := th.App.copyEmojiImages(th.Context, emoji.Id, emojiImagePath, pathToDir) require.NoError(t, copyError) - _, err = os.Stat(pathToDir + "/" + emoji.Id + "/image") + _, err = os.Stat(filepath.Join(pathToDir, emoji.Id, "image")) require.False(t, os.IsNotExist(err), "File should exist ") } @@ -186,7 +177,6 @@ func TestExportCustomEmoji(t *testing.T) { defer os.Remove(filePath) dirNameToExportEmoji := "exported_emoji_test" - defer os.RemoveAll("../" + dirNameToExportEmoji) outPath, err := filepath.Abs(filePath) require.NoError(t, err) diff --git a/server/channels/app/notification_push_test.go b/server/channels/app/notification_push_test.go index 5e0f537bd93b..c01583309461 100644 --- a/server/channels/app/notification_push_test.go +++ b/server/channels/app/notification_push_test.go @@ -668,7 +668,7 @@ func TestGetPushNotificationMessage(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1553,7 +1553,7 @@ func TestClearPushNotificationSync(t *testing.T) { mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1633,7 +1633,7 @@ func TestUpdateMobileAppBadgeSync(t *testing.T) { mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1709,7 +1709,7 @@ func TestSendAckToPushProxy(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1957,7 +1957,7 @@ func BenchmarkPushNotificationThroughput(b *testing.B) { mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockUserStore.On("GetUnreadCount", mock.AnythingOfType("string"), mock.AnythingOfType("bool")).Return(int64(1), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/channels/app/oauth.go b/server/channels/app/oauth.go index b0573e297abe..ef85bb44ce6d 100644 --- a/server/channels/app/oauth.go +++ b/server/channels/app/oauth.go @@ -1114,7 +1114,7 @@ func (a *App) AuthorizeOAuthUser(rctx request.CTX, w http.ResponseWriter, r *htt req.Header.Set("Content-Type", "application/x-www-form-urlencoded") req.Header.Set("Accept", "application/json") - resp, err := a.HTTPService().MakeClient(true).Do(req) + resp, err := a.HTTPService().MakeClient(false).Do(req) if err != nil { return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.token_failed.app_error", nil, "", http.StatusInternalServerError).Wrap(err) } @@ -1156,7 +1156,7 @@ func (a *App) AuthorizeOAuthUser(rctx request.CTX, w http.ResponseWriter, r *htt req.Header.Set("Accept", "application/json") req.Header.Set("Authorization", "Bearer "+ar.AccessToken) - resp, err = a.HTTPService().MakeClient(true).Do(req) + resp, err = a.HTTPService().MakeClient(false).Do(req) if err != nil { return nil, stateProps, nil, model.NewAppError("AuthorizeOAuthUser", "api.user.authorize_oauth_user.service.app_error", map[string]any{"Service": service}, "", http.StatusInternalServerError).Wrap(err) } else if resp.StatusCode != http.StatusOK { diff --git a/server/channels/app/oauth_test.go b/server/channels/app/oauth_test.go index 6903c21f6616..2ed0fc3f02cc 100644 --- a/server/channels/app/oauth_test.go +++ b/server/channels/app/oauth_test.go @@ -318,6 +318,14 @@ func TestAuthorizeOAuthUser(t *testing.T) { } else { *cfg.GitLabSettings.UserAPIEndpoint = "" } + + // The test server binds to a loopback (reserved) address, which the + // filtered OAuth client refuses unless it is explicitly allowlisted. + if serverURL != "" { + if u, parseErr := url.Parse(serverURL); parseErr == nil { + *cfg.ServiceSettings.AllowedUntrustedInternalConnections = u.Hostname() + } + } }) return th @@ -455,6 +463,32 @@ func TestAuthorizeOAuthUser(t *testing.T) { assert.Equal(t, "api.user.authorize_oauth_user.token_failed.app_error", err.Id) }) + t.Run("refuses a reserved-IP token endpoint that is not allowlisted", func(t *testing.T) { + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + err := json.NewEncoder(w).Encode(&model.AccessResponse{ + AccessToken: model.NewId(), + TokenType: model.AccessTokenType, + }) + require.NoError(t, err) + })) + defer server.Close() + + th := setup(t, true, true, true, server.URL) + + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.AllowedUntrustedInternalConnections = "" + }) + + cookie := model.NewId() + request := makeRequest(cookie) + state := makeState(makeToken(th, cookie)) + + _, _, _, err := th.App.AuthorizeOAuthUser(th.Context, &httptest.ResponseRecorder{}, request, model.ServiceGitlab, "", state, "") + require.NotNil(t, err) + assert.Equal(t, "api.user.authorize_oauth_user.token_failed.app_error", err.Id) + assert.ErrorContains(t, err.Unwrap(), "reserved range") + }) + t.Run("with an error token response", func(t *testing.T) { server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusTeapot) @@ -969,6 +1003,21 @@ func TestRegisterOAuthClient(t *testing.T) { require.Equal(t, model.ClientAuthMethodNone, registeredApp.GetTokenEndpointAuthMethod()) require.True(t, registeredApp.IsDynamicallyRegistered) }) + + t.Run("Custom scheme redirect URI persists end-to-end", func(t *testing.T) { + request := &model.ClientRegistrationRequest{ + RedirectURIs: []string{"cursor://anysphere.cursor-mcp/oauth/callback"}, + ClientName: new("Desktop Client"), + } + + app, appErr := th.App.RegisterOAuthClient(th.Context, request, th.BasicUser.Id) + + require.Nil(t, appErr) + require.NotNil(t, app) + assert.Equal(t, request.RedirectURIs, []string(app.CallbackUrls)) + assert.True(t, app.IsDynamicallyRegistered) + assert.NotEmpty(t, app.Id) + }) } func TestGetAuthorizationServerMetadata_DCRConfig(t *testing.T) { diff --git a/server/channels/app/plugin_install.go b/server/channels/app/plugin_install.go index 32fc606589b0..98b5d6a0df7e 100644 --- a/server/channels/app/plugin_install.go +++ b/server/channels/app/plugin_install.go @@ -252,6 +252,40 @@ func (ch *Channels) installPluginToFilestore(manifest *model.Manifest, bundle, s return nil } +func newPluginInstallConflictAppError(existingManifest, uploadedManifest *model.Manifest) *model.AppError { + // Installing only validates the plugin id, so the uploaded name and homepage may be missing. + // Fall back to the installed plugin's metadata to keep the plugin identifiable. + name := uploadedManifest.Name + if name == "" { + name = existingManifest.Name + } + homepageURL := uploadedManifest.HomepageURL + if homepageURL == "" { + homepageURL = existingManifest.HomepageURL + } + + direction := model.PluginInstallConflictVersionDirection(existingManifest, uploadedManifest) + + // The confirmation dialog reads this from AppError.Props, which is returned to clients even when + // developer mode is off. Keep it to plugin metadata that is safe for any admin client to see. + props := model.StringMap{ + model.PluginInstallConflictPropPluginID: uploadedManifest.Id, + model.PluginInstallConflictPropPluginName: name, + model.PluginInstallConflictPropHomepageURL: homepageURL, + model.PluginInstallConflictPropExistingVersion: existingManifest.Version, + model.PluginInstallConflictPropUploadedVersion: uploadedManifest.Version, + model.PluginInstallConflictPropVersionDirection: direction, + } + + // DetailedError is only for the server log; the client reads Props instead. + details := fmt.Sprintf("plugin %q conflict: existing version %q, uploaded version %q (%s)", uploadedManifest.Id, existingManifest.Version, uploadedManifest.Version, direction) + + appErr := model.NewAppError("installExtractedPlugin", "app.plugin.install_id.app_error", nil, details, http.StatusBadRequest) + appErr.Props = props + + return appErr +} + // InstallMarketplacePlugin installs a plugin listed in the marketplace server. It will get the // plugin bundle from the prepackaged folder, if available, or remotely if EnableRemoteMarketplace // is true. @@ -451,7 +485,7 @@ func (ch *Channels) installExtractedPlugin(manifest *model.Manifest, fromPluginD if existingManifest != nil { // Return an error if already installed and strategy disallows installation. if installationStrategy == installPluginLocallyOnlyIfNew { - return nil, model.NewAppError("installExtractedPlugin", "app.plugin.install_id.app_error", nil, "", http.StatusBadRequest) + return nil, newPluginInstallConflictAppError(existingManifest, manifest) } // Skip installation if already installed and newer. diff --git a/server/channels/app/plugin_install_test.go b/server/channels/app/plugin_install_test.go index 0bf1f5a64cfc..0a8ebde5495b 100644 --- a/server/channels/app/plugin_install_test.go +++ b/server/channels/app/plugin_install_test.go @@ -186,9 +186,65 @@ func TestInstallPluginLocally(t *testing.T) { require.Equal(t, "app.plugin.install_id.app_error", appErr.Id, appErr.Error()) require.Nil(t, manifest) + require.Equal(t, existingManifest.Id, appErr.Props[model.PluginInstallConflictPropPluginID]) + require.Equal(t, existingManifest.Version, appErr.Props[model.PluginInstallConflictPropExistingVersion]) + require.Equal(t, existingManifest.Version, appErr.Props[model.PluginInstallConflictPropUploadedVersion]) + require.Equal(t, model.PluginInstallConflictVersionDirectionSame, appErr.Props[model.PluginInstallConflictPropVersionDirection]) + assertBundleInfoManifests(t, th, []*model.Manifest{existingManifest}) }) + t.Run("install only if new returns conflict version direction", func(t *testing.T) { + for name, tc := range map[string]struct { + existingVersion string + uploadedVersion string + versionDirection string + }{ + "upgrade": { + existingVersion: "0.0.1", + uploadedVersion: "0.0.2", + versionDirection: model.PluginInstallConflictVersionDirectionUpgrade, + }, + "downgrade": { + existingVersion: "0.0.2", + uploadedVersion: "0.0.1", + versionDirection: model.PluginInstallConflictVersionDirectionDowngrade, + }, + "same": { + existingVersion: "0.0.1", + uploadedVersion: "0.0.1", + versionDirection: model.PluginInstallConflictVersionDirectionSame, + }, + "unknown": { + existingVersion: "0.0.1", + uploadedVersion: "not-semver", + versionDirection: model.PluginInstallConflictVersionDirectionUnknown, + }, + } { + t.Run(name, func(t *testing.T) { + th := Setup(t) + + cleanExistingBundles(t, th) + + existingManifest, appErr := installPlugin(t, th, "valid", tc.existingVersion, installPluginLocallyOnlyIfNew) + require.Nil(t, appErr) + require.NotNil(t, existingManifest) + + manifest, appErr := installPlugin(t, th, "valid", tc.uploadedVersion, installPluginLocallyOnlyIfNew) + require.NotNil(t, appErr) + require.Equal(t, "app.plugin.install_id.app_error", appErr.Id, appErr.Error()) + require.Nil(t, manifest) + + require.Equal(t, existingManifest.Id, appErr.Props[model.PluginInstallConflictPropPluginID]) + require.Equal(t, tc.existingVersion, appErr.Props[model.PluginInstallConflictPropExistingVersion]) + require.Equal(t, tc.uploadedVersion, appErr.Props[model.PluginInstallConflictPropUploadedVersion]) + require.Equal(t, tc.versionDirection, appErr.Props[model.PluginInstallConflictPropVersionDirection]) + + assertBundleInfoManifests(t, th, []*model.Manifest{existingManifest}) + }) + } + }) + t.Run("install if upgrade, but older", func(t *testing.T) { th := Setup(t) diff --git a/server/channels/app/plugin_signature_test.go b/server/channels/app/plugin_signature_test.go index 840feeafb0c1..b0ed93affdb9 100644 --- a/server/channels/app/plugin_signature_test.go +++ b/server/channels/app/plugin_signature_test.go @@ -26,7 +26,7 @@ func TestPluginPublicKeys(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/channels/app/post_test.go b/server/channels/app/post_test.go index 41e34d099ceb..fe9b8e29f7a0 100644 --- a/server/channels/app/post_test.go +++ b/server/channels/app/post_test.go @@ -877,7 +877,7 @@ func TestImageProxy(t *testing.T) { mockUserStore := storemocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := storemocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := storemocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/channels/app/product_notices_test.go b/server/channels/app/product_notices_test.go index 8285df3443e4..997e93fd4897 100644 --- a/server/channels/app/product_notices_test.go +++ b/server/channels/app/product_notices_test.go @@ -43,7 +43,7 @@ func TestNoticeValidation(t *testing.T) { mockPreferenceStore.On("Get", "test", "Stuff", "Data").Return(&model.Preference{Value: "test2"}, nil) mockPreferenceStore.On("Get", "test", "Stuff", "Data2").Return(&model.Preference{Value: "test"}, nil) mockPreferenceStore.On("Get", "test", "Stuff", "Data3").Return(nil, errors.New("Error!")) - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) th.App.UpdateConfig(func(cfg *model.Config) { *cfg.AnnouncementSettings.AdminNoticesEnabled = true diff --git a/server/channels/app/team_test.go b/server/channels/app/team_test.go index 5e10ee7750c5..bb2461f78246 100644 --- a/server/channels/app/team_test.go +++ b/server/channels/app/team_test.go @@ -1352,7 +1352,7 @@ func TestLeaveTeamPanic(t *testing.T) { mockPreferenceStore.On("Get", "userID", model.PreferenceCategoryDisplaySettings, model.PreferenceNameCollapsedThreadsEnabled).Return(&model.Preference{Value: "on"}, nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) diff --git a/server/channels/db/migrations/migrations.list b/server/channels/db/migrations/migrations.list index 6baf610d1702..a9764be02f98 100644 --- a/server/channels/db/migrations/migrations.list +++ b/server/channels/db/migrations/migrations.list @@ -427,3 +427,5 @@ channels/db/migrations/postgres/000215_drop_channelmembers_autotranslation_colum channels/db/migrations/postgres/000215_drop_channelmembers_autotranslation_column.up.sql channels/db/migrations/postgres/000216_split_attribute_view_by_object_type.down.sql channels/db/migrations/postgres/000216_split_attribute_view_by_object_type.up.sql +channels/db/migrations/postgres/000217_resize_message_columns.down.sql +channels/db/migrations/postgres/000217_resize_message_columns.up.sql diff --git a/server/channels/db/migrations/postgres/000217_resize_message_columns.down.sql b/server/channels/db/migrations/postgres/000217_resize_message_columns.down.sql new file mode 100644 index 000000000000..50903ac600c7 --- /dev/null +++ b/server/channels/db/migrations/postgres/000217_resize_message_columns.down.sql @@ -0,0 +1,32 @@ +DO $$ +DECLARE + col_len int; +BEGIN + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'posts' AND column_name = 'message'; + IF col_len = 1048576 THEN + ALTER TABLE posts ALTER COLUMN message TYPE VARCHAR(65535); + END IF; + + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'drafts' AND column_name = 'message'; + IF col_len = 1048576 THEN + ALTER TABLE drafts ALTER COLUMN message TYPE VARCHAR(65535); + END IF; + + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'scheduledposts' AND column_name = 'message'; + IF col_len = 1048576 THEN + ALTER TABLE scheduledposts ALTER COLUMN message TYPE VARCHAR(65535); + END IF; + + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'temporaryposts' AND column_name = 'message'; + IF col_len = 1048576 THEN + ALTER TABLE temporaryposts ALTER COLUMN message TYPE VARCHAR(65535); + END IF; +END $$; diff --git a/server/channels/db/migrations/postgres/000217_resize_message_columns.up.sql b/server/channels/db/migrations/postgres/000217_resize_message_columns.up.sql new file mode 100644 index 000000000000..7d5abe211ce8 --- /dev/null +++ b/server/channels/db/migrations/postgres/000217_resize_message_columns.up.sql @@ -0,0 +1,32 @@ +DO $$ +DECLARE + col_len int; +BEGIN + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'posts' AND column_name = 'message'; + IF col_len IS NOT NULL AND col_len < 1048576 THEN + ALTER TABLE posts ALTER COLUMN message TYPE VARCHAR(1048576); + END IF; + + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'drafts' AND column_name = 'message'; + IF col_len IS NOT NULL AND col_len < 1048576 THEN + ALTER TABLE drafts ALTER COLUMN message TYPE VARCHAR(1048576); + END IF; + + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'scheduledposts' AND column_name = 'message'; + IF col_len IS NOT NULL AND col_len < 1048576 THEN + ALTER TABLE scheduledposts ALTER COLUMN message TYPE VARCHAR(1048576); + END IF; + + SELECT character_maximum_length INTO col_len + FROM information_schema.columns + WHERE table_name = 'temporaryposts' AND column_name = 'message'; + IF col_len IS NOT NULL AND col_len < 1048576 THEN + ALTER TABLE temporaryposts ALTER COLUMN message TYPE VARCHAR(1048576); + END IF; +END $$; diff --git a/server/channels/store/sqlstore/migration_000217_test.go b/server/channels/store/sqlstore/migration_000217_test.go new file mode 100644 index 000000000000..f237c4d39847 --- /dev/null +++ b/server/channels/store/sqlstore/migration_000217_test.go @@ -0,0 +1,121 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package sqlstore + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "github.com/mattermost/mattermost/server/public/model" + "github.com/mattermost/mattermost/server/public/shared/mlog" +) + +func TestMigration000217(t *testing.T) { + logger := mlog.CreateTestLogger(t) + + settings, err := makeSqlSettings(model.DatabaseDriverPostgres) + if err != nil { + t.Skip(err) + } + + store, err := New(*settings, logger, nil) + require.NoError(t, err) + defer store.Close() + + master := store.GetMaster() + + upSQL := readMigrationSQL(t, "000217_resize_message_columns.up.sql") + downSQL := readMigrationSQL(t, "000217_resize_message_columns.down.sql") + + type tableCol struct { + table string + column string + } + targets := []tableCol{ + {"posts", "message"}, + {"drafts", "message"}, + {"scheduledposts", "message"}, + {"temporaryposts", "message"}, + } + + colLength := func(t *testing.T, table, column string) int { + t.Helper() + var length int + require.NoError(t, master.Get(&length, fmt.Sprintf(` + SELECT COALESCE(character_maximum_length, 0) + FROM information_schema.columns + WHERE table_name = '%s' AND column_name = '%s' + `, table, column))) + return length + } + + setColLength := func(t *testing.T, table, column string, size int) { + t.Helper() + _, alterErr := master.ExecNoTimeout(fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s TYPE VARCHAR(%d)", table, column, size)) + require.NoError(t, alterErr) + } + + restoreTo := func(t *testing.T, size int) { + t.Helper() + for _, tc := range targets { + _, alterErr := master.ExecNoTimeout(fmt.Sprintf("ALTER TABLE %s ALTER COLUMN %s TYPE VARCHAR(%d)", tc.table, tc.column, size)) + require.NoError(t, alterErr) + } + } + + t.Run("NormalUpThenDown", func(t *testing.T) { + t.Cleanup(func() { restoreTo(t, 1048576) }) + + for _, tc := range targets { + setColLength(t, tc.table, tc.column, 65535) + } + + _, err := master.ExecNoTimeout(upSQL) + require.NoError(t, err, "up migration should succeed") + + for _, tc := range targets { + assert.Equal(t, 1048576, colLength(t, tc.table, tc.column), "%s.%s after up migration", tc.table, tc.column) + } + + _, err = master.ExecNoTimeout(downSQL) + require.NoError(t, err, "down migration should succeed") + + for _, tc := range targets { + assert.Equal(t, 65535, colLength(t, tc.table, tc.column), "%s.%s after down migration", tc.table, tc.column) + } + }) + + t.Run("UpSkipsWhenAlreadyLarger", func(t *testing.T) { + t.Cleanup(func() { restoreTo(t, 1048576) }) + + for _, tc := range targets { + setColLength(t, tc.table, tc.column, 2097152) + } + + _, err := master.ExecNoTimeout(upSQL) + require.NoError(t, err, "up migration should succeed even when columns are already larger") + + for _, tc := range targets { + assert.Equal(t, 2097152, colLength(t, tc.table, tc.column), "%s.%s should be unchanged by up migration", tc.table, tc.column) + } + }) + + t.Run("DownSkipsWhenLargerThanTarget", func(t *testing.T) { + t.Cleanup(func() { restoreTo(t, 1048576) }) + + for _, tc := range targets { + setColLength(t, tc.table, tc.column, 2097152) + } + + _, err := master.ExecNoTimeout(downSQL) + require.NoError(t, err, "down migration should succeed even when columns exceed the target size") + + for _, tc := range targets { + assert.Equal(t, 2097152, colLength(t, tc.table, tc.column), "%s.%s should be unchanged by down migration", tc.table, tc.column) + } + }) +} diff --git a/server/channels/web/context_test.go b/server/channels/web/context_test.go index c20f9e6e5ea4..b2d4c51c1e17 100644 --- a/server/channels/web/context_test.go +++ b/server/channels/web/context_test.go @@ -149,7 +149,7 @@ func TestMfaRequired(t *testing.T) { mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockUserStore.On("Get", mock.Anything, "userid").Return(nil, model.NewAppError("Userstore.Get", "storeerror", nil, "store error", http.StatusInternalServerError)) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/channels/web/handlers.go b/server/channels/web/handlers.go index d4622600ece0..ec22941a3d3e 100644 --- a/server/channels/web/handlers.go +++ b/server/channels/web/handlers.go @@ -451,6 +451,8 @@ func (h Handler) handleContextError(c *Context, w http.ResponseWriter, r *http.R c.Err.Id = "" c.Err.Message = "Internal Server Error" c.Err.WipeDetailed() + // Props is always exposed to clients, so scrub it along with the rest of the sanitized 5xx. + c.Err.Props = nil c.Err.StatusCode = 500 c.Err.Where = "" } diff --git a/server/channels/web/handlers_test.go b/server/channels/web/handlers_test.go index 86a161cd7d12..5e2bf72115f2 100644 --- a/server/channels/web/handlers_test.go +++ b/server/channels/web/handlers_test.go @@ -108,7 +108,7 @@ func TestHandlerServeHTTPSecureTransport(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -381,7 +381,7 @@ func TestHandlerServeCSPHeader(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -756,7 +756,7 @@ func TestCheckCSRFToken(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1035,7 +1035,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1066,7 +1066,7 @@ func TestHandlerServeHTTPBasicSecurityChecks(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1128,7 +1128,7 @@ func TestHandlerServeHTTPRequestPayloadLimit(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) @@ -1260,6 +1260,61 @@ func TestHandleContextErrorZeroStatusCode(t *testing.T) { }) } +func TestHandleContextErrorProps(t *testing.T) { + respondWith := func(t *testing.T, th *TestHelper, appErr *model.AppError) *model.AppError { + t.Helper() + + c := &Context{ + App: th.App, + AppContext: th.Context, + Logger: th.App.Log(), + Err: appErr, + } + + request := httptest.NewRequest("POST", "/api/v4/test", nil) + response := httptest.NewRecorder() + + h := Handler{Srv: th.Server} + h.handleContextError(c, response, request) + + var responded model.AppError + require.NoError(t, json.Unmarshal(response.Body.Bytes(), &responded)) + + return &responded + } + + t.Run("should wipe detailed error but keep props when developer mode is off", func(t *testing.T) { + th := Setup(t) + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.EnableDeveloper = false + }) + + appErr := model.NewAppError("TestFunction", "test.error", nil, "test details", http.StatusBadRequest) + appErr.Props = model.StringMap{"plugin_id": "com.example"} + + responded := respondWith(t, th, appErr) + assert.Empty(t, responded.DetailedError, "developer mode off must still wipe the detailed error") + assert.Equal(t, model.StringMap{"plugin_id": "com.example"}, responded.Props) + }) + + t.Run("hardened mode should sanitize a 5xx including props", func(t *testing.T) { + th := Setup(t) + th.App.UpdateConfig(func(cfg *model.Config) { + *cfg.ServiceSettings.EnableDeveloper = false + *cfg.ServiceSettings.EnableHardenedMode = true + }) + + appErr := model.NewAppError("TestFunction", "test.error", nil, "test details", http.StatusInternalServerError) + appErr.Props = model.StringMap{"plugin_id": "com.example"} + + responded := respondWith(t, th, appErr) + assert.Empty(t, responded.DetailedError) + assert.Empty(t, responded.Props, "hardened mode must scrub props on a sanitized 5xx") + assert.Equal(t, "Internal Server Error", responded.Message) + assert.Empty(t, responded.Id) + }) +} + func TestTokenDigest(t *testing.T) { token := model.NewId() digest := tokenDigest(token) diff --git a/server/cmd/mmctl/commands/plugin_e2e_test.go b/server/cmd/mmctl/commands/plugin_e2e_test.go index 70f69da955c5..52166b18384c 100644 --- a/server/cmd/mmctl/commands/plugin_e2e_test.go +++ b/server/cmd/mmctl/commands/plugin_e2e_test.go @@ -249,10 +249,8 @@ func (s *MmctlE2ETestSuite) TestPluginInstallURLCmd() { s.Require().Len(printer.GetErrorLines(), 0) s.Require().Equal(plugin1ID, printer.GetLines()[0].(*model.Manifest).Id) - var expected error - expected = multierror.Append(expected, errors.New("Unable to install plugin. A plugin with the same ID is already installed.")) //nolint:revive err = pluginInstallURLCmdF(c, &cobra.Command{}, []string{plugin1URL}) - s.Require().ErrorContains(err, expected.Error()) + s.Require().ErrorContains(err, "Unable to install plugin. A plugin with the same ID is already installed.") s.Require().Len(printer.GetLines(), 1) s.Require().Len(printer.GetErrorLines(), 1) s.Require().Contains(printer.GetErrorLines()[0], fmt.Sprintf("Unable to install plugin from URL \"%s\".", plugin1URL)) diff --git a/server/enterprise/elasticsearch/elasticsearch/aggregation_job_test.go b/server/enterprise/elasticsearch/elasticsearch/aggregation_job_test.go index f8593fabf100..b2c320ecf993 100644 --- a/server/enterprise/elasticsearch/elasticsearch/aggregation_job_test.go +++ b/server/enterprise/elasticsearch/elasticsearch/aggregation_job_test.go @@ -28,7 +28,7 @@ func TestElasticsearchAggregation(t *testing.T) { mockUserStore.On("GetAllProfiles", mock.Anything).Return(nil, nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) @@ -133,7 +133,7 @@ func TestElasticsearchAggregationSkipDuringBulkIndexing(t *testing.T) { mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) diff --git a/server/enterprise/elasticsearch/opensearch/aggregation_job_test.go b/server/enterprise/elasticsearch/opensearch/aggregation_job_test.go index fa4e109c0b37..c69306144c74 100644 --- a/server/enterprise/elasticsearch/opensearch/aggregation_job_test.go +++ b/server/enterprise/elasticsearch/opensearch/aggregation_job_test.go @@ -44,7 +44,7 @@ func TestElasticsearchAggregation(t *testing.T) { mockUserStore.On("GetAllProfiles", mock.Anything).Return(nil, nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) @@ -155,7 +155,7 @@ func TestElasticsearchAggregationSkipDuringBulkIndexing(t *testing.T) { mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) diff --git a/server/enterprise/metrics/metrics_test.go b/server/enterprise/metrics/metrics_test.go index 8c9614d46be7..d9e9c1e73a1a 100644 --- a/server/enterprise/metrics/metrics_test.go +++ b/server/enterprise/metrics/metrics_test.go @@ -37,7 +37,7 @@ func TestMetrics(t *testing.T) { mockUserStore := mocks.UserStore{} mockUserStore.On("Count", mock.Anything).Return(int64(10), nil) mockPostStore := mocks.PostStore{} - mockPostStore.On("GetMaxPostSize").Return(65535, nil) + mockPostStore.On("GetMaxPostSize").Return(model.PostMessageMaxBytesV2, nil) mockSystemStore := mocks.SystemStore{} mockSystemStore.On("GetByName", "UpgradedFromTE").Return(&model.System{Name: "UpgradedFromTE", Value: "false"}, nil) mockSystemStore.On("GetByName", "InstallationDate").Return(&model.System{Name: "InstallationDate", Value: "10"}, nil) diff --git a/server/i18n/en.json b/server/i18n/en.json index ce29581ec103..c510dc796c5a 100644 --- a/server/i18n/en.json +++ b/server/i18n/en.json @@ -12000,7 +12000,7 @@ }, { "id": "model.config.is_valid.dcr_redirect_uri_allowlist.app_error", - "translation": "DCR redirect URI allowlist contains an invalid pattern. Patterns must start with http:// or https:// and cannot be empty or whitespace." + "translation": "DCR redirect URI allowlist contains an invalid pattern. Each pattern must be a valid URI with a scheme and host (for example https://example.com/** or cursor://app/callback) and cannot be empty or whitespace." }, { "id": "model.config.is_valid.directory.app_error", diff --git a/server/public/model/config.go b/server/public/model/config.go index e3a9bed0a532..04558d34fcdd 100644 --- a/server/public/model/config.go +++ b/server/public/model/config.go @@ -2152,7 +2152,7 @@ type EmailSettings struct { EnableSignInWithEmail *bool `access:"authentication_email"` EnableSignInWithUsername *bool `access:"authentication_email"` SendEmailNotifications *bool `access:"site_notifications"` - UseChannelInEmailNotifications *bool `access:"experimental_features"` + UseChannelInEmailNotifications *bool `access:"site_notifications"` RequireEmailVerification *bool `access:"authentication_email"` FeedbackName *string `access:"site_notifications"` FeedbackEmail *string `access:"site_notifications,cloud_restrictable"` @@ -2170,8 +2170,8 @@ type EmailSettings struct { PushNotificationContents *string `access:"site_notifications"` PushNotificationBuffer *int // telemetry: none EnableEmailBatching *bool `access:"site_notifications"` - EmailBatchingBufferSize *int `access:"experimental_features"` - EmailBatchingInterval *int `access:"experimental_features"` + EmailBatchingBufferSize *int `access:"site_notifications"` + EmailBatchingInterval *int `access:"site_notifications"` EnablePreviewModeBanner *bool `access:"site_notifications"` SkipServerCertificateVerification *bool `access:"environment_smtp,write_restrictable,cloud_restrictable"` EmailNotificationContentsType *string `access:"site_notifications"` diff --git a/server/public/model/config_test.go b/server/public/model/config_test.go index 387e7bd06e81..24868693b970 100644 --- a/server/public/model/config_test.go +++ b/server/public/model/config_test.go @@ -2776,14 +2776,25 @@ func TestConfigServiceSettingsIsValid(t *testing.T) { appErr := cfg.ServiceSettings.isValid() require.Nil(t, appErr) + // Custom URI schemes used by desktop OAuth clients are accepted + cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"cursor://anysphere.cursor-mcp/oauth/callback", "com.example.app://callback/**"} + appErr = cfg.ServiceSettings.isValid() + require.Nil(t, appErr) + // Empty/whitespace entry rejected cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"https://ok.com/**", " ", "https://also.com/cb"} appErr = cfg.ServiceSettings.isValid() require.NotNil(t, appErr) require.Equal(t, "model.config.is_valid.dcr_redirect_uri_allowlist.app_error", appErr.Id) - // Non-http(s) scheme rejected - cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"ftp://example.com/**"} + // Scheme without a host rejected + cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"cursor://"} + appErr = cfg.ServiceSettings.isValid() + require.NotNil(t, appErr) + require.Equal(t, "model.config.is_valid.dcr_redirect_uri_allowlist.app_error", appErr.Id) + + // Opaque URI without a host rejected + cfg.ServiceSettings.DCRRedirectURIAllowlist = []string{"javascript:alert(1)"} appErr = cfg.ServiceSettings.isValid() require.NotNil(t, appErr) require.Equal(t, "model.config.is_valid.dcr_redirect_uri_allowlist.app_error", appErr.Id) diff --git a/server/public/model/integration_action.go b/server/public/model/integration_action.go index 9602cb2370c2..d288ef1411e7 100644 --- a/server/public/model/integration_action.go +++ b/server/public/model/integration_action.go @@ -32,7 +32,7 @@ import ( const ( PostActionTypeButton = "button" PostActionTypeSelect = "select" - DialogTitleMaxLength = 24 + DialogTitleMaxLength = 80 DialogElementDisplayNameMaxLength = 24 DialogElementNameMaxLength = 300 DialogElementHelpTextMaxLength = 150 diff --git a/server/public/model/integration_action_test.go b/server/public/model/integration_action_test.go index 592850b17edf..bd61c1535ade 100644 --- a/server/public/model/integration_action_test.go +++ b/server/public/model/integration_action_test.go @@ -589,7 +589,7 @@ func TestOpenDialogRequestIsValid(t *testing.T) { t.Run("should fail on wrong subtype and long dialog title", func(t *testing.T) { request := getBaseOpenDialogRequest() request.Dialog.Elements[0].SubType = "wrong SubType" - request.Dialog.Title = "Very very long Dialog Name" + request.Dialog.Title = strings.Repeat("Very very long Dialog Name ", 5) err := request.IsValid() assert.ErrorContains(t, err, "invalid subtype") assert.ErrorContains(t, err, "invalid dialog title") diff --git a/server/public/model/oauth.go b/server/public/model/oauth.go index e8f5c195e6b4..37c9a9f50fcc 100644 --- a/server/public/model/oauth.go +++ b/server/public/model/oauth.go @@ -106,7 +106,13 @@ func (a *OAuthApp) IsValid() *AppError { } for _, callback := range a.CallbackUrls { - if !IsValidHTTPURL(callback) { + // Dynamically registered (DCR) clients may use custom URI schemes such as + // cursor://; manually created OAuth apps remain restricted to http/https. + valid := IsValidHTTPURL(callback) + if a.IsDynamicallyRegistered { + valid = IsValidDCRRedirectURI(callback) + } + if !valid { return NewAppError("OAuthApp.IsValid", "model.oauth.is_valid.callback.app_error", nil, "", http.StatusBadRequest) } } diff --git a/server/public/model/oauth_dcr.go b/server/public/model/oauth_dcr.go index 7a7250339ea8..0324bfb2362d 100644 --- a/server/public/model/oauth_dcr.go +++ b/server/public/model/oauth_dcr.go @@ -52,7 +52,7 @@ func (r *ClientRegistrationRequest) IsValid() *AppError { } for _, uri := range r.RedirectURIs { - if !IsValidHTTPURL(uri) { + if !IsValidDCRRedirectURI(uri) { return NewAppError("ClientRegistrationRequest.IsValid", "model.dcr.is_valid.redirect_uri_format.app_error", nil, "uri="+uri, http.StatusBadRequest) } } @@ -92,20 +92,29 @@ func GetDefaultResponseTypes() []string { return []string{ResponseTypeCode} } -// IsValidDCRRedirectURIPattern validates a DCR redirect URI allowlist pattern. -// Patterns must start with http:// or https:// and be well-formed for glob matching. -func IsValidDCRRedirectURIPattern(pattern string) bool { - if strings.HasPrefix(pattern, "https://") { - if len(pattern) < 9 { // minimum "https://x" - return false - } - } else if strings.HasPrefix(pattern, "http://") { - if len(pattern) < 8 { // minimum "http://x" - return false - } - } else { +// IsValidDCRRedirectURI validates a concrete DCR redirect URI. Unlike +// IsValidHTTPURL, it accepts custom (non-HTTP) schemes so that desktop OAuth +// clients can use their own URI schemes (e.g. cursor://anysphere.cursor-mcp/oauth/callback). +// The URI must be absolute with both a scheme and a host. Dangerous schemes +// (javascript, data, vbscript, file, blob, about) are rejected even in +// authority form (e.g. "javascript://evil.example.com/x"). +func IsValidDCRRedirectURI(rawURL string) bool { + u, err := url.ParseRequestURI(rawURL) + if err != nil || u.Scheme == "" || u.Host == "" { return false } + switch strings.ToLower(u.Scheme) { + case "javascript", "data", "vbscript", "file", "blob", "about": + return false + } + return true +} + +// IsValidDCRRedirectURIPattern validates a DCR redirect URI allowlist pattern. +// Patterns must be absolute URIs with a scheme and host and be well-formed for +// glob matching. Custom schemes (e.g. cursor://) are permitted in addition to +// http:// and https://. +func IsValidDCRRedirectURIPattern(pattern string) bool { // Reject control characters and other invalid chars for _, r := range pattern { if r < 0x20 || r == 0x7f { @@ -126,7 +135,7 @@ func IsValidDCRRedirectURIPattern(pattern string) bool { normalized = strings.ReplaceAll(normalized, "mmdoublewildcard", "1") normalized = strings.ReplaceAll(normalized, "mmsinglewildcard", "1") - return IsValidHTTPURL(normalized) + return IsValidDCRRedirectURI(normalized) } // RedirectURIMatchesGlob returns true if uri matches the glob pattern. diff --git a/server/public/model/oauth_dcr_test.go b/server/public/model/oauth_dcr_test.go index 162977ccbb27..2457b8b7cab9 100644 --- a/server/public/model/oauth_dcr_test.go +++ b/server/public/model/oauth_dcr_test.go @@ -47,8 +47,20 @@ func TestClientRegistrationRequestIsValid(t *testing.T) { req.RedirectURIs = []string{"http://localhost:3000/callback"} require.Nil(t, req.IsValid()) + // Custom URI schemes used by desktop OAuth clients are accepted. + req.RedirectURIs = []string{"cursor://anysphere.cursor-mcp/oauth/callback"} + require.Nil(t, req.IsValid()) + req.RedirectURIs = []string{"invalid-uri"} require.NotNil(t, req.IsValid()) + + // Opaque URIs without a host are rejected. + req.RedirectURIs = []string{"javascript:alert(1)"} + require.NotNil(t, req.IsValid()) + + // Authority-form dangerous schemes are rejected even though they have a host. + req.RedirectURIs = []string{"javascript://evil.example.com/steal"} + require.NotNil(t, req.IsValid()) }) } @@ -74,6 +86,31 @@ func TestNewOAuthAppFromClientRegistration(t *testing.T) { require.Empty(t, app.ClientSecret) }) + + t.Run("CustomSchemeRedirectURIPassesOAuthAppValidation", func(t *testing.T) { + req := &ClientRegistrationRequest{ + RedirectURIs: []string{"cursor://anysphere.cursor-mcp/oauth/callback"}, + ClientName: new("Desktop Client"), + } + + app := NewOAuthAppFromClientRegistration(req, NewId()) + app.PreSave() + + // DCR apps map redirect URIs to CallbackUrls, so OAuthApp.IsValid must + // accept the same custom schemes allowed at registration time. + require.Nil(t, app.IsValid()) + }) + + t.Run("DangerousSchemeRedirectURIRejectedByOAuthAppValidation", func(t *testing.T) { + app := &OAuthApp{ + CallbackUrls: StringArray{"javascript://evil.example.com/steal"}, + Name: "Desktop Client", + IsDynamicallyRegistered: true, + } + app.PreSave() + + require.NotNil(t, app.IsValid()) + }) } func TestRedirectURIMatchesGlob(t *testing.T) { @@ -117,6 +154,17 @@ func TestRedirectURIMatchesGlob(t *testing.T) { t.Run("query string must be explicitly allowed", func(t *testing.T) { require.False(t, RedirectURIMatchesGlob("https://app.example.com/callback?tenant=foo", "https://app.example.com/callback")) require.True(t, RedirectURIMatchesGlob("https://app.example.com/callback?tenant=foo", "https://app.example.com/callback?tenant=*")) + require.True(t, RedirectURIMatchesGlob("https://app.example.com/callback?a=1&b=2", "https://app.example.com/callback?**")) + // A pattern that requires a query must not match a URI without one. + require.False(t, RedirectURIMatchesGlob("https://app.example.com/callback", "https://app.example.com/callback?tenant=*")) + }) + + t.Run("invalid pattern or candidate never matches", func(t *testing.T) { + // Malformed pattern (wildcard run) is rejected before matching. + require.False(t, RedirectURIMatchesGlob("https://a.com/x", "https://a.com/***")) + // Opaque/unparseable candidate URIs never match a valid pattern. + require.False(t, RedirectURIMatchesGlob("javascript:alert(1)", "https://a.com/**")) + require.False(t, RedirectURIMatchesGlob("not a url", "https://a.com/**")) }) t.Run("port wildcard", func(t *testing.T) { @@ -124,6 +172,14 @@ func TestRedirectURIMatchesGlob(t *testing.T) { require.False(t, RedirectURIMatchesGlob("https://localhost:3000/cb", "https://localhost:8080/cb")) }) + t.Run("custom scheme", func(t *testing.T) { + require.True(t, RedirectURIMatchesGlob("cursor://anysphere.cursor-mcp/oauth/callback", "cursor://anysphere.cursor-mcp/oauth/callback")) + require.True(t, RedirectURIMatchesGlob("cursor://anysphere.cursor-mcp/oauth/callback", "cursor://anysphere.cursor-mcp/**")) + require.False(t, RedirectURIMatchesGlob("cursor://anysphere.cursor-mcp/oauth/callback", "cursor://other.app/**")) + // Scheme must match exactly: an https candidate must not satisfy a cursor:// pattern. + require.False(t, RedirectURIMatchesGlob("https://anysphere.cursor-mcp/oauth/callback", "cursor://anysphere.cursor-mcp/**")) + }) + t.Run("multiple patterns one match suffices", func(t *testing.T) { allowlist := []string{"https://a.com/**", "https://b.com/**"} require.True(t, RedirectURIMatchesAllowlist("https://a.com/x", allowlist)) @@ -131,6 +187,11 @@ func TestRedirectURIMatchesGlob(t *testing.T) { require.False(t, RedirectURIMatchesAllowlist("https://c.com/z", allowlist)) }) + t.Run("whitespace allowlist entries are skipped", func(t *testing.T) { + require.True(t, RedirectURIMatchesAllowlist("https://a.com/x", []string{" ", "https://a.com/**"})) + require.False(t, RedirectURIMatchesAllowlist("https://a.com/x", []string{" ", ""})) + }) + t.Run("empty allowlist permits all", func(t *testing.T) { require.True(t, RedirectURIMatchesAllowlist("https://any.com/cb", []string{})) }) @@ -149,13 +210,77 @@ func TestRedirectURIMatchesGlob(t *testing.T) { }) } +func TestIsValidDCRRedirectURI(t *testing.T) { + valid := []string{ + "https://example.com/callback", + "http://localhost:3000/cb", + "cursor://anysphere.cursor-mcp/oauth/callback", + "com.example.app://callback", + } + for _, uri := range valid { + require.True(t, IsValidDCRRedirectURI(uri), "expected valid: %s", uri) + } + + invalid := []string{ + "", + " ", // whitespace only + " https://x ", // surrounding whitespace + "cursor://", // custom scheme without a host + "https://", // http scheme without a host + "javascript:alert(1)", // opaque URI, no host + "data:text/html,hi", // opaque URI, no host + "/relative/path", // not absolute + "not a url", + // Authority-form dangerous schemes parse with a host but must still be rejected. + "javascript://evil.example.com/steal", + "data://evil.example.com/steal", + "file://localhost/etc/passwd", + "vbscript://evil.example.com/x", + "blob://evil.example.com/x", + "about://evil.example.com/x", + "JavaScript://evil.example.com/steal", // case-insensitive scheme check + } + for _, uri := range invalid { + require.False(t, IsValidDCRRedirectURI(uri), "expected invalid: %s", uri) + } +} + func TestIsValidDCRRedirectURIPattern(t *testing.T) { - require.True(t, IsValidDCRRedirectURIPattern("https://example.com/**")) - require.True(t, IsValidDCRRedirectURIPattern("http://localhost:3000/cb")) - require.True(t, IsValidDCRRedirectURIPattern("http://localhost:*")) - require.True(t, IsValidDCRRedirectURIPattern("http://x")) // minimum valid http URL (8 chars) - require.True(t, IsValidDCRRedirectURIPattern("https://x")) // minimum valid https URL (9 chars) - require.False(t, IsValidDCRRedirectURIPattern("https://")) - require.False(t, IsValidDCRRedirectURIPattern("ftp://example.com")) - require.False(t, IsValidDCRRedirectURIPattern("https://example.com/***")) + valid := []string{ + "https://example.com/**", + "http://localhost:3000/cb", + "http://localhost:*", + "http://x", // minimum valid http URL + "https://x", // minimum valid https URL + "cursor://anysphere.cursor-mcp/oauth/callback", // custom desktop-app scheme + "cursor://app/callback", + "com.example.app://callback/**", + "cursor://*.example.com/**", // wildcards allowed with custom schemes + } + for _, p := range valid { + require.True(t, IsValidDCRRedirectURIPattern(p), "expected valid: %s", p) + } + + invalid := []string{ + "", + "https://", // missing host + "cursor://", // custom scheme missing host + "://example.com", // missing scheme + "javascript:alert(1)", // opaque URI, no host + "https://example.com/***", // malformed wildcard run + "https://example.com/\n", // control character (newline) + "https://exa\tmple.com/*", // control character (tab) + "\x7f", // control character (DEL) + // Authority-form dangerous schemes must not be usable as allowlist patterns. + "javascript://evil.example.com/**", + "data://evil.example.com/**", + "file://localhost/**", + "vbscript://evil.example.com/*", + "blob://evil.example.com/**", + "about://evil.example.com/**", + "JavaScript://evil.example.com/**", + } + for _, p := range invalid { + require.False(t, IsValidDCRRedirectURIPattern(p), "expected invalid: %s", p) + } } diff --git a/server/public/model/oauth_test.go b/server/public/model/oauth_test.go index 1119509d3e73..83eb74173848 100644 --- a/server/public/model/oauth_test.go +++ b/server/public/model/oauth_test.go @@ -121,4 +121,44 @@ func TestOAuthAppIsValid(t *testing.T) { require.False(t, app.IsPublicClient()) require.Equal(t, ClientAuthMethodClientSecretPost, app.GetTokenEndpointAuthMethod()) }) + + t.Run("ManualAppRejectsCustomSchemeCallback", func(t *testing.T) { + app := OAuthApp{ + Id: NewId(), + CreatorId: NewId(), + CreateAt: 1, + UpdateAt: 1, + Name: "Test Client", + CallbackUrls: []string{"cursor://anysphere.cursor-mcp/oauth/callback"}, + Homepage: "https://example.com", + } + + require.NotNil(t, app.IsValid()) + }) + + t.Run("DynamicallyRegisteredAppAcceptsCustomSchemeCallback", func(t *testing.T) { + app := OAuthApp{ + Id: NewId(), + CreateAt: 1, + UpdateAt: 1, + Name: "Test Client", + CallbackUrls: []string{"cursor://anysphere.cursor-mcp/oauth/callback"}, + IsDynamicallyRegistered: true, + } + + require.Nil(t, app.IsValid()) + }) + + t.Run("DynamicallyRegisteredAppRejectsDangerousSchemeCallback", func(t *testing.T) { + app := OAuthApp{ + Id: NewId(), + CreateAt: 1, + UpdateAt: 1, + Name: "Test Client", + CallbackUrls: []string{"javascript://evil.example.com/steal"}, + IsDynamicallyRegistered: true, + } + + require.NotNil(t, app.IsValid()) + }) } diff --git a/server/public/model/plugin_install.go b/server/public/model/plugin_install.go new file mode 100644 index 000000000000..eec9135569bd --- /dev/null +++ b/server/public/model/plugin_install.go @@ -0,0 +1,52 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +package model + +import "github.com/Masterminds/semver/v3" + +const ( + PluginInstallConflictVersionDirectionUpgrade = "upgrade" + PluginInstallConflictVersionDirectionDowngrade = "downgrade" + PluginInstallConflictVersionDirectionSame = "same" + PluginInstallConflictVersionDirectionUnknown = "unknown" +) + +// Keys set in AppError.Props when a plugin upload conflicts with an already installed plugin. The +// plugin upload confirmation dialog reads them to describe the version change before overwriting. +const ( + PluginInstallConflictPropPluginID = "plugin_id" + PluginInstallConflictPropPluginName = "plugin_name" + PluginInstallConflictPropHomepageURL = "homepage_url" + PluginInstallConflictPropExistingVersion = "existing_version" + PluginInstallConflictPropUploadedVersion = "uploaded_version" + PluginInstallConflictPropVersionDirection = "version_direction" +) + +// PluginInstallConflictVersionDirection compares the versions of an already installed plugin +// manifest and an uploaded plugin manifest with the same id, returning one of the +// PluginInstallConflictVersionDirection* constants. +func PluginInstallConflictVersionDirection(existingManifest, uploadedManifest *Manifest) string { + if existingManifest == nil || uploadedManifest == nil { + return PluginInstallConflictVersionDirectionUnknown + } + + existing, err := semver.StrictNewVersion(existingManifest.Version) + if err != nil { + return PluginInstallConflictVersionDirectionUnknown + } + + uploaded, err := semver.StrictNewVersion(uploadedManifest.Version) + if err != nil { + return PluginInstallConflictVersionDirectionUnknown + } + + if uploaded.Equal(existing) { + return PluginInstallConflictVersionDirectionSame + } + if uploaded.GreaterThan(existing) { + return PluginInstallConflictVersionDirectionUpgrade + } + + return PluginInstallConflictVersionDirectionDowngrade +} diff --git a/server/public/model/post.go b/server/public/model/post.go index fc03584858e2..54577df1b296 100644 --- a/server/public/model/post.go +++ b/server/public/model/post.go @@ -73,7 +73,7 @@ const ( PostFilenamesMaxRunes = 4000 PostHashtagsMaxRunes = 1000 PostMessageMaxRunesV1 = 4000 - PostMessageMaxBytesV2 = 65535 + PostMessageMaxBytesV2 = 1048576 PostMessageMaxRunesV2 = PostMessageMaxBytesV2 / 4 // Assume a worst-case representation // Reporting API constants diff --git a/server/public/model/utils.go b/server/public/model/utils.go index 5e1341edaaf0..13dcd26800d4 100644 --- a/server/public/model/utils.go +++ b/server/public/model/utils.go @@ -237,8 +237,14 @@ type AppError struct { StatusCode int `json:"status_code,omitempty"` // The http status code Where string `json:"-"` // The function where it happened in the form of Struct.Func SkipTranslation bool `json:"-"` // Whether translation for the error should be skipped. - params map[string]any - wrapped error + + // Props carries caller-authored context to API clients, like the Props fields on Users and + // Posts. Unlike DetailedError it is always returned to clients regardless of developer mode, so + // it must only hold flat strings that are safe for any client to see - never internal details. + Props StringMap `json:"props,omitempty"` + + params map[string]any + wrapped error } const maxErrorLength = 1024 diff --git a/server/public/model/utils_test.go b/server/public/model/utils_test.go index 98da0f44d338..3721c4744307 100644 --- a/server/public/model/utils_test.go +++ b/server/public/model/utils_test.go @@ -224,6 +224,20 @@ func TestAppErrorSerialize(t *testing.T) { require.EqualError(t, berr, aerr.Error()) }) + t.Run("Props survive WipeDetailed and round-trip to clients", func(t *testing.T) { + aerr := NewAppError("", "message", nil, "detail", http.StatusTeapot).Wrap(errors.New("wrapped")) + aerr.Props = StringMap{"plugin_id": "com.example", "version_direction": "downgrade"} + aerr.WipeDetailed() + js := aerr.ToJSON() + err := AppErrorFromJSON(strings.NewReader(js)) + berr, ok := err.(*AppError) + require.True(t, ok) + require.Equal(t, "message", berr.Id) + require.Empty(t, berr.DetailedError, "WipeDetailed must still discard the detailed error") + require.Equal(t, StringMap{"plugin_id": "com.example", "version_direction": "downgrade"}, berr.Props) + require.Equal(t, http.StatusTeapot, berr.StatusCode) + }) + t.Run("Where", func(t *testing.T) { appErr := NewAppError("TestAppError", "message", nil, "", http.StatusInternalServerError) json := appErr.ToJSON() diff --git a/webapp/channels/src/components/admin_console/admin_definition.tsx b/webapp/channels/src/components/admin_console/admin_definition.tsx index 7ee4b0ed5ebd..a15ddb3b77d9 100644 --- a/webapp/channels/src/components/admin_console/admin_definition.tsx +++ b/webapp/channels/src/components/admin_console/admin_definition.tsx @@ -3196,6 +3196,30 @@ const AdminDefinition: AdminDefinitionType = { ), isHidden: it.licensedForFeature('Cloud'), }, + { + type: 'number', + key: 'EmailSettings.EmailBatchingBufferSize', + label: defineMessage({id: 'admin.environment.notifications.emailBatchingBufferSize.label', defaultMessage: 'Email Batching Buffer Size:'}), + help_text: defineMessage({id: 'admin.environment.notifications.emailBatchingBufferSize.help', defaultMessage: 'Specify the maximum number of notifications batched into a single email.'}), + placeholder: defineMessage({id: 'admin.environment.notifications.emailBatchingBufferSize.placeholder', defaultMessage: 'E.g.: "256"'}), + isDisabled: it.any( + it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.NOTIFICATIONS)), + it.stateIsFalse('EmailSettings.EnableEmailBatching'), + ), + isHidden: it.licensedForFeature('Cloud'), + }, + { + type: 'number', + key: 'EmailSettings.EmailBatchingInterval', + label: defineMessage({id: 'admin.environment.notifications.emailBatchingInterval.label', defaultMessage: 'Email Batching Interval:'}), + help_text: defineMessage({id: 'admin.environment.notifications.emailBatchingInterval.help', defaultMessage: 'Specify the maximum frequency, in seconds, which the batching job checks for new notifications. Longer batching intervals will increase performance.'}), + placeholder: defineMessage({id: 'admin.environment.notifications.emailBatchingInterval.placeholder', defaultMessage: 'E.g.: "30"'}), + isDisabled: it.any( + it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.NOTIFICATIONS)), + it.stateIsFalse('EmailSettings.EnableEmailBatching'), + ), + isHidden: it.licensedForFeature('Cloud'), + }, { type: 'dropdown', key: 'EmailSettings.EmailNotificationContentsType', @@ -3215,6 +3239,13 @@ const AdminDefinition: AdminDefinitionType = { isHidden: it.not(it.licensedForFeature('EmailNotificationContents')), isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.NOTIFICATIONS)), }, + { + type: 'bool', + key: 'EmailSettings.UseChannelInEmailNotifications', + label: defineMessage({id: 'admin.environment.notifications.useChannelInEmailNotifications.label', defaultMessage: 'Use Channel Name in Email Notifications:'}), + help_text: defineMessage({id: 'admin.environment.notifications.useChannelInEmailNotifications.help', defaultMessage: 'When true, channel and team name appears in email notification subject lines. Useful for servers using only one team. When false, only team name appears in email notification subject line.'}), + isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.SITE.NOTIFICATIONS)), + }, { type: 'text', key: 'EmailSettings.FeedbackName', @@ -6050,7 +6081,7 @@ const AdminDefinition: AdminDefinitionType = { key: 'ServiceSettings.DCRRedirectURIAllowlist', multiple: true, label: defineMessage({id: 'admin.oauth.dcrRedirectURIAllowlistTitle', defaultMessage: 'DCR Redirect URI Allowlist:'}), - help_text: defineMessage({id: 'admin.oauth.dcrRedirectURIAllowlistDesc', defaultMessage: 'When Dynamic Client Registration is enabled, optionally restrict which redirect URIs can be registered. Enter comma-separated URL glob patterns (e.g. https://*.example.com/**). If empty, all valid redirect URIs are allowed. Wildcards are matched within URL components only: host wildcards apply to the host, path wildcards apply to the path, and query strings must be explicitly included if allowed.'}), + help_text: defineMessage({id: 'admin.oauth.dcrRedirectURIAllowlistDesc', defaultMessage: 'When Dynamic Client Registration is enabled, optionally restrict which redirect URIs can be registered. Enter comma-separated URL glob patterns (e.g. https://*.example.com/**). Custom URI schemes used by desktop OAuth clients (e.g. cursor://app/callback) are supported in addition to http and https. If empty, all valid redirect URIs are allowed. Wildcards are matched within URL components only: host wildcards apply to the host, path wildcards apply to the path, and query strings must be explicitly included if allowed.'}), help_text_markdown: false, placeholder: defineMessage({id: 'admin.oauth.dcrRedirectURIAllowlistPlaceholder', defaultMessage: 'E.g.: https://*.example.com/**, https://app.example.com/callback'}), isDisabled: it.any( @@ -6660,24 +6691,6 @@ const AdminDefinition: AdminDefinitionType = { placeholder: defineMessage({id: 'admin.experimental.linkMetadataTimeoutMilliseconds.example', defaultMessage: 'E.g.: "5000"'}), isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)), }, - { - type: 'number', - key: 'EmailSettings.EmailBatchingBufferSize', - label: defineMessage({id: 'admin.experimental.emailBatchingBufferSize.title', defaultMessage: 'Email Batching Buffer Size:'}), - help_text: defineMessage({id: 'admin.experimental.emailBatchingBufferSize.desc', defaultMessage: 'Specify the maximum number of notifications batched into a single email.'}), - help_text_markdown: false, - placeholder: defineMessage({id: 'admin.experimental.emailBatchingBufferSize.example', defaultMessage: 'E.g.: "256"'}), - isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)), - }, - { - type: 'number', - key: 'EmailSettings.EmailBatchingInterval', - label: defineMessage({id: 'admin.experimental.emailBatchingInterval.title', defaultMessage: 'Email Batching Interval:'}), - help_text: defineMessage({id: 'admin.experimental.emailBatchingInterval.desc', defaultMessage: 'Specify the maximum frequency, in seconds, which the batching job checks for new notifications. Longer batching intervals will increase performance.'}), - help_text_markdown: false, - placeholder: defineMessage({id: 'admin.experimental.emailBatchingInterval.example', defaultMessage: 'E.g.: "30"'}), - isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)), - }, { type: 'bool', key: 'TeamSettings.ExperimentalEnableAutomaticReplies', @@ -6712,14 +6725,6 @@ const AdminDefinition: AdminDefinitionType = { placeholder: defineMessage({id: 'admin.experimental.experimentalPrimaryTeam.example', defaultMessage: 'E.g.: "teamname"'}), isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)), }, - { - type: 'bool', - key: 'EmailSettings.UseChannelInEmailNotifications', - label: defineMessage({id: 'admin.experimental.useChannelInEmailNotifications.title', defaultMessage: 'Use Channel Name in Email Notifications:'}), - help_text: defineMessage({id: 'admin.experimental.useChannelInEmailNotifications.desc', defaultMessage: 'When true, channel and team name appears in email notification subject lines. Useful for servers using only one team. When false, only team name appears in email notification subject line.'}), - help_text_markdown: false, - isDisabled: it.not(it.userHasWritePermissionOnResource(RESOURCE_KEYS.EXPERIMENTAL.FEATURES)), - }, { type: 'bool', key: 'ExperimentalSettings.DisableAppBar', diff --git a/webapp/channels/src/components/admin_console/plugin_management/plugin_management.test.tsx b/webapp/channels/src/components/admin_console/plugin_management/plugin_management.test.tsx index 3d4cd3380a7f..8346ce01d447 100644 --- a/webapp/channels/src/components/admin_console/plugin_management/plugin_management.test.tsx +++ b/webapp/channels/src/components/admin_console/plugin_management/plugin_management.test.tsx @@ -106,6 +106,46 @@ describe('components/PluginManagement', () => { }, }; + const makeConflictProps = (versionDirection: string, existingVersion = '1.0.0', uploadedVersion = '2.0.0'): Record => ({ + plugin_id: 'com.mattermost.test-plugin', + plugin_name: 'Test Plugin', + existing_version: existingVersion, + uploaded_version: uploadedVersion, + version_direction: versionDirection, + }); + + const renderWithUploadConflict = async (conflictProps: Record) => { + const uploadPlugin = jest.fn().mockResolvedValueOnce({ + error: { + server_error_id: 'app.plugin.install_id.app_error', + props: conflictProps, + message: 'A plugin with this ID already exists.', + }, + }); + const file = new File(['plugin'], 'plugin.tar.gz', {type: 'application/gzip'}); + const ref = React.createRef>(); + + renderWithContext( + , + ); + + act(() => { + ref.current!.setState({file, fileSelected: true} as any); + }); + await act(async () => { + await ref.current!.helpSubmitUpload(file, false); + }); + + return {file, ref, uploadPlugin}; + }; + test('should match snapshot', () => { const props = {...defaultProps}; const {container} = renderWithContext(); @@ -577,6 +617,71 @@ describe('components/PluginManagement', () => { expect(container).toMatchSnapshot(); }); + test.each([ + ['upgrade', '1.0.0', '2.0.0', 'This upload upgrades the existing plugin.'], + ['same', '1.0.0', '1.0.0', 'This upload has the same version as the existing plugin.'], + ['downgrade', '2.0.0', '1.0.0', 'This upload downgrades the existing plugin. Downgrades can remove fixes or features.'], + ['unknown', '1.0.0', 'not-semver', 'Review the uploaded plugin before overwriting the existing installation. The server could not compare these plugin versions.'], + ])('should render overwrite review panel for %s uploads', async (direction, existingVersion, uploadedVersion, message) => { + await renderWithUploadConflict(makeConflictProps(direction, existingVersion, uploadedVersion)); + + expect(screen.getByTestId('plugin-upload-overwrite-review')).toHaveClass(`PluginUploadOverwriteReview--${direction}`); + expect(screen.getByText('Review plugin overwrite')).toBeInTheDocument(); + expect(screen.getByText(message)).toBeInTheDocument(); + expect(screen.getByText(`${existingVersion.startsWith('v') ? existingVersion : `v${existingVersion}`} \u2192 ${uploadedVersion.startsWith('v') ? uploadedVersion : `v${uploadedVersion}`}`)).toBeInTheDocument(); + expect(screen.getByText('com.mattermost.test-plugin')).toBeInTheDocument(); + expect(document.getElementById('confirmModalButton')).toBeInTheDocument(); + }); + + test('should retry upload with force when overwrite is confirmed', async () => { + const uploadPlugin = jest.fn(). + mockResolvedValueOnce({ + error: { + server_error_id: 'app.plugin.install_id.app_error', + props: makeConflictProps('upgrade'), + message: 'A plugin with this ID already exists.', + }, + }). + mockResolvedValueOnce({data: {}}); + const getPlugins = jest.fn().mockResolvedValue([]); + const file = new File(['plugin'], 'plugin.tar.gz', {type: 'application/gzip'}); + const ref = React.createRef>(); + + renderWithContext( + , + ); + + act(() => { + ref.current!.setState({file, fileSelected: true} as any); + }); + await act(async () => { + await ref.current!.helpSubmitUpload(file, false); + }); + + await userEvent.click(document.getElementById('confirmModalButton')!); + + await waitFor(() => expect(uploadPlugin).toHaveBeenLastCalledWith(file, true)); + await waitFor(() => expect(getPlugins).toHaveBeenCalled()); + expect(screen.queryByTestId('plugin-upload-overwrite-review')).not.toBeInTheDocument(); + }); + + test('should clear overwrite review when upload overwrite is cancelled', async () => { + const {uploadPlugin} = await renderWithUploadConflict(makeConflictProps('downgrade', '2.0.0', '1.0.0')); + + await userEvent.click(document.getElementById('cancelModalButton')!); + + expect(screen.queryByTestId('plugin-upload-overwrite-review')).not.toBeInTheDocument(); + expect(uploadPlugin).toHaveBeenCalledTimes(1); + }); + test('uploads the selected plugin bundle immediately', async () => { const uploadPlugin = jest.fn().mockResolvedValue({data: {}}); const getPlugins = jest.fn().mockResolvedValue({data: {}}); diff --git a/webapp/channels/src/components/admin_console/plugin_management/plugin_management.tsx b/webapp/channels/src/components/admin_console/plugin_management/plugin_management.tsx index cc6c5c7ee503..072dc7665914 100644 --- a/webapp/channels/src/components/admin_console/plugin_management/plugin_management.tsx +++ b/webapp/channels/src/components/admin_console/plugin_management/plugin_management.tsx @@ -22,6 +22,9 @@ import {appsPluginID} from 'utils/apps'; import {DeveloperLinks} from 'utils/constants'; import * as Utils from 'utils/utils'; +import type {PluginInstallConflict} from './plugin_upload_overwrite_review_modal'; +import PluginUploadOverwriteReviewModal from './plugin_upload_overwrite_review_modal'; + import BooleanSetting from '../boolean_setting'; import OLDAdminSettings from '../old_admin_settings'; import type {BaseProps, BaseState} from '../old_admin_settings'; @@ -496,6 +499,7 @@ type State = BaseState & { installing: boolean; overwritingUpload: boolean; confirmOverwriteUploadModal: boolean; + overwriteUploadConflict: PluginInstallConflict | null; overwritingInstall?: boolean; confirmOverwriteInstallModal: boolean; showRemoveModal: boolean; @@ -527,6 +531,7 @@ export class PluginManagement extends OLDAdminSettings { installing: false, overwritingUpload: false, confirmOverwriteUploadModal: false, + overwriteUploadConflict: null, overwritingInstall: false, confirmOverwriteInstallModal: false, showRemoveModal: false, @@ -622,6 +627,7 @@ export class PluginManagement extends OLDAdminSettings { serverError: null, fileSelected: true, file, + overwriteUploadConflict: null, }); this.helpSubmitUpload(file, false); }; @@ -634,6 +640,17 @@ export class PluginManagement extends OLDAdminSettings { Utils.clearFileInput(element); }; + pluginInstallConflictFromProps = (props?: Record): PluginInstallConflict => { + return { + plugin_id: props?.plugin_id, + plugin_name: props?.plugin_name, + homepage_url: props?.homepage_url, + existing_version: props?.existing_version, + uploaded_version: props?.uploaded_version, + version_direction: (props?.version_direction as PluginInstallConflict['version_direction']) || 'unknown', + }; + }; + handleUploadDragEnter = (e: React.DragEvent) => { if (!this.canUploadPlugin()) { return; @@ -691,15 +708,16 @@ export class PluginManagement extends OLDAdminSettings { }; helpSubmitUpload = async (file: File, force: boolean) => { - this.setState({uploading: true}); + this.setState({uploading: true, overwriteUploadConflict: null, serverError: null, lastMessage: null}); const {data, error} = await this.props.actions.uploadPlugin(file, force); if (error) { if (error.server_error_id === 'app.plugin.install_id.app_error' && !force) { this.setState({ confirmOverwriteUploadModal: true, - overwritingUpload: true, + overwriteUploadConflict: this.pluginInstallConflictFromProps(error.props), uploading: false, + overwritingUpload: false, }); return; } @@ -707,6 +725,8 @@ export class PluginManagement extends OLDAdminSettings { file: null, fileSelected: false, uploading: false, + overwritingUpload: false, + overwriteUploadConflict: null, }); if (error.server_error_id === 'app.plugin.activate.app_error') { this.setState({serverError: this.props.intl.formatMessage({id: 'admin.plugin.error.activate', defaultMessage: 'Unable to upload the plugin. It may conflict with another plugin on your server.'})}); @@ -734,6 +754,7 @@ export class PluginManagement extends OLDAdminSettings { serverError: null, lastMessage: msg, overwritingUpload: false, + overwriteUploadConflict: null, uploading: false, loading: false, }); @@ -745,13 +766,15 @@ export class PluginManagement extends OLDAdminSettings { fileSelected: false, serverError: null, confirmOverwriteUploadModal: false, + overwriteUploadConflict: null, lastMessage: null, uploading: false, + overwritingUpload: false, }); }; handleOverwriteUploadPlugin = () => { - this.setState({confirmOverwriteUploadModal: false}); + this.setState({confirmOverwriteUploadModal: false, overwriteUploadConflict: null, overwritingUpload: true}); if (this.state.file) { this.helpSubmitUpload(this.state.file, true); } @@ -936,39 +959,13 @@ export class PluginManagement extends OLDAdminSettings { return (); } - renderOverwritePluginModal = ( - {show, onConfirm, onCancel}: - {show: boolean; onConfirm: (checked: boolean) => void; onCancel: (checked: boolean) => void}) => { - const title = ( - - ); - - const message = ( - - ); - - const overwriteButton = ( - - ); - + renderOverwritePluginModal = () => { return ( - ); }; @@ -1239,11 +1236,7 @@ export class PluginManagement extends OLDAdminSettings { ); } - const overwriteUploadPluginModal = this.state.confirmOverwriteUploadModal && this.renderOverwritePluginModal({ - show: this.state.confirmOverwriteUploadModal, - onConfirm: this.handleOverwriteUploadPlugin, - onCancel: this.handleOverwriteUploadPluginCancel, - }); + const overwriteUploadPluginModal = this.state.confirmOverwriteUploadModal && this.renderOverwritePluginModal(); const removePluginModal = this.state.showRemoveModal && this.renderRemovePluginModal( this.state.showRemoveModal, diff --git a/webapp/channels/src/components/admin_console/plugin_management/plugin_upload_overwrite_review_modal.tsx b/webapp/channels/src/components/admin_console/plugin_management/plugin_upload_overwrite_review_modal.tsx new file mode 100644 index 000000000000..f0b4e8a610d3 --- /dev/null +++ b/webapp/channels/src/components/admin_console/plugin_management/plugin_upload_overwrite_review_modal.tsx @@ -0,0 +1,152 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react'; +import {FormattedMessage} from 'react-intl'; + +import ConfirmModal from 'components/confirm_modal'; + +import PluginMetadataPanel, {formatPluginVersion} from '../plugin_metadata_panel/plugin_metadata_panel'; + +export type PluginInstallVersionDirection = 'upgrade' | 'downgrade' | 'same' | 'unknown'; + +// Built from the plugin conflict keys in AppError.props returned on a plugin upload id conflict. +export type PluginInstallConflict = { + plugin_id?: string; + plugin_name?: string; + homepage_url?: string; + existing_version?: string; + uploaded_version?: string; + version_direction?: PluginInstallVersionDirection; +}; + +type Props = { + show: boolean; + conflict: PluginInstallConflict | null; + onConfirm: () => void; + onCancel: () => void; +}; + +const renderReviewMessage = (conflict: PluginInstallConflict | null) => { + if (!conflict) { + return null; + } + + const existingVersion = conflict.existing_version || ''; + const uploadedVersion = conflict.uploaded_version || ''; + const displayExistingVersion = formatPluginVersion(existingVersion) || ( + + ); + const displayUploadedVersion = formatPluginVersion(uploadedVersion) || ( + + ); + const direction = conflict.version_direction || 'unknown'; + const directionClassName = `PluginUploadOverwriteReview--${direction}`; + + let warningCopy = ( + + ); + if (direction === 'upgrade') { + warningCopy = ( + + ); + } else if (direction === 'same') { + warningCopy = ( + + ); + } else if (direction === 'downgrade') { + warningCopy = ( + + ); + } + + const plugin = conflict.plugin_id ? ( + + ) : null; + + return ( +
    +

    + {warningCopy} +

    +
    + {plugin && ( + <> +
    + +
    +
    {plugin}
    + + )} +
    + +
    +
    + {displayExistingVersion} + {' → '} + {displayUploadedVersion} +
    +
    +
    + ); +}; + +export default function PluginUploadOverwriteReviewModal({show, conflict, onConfirm, onCancel}: Props) { + const direction = conflict?.version_direction || 'unknown'; + const title = ( + + ); + const overwriteButton = ( + + ); + + return ( + + ); +} diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index ab546637dc6d..f4f5ba26345a 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -1263,6 +1263,12 @@ "admin.environment.notifications.contents.generic": "Send generic description with only sender name", "admin.environment.notifications.contents.help": "**Send full message contents** - Sender name and channel are included in email notifications.\n **Send generic description with only sender name** - Only the name of the person who sent the message, with no information about channel name or message contents are included in email notifications. Typically used for compliance reasons if Mattermost contains confidential information and policy dictates it cannot be stored in email.", "admin.environment.notifications.contents.label": "Email Notification Contents:", + "admin.environment.notifications.emailBatchingBufferSize.help": "Specify the maximum number of notifications batched into a single email.", + "admin.environment.notifications.emailBatchingBufferSize.label": "Email Batching Buffer Size:", + "admin.environment.notifications.emailBatchingBufferSize.placeholder": "E.g.: \"256\"", + "admin.environment.notifications.emailBatchingInterval.help": "Specify the maximum frequency, in seconds, which the batching job checks for new notifications. Longer batching intervals will increase performance.", + "admin.environment.notifications.emailBatchingInterval.label": "Email Batching Interval:", + "admin.environment.notifications.emailBatchingInterval.placeholder": "E.g.: \"30\"", "admin.environment.notifications.enable.help": "Typically set to true in production. When true, Mattermost attempts to send email notifications. When false, email invitations and user account setting change emails are still sent as long as the SMTP server is configured. Developers may set this field to false to skip email setup for faster development.", "admin.environment.notifications.enable.label": "Enable Email Notifications:", "admin.environment.notifications.enableConfirmNotificationsToChannel.help": "When true, users will be prompted to confirm when posting @channel, @all, @here and group mentions in channels with over five members. When false, no confirmation is required.", @@ -1296,6 +1302,8 @@ "admin.environment.notifications.supportEmail.help": "Email address displayed on support emails.", "admin.environment.notifications.supportEmail.label": "Support Email Address:", "admin.environment.notifications.supportEmail.required": "\"Support Email Address\" is required", + "admin.environment.notifications.useChannelInEmailNotifications.help": "When true, channel and team name appears in email notification subject lines. Useful for servers using only one team. When false, only team name appears in email notification subject line.", + "admin.environment.notifications.useChannelInEmailNotifications.label": "Use Channel Name in Email Notifications:", "admin.environment.pushNotificationServer": "Push Notification Server", "admin.environment.smtp": "SMTP", "admin.environment.smtp.connectionSecurity.option.none": "None", @@ -1344,12 +1352,6 @@ "admin.experimental.disableRefetchingOnBrowserFocus.title": "Disable data refetching on browser refocus:", "admin.experimental.disableWakeUpReconnectHandler.desc": "When true, Mattermost will not attempt to detect when the computer has woken up and refetch data. This might reduce the amount of regular network traffic the app is sending.", "admin.experimental.disableWakeUpReconnectHandler.title": "Disable Wake Up Reconnect Handler:", - "admin.experimental.emailBatchingBufferSize.desc": "Specify the maximum number of notifications batched into a single email.", - "admin.experimental.emailBatchingBufferSize.example": "E.g.: \"256\"", - "admin.experimental.emailBatchingBufferSize.title": "Email Batching Buffer Size:", - "admin.experimental.emailBatchingInterval.desc": "Specify the maximum frequency, in seconds, which the batching job checks for new notifications. Longer batching intervals will increase performance.", - "admin.experimental.emailBatchingInterval.example": "E.g.: \"30\"", - "admin.experimental.emailBatchingInterval.title": "Email Batching Interval:", "admin.experimental.enableWatermark.desc": "When true, authenticated mobile sessions will display a watermark overlay showing the username, domain, date (YYYY-MM-DD), and time (HH:mm) for data loss prevention (DLP) purposes.", "admin.experimental.enableWatermark.title": "Enable Mobile Watermark:", "admin.experimental.experimentalEnableAuthenticationTransfer.desc": "When true, users can change their sign-in method to any that is enabled on the server, either via their Profile or the APIs. When false, Users cannot change their sign-in method, regardless of which authentication options are enabled.", @@ -1379,8 +1381,6 @@ "admin.experimental.PermittedMoveThreadRoles.title": "Permitted Roles", "admin.experimental.threadAutoFollow.desc": "This setting must be enabled in order to enable Threaded Discussions. When enabled, threads a user starts, participates in, or is mentioned in are automatically followed. A new `Threads` table is added in the database that tracks threads and thread participants, and a `ThreadMembership` table tracks followed threads for each user and the read or unread state of each followed thread. When false, all backend operations to support Threaded Discussions are disabled.", "admin.experimental.threadAutoFollow.title": "Automatically Follow Threads", - "admin.experimental.useChannelInEmailNotifications.desc": "When true, channel and team name appears in email notification subject lines. Useful for servers using only one team. When false, only team name appears in email notification subject line.", - "admin.experimental.useChannelInEmailNotifications.title": "Use Channel Name in Email Notifications:", "admin.experimental.UsersStatusAndProfileFetchingPollIntervalMilliseconds.desc": "The number of milliseconds to wait between fetching user statuses and profiles periodically.", "admin.experimental.UsersStatusAndProfileFetchingPollIntervalMilliseconds.example": "E.g.: \"5000\"", "admin.experimental.UsersStatusAndProfileFetchingPollIntervalMilliseconds.title": "User's Status and Profile Fetching Poll Interval:", @@ -2248,7 +2248,7 @@ "admin.notices.enableEndUserNoticesDescription": "When enabled, all users will receive notices about available client upgrades and relevant end user features to improve user experience. Learn more about notices in our documentation.", "admin.notices.enableEndUserNoticesTitle": "Enable End User Notices: ", "admin.oauth.dcrDescription": "When true, external applications can dynamically register as OAuth 2.0 clients with Mattermost. Only enable this if you need third-party applications to register OAuth clients programmatically.", - "admin.oauth.dcrRedirectURIAllowlistDesc": "When Dynamic Client Registration is enabled, optionally restrict which redirect URIs can be registered. Enter comma-separated URL glob patterns (e.g. https://*.example.com/**). If empty, all valid redirect URIs are allowed. Wildcards are matched within URL components only: host wildcards apply to the host, path wildcards apply to the path, and query strings must be explicitly included if allowed.", + "admin.oauth.dcrRedirectURIAllowlistDesc": "When Dynamic Client Registration is enabled, optionally restrict which redirect URIs can be registered. Enter comma-separated URL glob patterns (e.g. https://*.example.com/**). Custom URI schemes used by desktop OAuth clients (e.g. cursor://app/callback) are supported in addition to http and https. If empty, all valid redirect URIs are allowed. Wildcards are matched within URL components only: host wildcards apply to the host, path wildcards apply to the path, and query strings must be explicitly included if allowed.", "admin.oauth.dcrRedirectURIAllowlistPlaceholder": "E.g.: https://*.example.com/**, https://app.example.com/callback", "admin.oauth.dcrRedirectURIAllowlistTitle": "DCR Redirect URI Allowlist:", "admin.oauth.dcrTitle": "Enable OAuth 2.0 Dynamic Client Registration: ", @@ -2823,9 +2823,15 @@ "admin.plugin.upload.disabled_signature_required": "Plugin signatures are required. Install plugins through Marketplace instead.", "admin.plugin.upload.disabled_uploads": "Plugin uploads are disabled. Enable plugin uploads in config.json before uploading a plugin.", "admin.plugin.upload.dropzone_title": "Click or drop plugin bundle to upload", - "admin.plugin.upload.overwrite_modal.desc": "A plugin with this ID already exists. Would you like to overwrite it?", - "admin.plugin.upload.overwrite_modal.overwrite": "Overwrite", - "admin.plugin.upload.overwrite_modal.title": "Overwrite existing plugin?", + "admin.plugin.upload.overwrite_review.downgrade": "This upload downgrades the existing plugin. Downgrades can remove fixes or features.", + "admin.plugin.upload.overwrite_review.overwrite": "Overwrite", + "admin.plugin.upload.overwrite_review.plugin": "Plugin", + "admin.plugin.upload.overwrite_review.same": "This upload has the same version as the existing plugin.", + "admin.plugin.upload.overwrite_review.title": "Review plugin overwrite", + "admin.plugin.upload.overwrite_review.unknown": "Review the uploaded plugin before overwriting the existing installation. The server could not compare these plugin versions.", + "admin.plugin.upload.overwrite_review.upgrade": "This upload upgrades the existing plugin.", + "admin.plugin.upload.overwrite_review.version_change": "Version change", + "admin.plugin.upload.overwrite_review.version_unknown": "Unknown", "admin.plugin.upload.progress": "Plugin upload progress", "admin.plugin.upload.selected_file": "Selected: {fileName}", "admin.plugin.upload.success": "Successfully uploaded plugin: {pluginName}", diff --git a/webapp/channels/src/sass/routes/_admin-console.scss b/webapp/channels/src/sass/routes/_admin-console.scss index b93bdd1a0b20..27472e11892a 100644 --- a/webapp/channels/src/sass/routes/_admin-console.scss +++ b/webapp/channels/src/sass/routes/_admin-console.scss @@ -8,6 +8,47 @@ overflow: auto; } +.PluginUploadOverwriteReview { + max-width: 640px; + padding: 16px; + border: 1px solid rgba(var(--button-bg-rgb), 0.16); + border-radius: 4px; + margin: 0; + background: rgba(var(--button-bg-rgb), 0.04); + + &--same, + &--unknown { + border-color: rgba(var(--away-indicator-rgb), 0.16); + background: rgba(var(--away-indicator-rgb), 0.08); + } + + &--downgrade { + border-color: rgba(var(--error-text-color-rgb), 0.16); + background: rgba(var(--error-text-color-rgb), 0.08); + } + + &__message { + margin: 0 0 16px; + } + + &__details { + display: grid; + margin-bottom: 0px; + column-gap: 24px; + grid-template-columns: max-content minmax(0, 1fr); + row-gap: 8px; + + dt { + color: rgba(var(--center-channel-color-rgb), 0.72); + font-weight: 600; + } + + dd { + margin: 0; + } + } +} + .admin-console { overflow: auto; height: 100%; diff --git a/webapp/channels/src/utils/admin_console_index.test.tsx b/webapp/channels/src/utils/admin_console_index.test.tsx index ddc4b0413e9f..20b99ba9a879 100644 --- a/webapp/channels/src/utils/admin_console_index.test.tsx +++ b/webapp/channels/src/utils/admin_console_index.test.tsx @@ -44,6 +44,15 @@ describe('AdminConsoleIndex.generateIndex', () => { 'site_config/customization', 'authentication/password', ]); + expect(idx.search('batching')).toEqual([ + 'environment/notifications', + ]); + expect(idx.search('Email Batching Buffer Size')).toEqual([ + 'environment/notifications', + ]); + expect(idx.search('subject lines')).toEqual([ + 'environment/notifications', + ]); expect(idx.search('deactivation')).toEqual([ 'authentication/saml', 'site_config/users_and_teams', diff --git a/webapp/platform/client/src/client4.ts b/webapp/platform/client/src/client4.ts index d2ffb807bfea..c1d7f16fb5ef 100644 --- a/webapp/platform/client/src/client4.ts +++ b/webapp/platform/client/src/client4.ts @@ -4929,6 +4929,7 @@ export default class Client4 { server_error_id: data.id, status_code: data.status_code, detailed_error: data.detailed_error, + props: data.props, url, }); }; @@ -5491,6 +5492,7 @@ export class ClientError extends Error implements ServerError { server_error_id?: string; status_code?: number; detailed_error?: string; + props?: Record; constructor(baseUrl: string, data: ServerError, cause?: any) { super(data.message + ': ' + cleanUrlForLogging(baseUrl, data.url || ''), {cause}); @@ -5500,6 +5502,7 @@ export class ClientError extends Error implements ServerError { this.server_error_id = data.server_error_id; this.status_code = data.status_code; this.detailed_error = data.detailed_error; + this.props = data.props; // Ensure message is treated as a property of this class when object spreading. Without this, // copying the object by using `{...error}` would not include the message. diff --git a/webapp/platform/types/src/errors.ts b/webapp/platform/types/src/errors.ts index d2a5e6285b1e..c15daccf1e01 100644 --- a/webapp/platform/types/src/errors.ts +++ b/webapp/platform/types/src/errors.ts @@ -9,4 +9,7 @@ export type ServerError = { detailed_error?: string; status_code?: number; url?: string; + + // Caller-authored context returned by the server on any AppError. Mirrors model.AppError.Props. + props?: Record; };