diff --git a/server/channels/app/platform/web_conn.go b/server/channels/app/platform/web_conn.go index 1090b7fa720..c92856eecd6 100644 --- a/server/channels/app/platform/web_conn.go +++ b/server/channels/app/platform/web_conn.go @@ -11,6 +11,7 @@ import ( "fmt" "net" "net/http" + "os" "slices" "strconv" "strings" @@ -776,6 +777,15 @@ func (wc *WebConn) createHelloMessage() *model.WebSocketEvent { wc.Platform.ClientConfigHash(), ee)) msg.Add("connection_id", wc.connectionID.Load()) + + hostname, err := os.Hostname() + if err != nil { + wc.Platform.logger.Error("Could not get hostname", mlog.Err(err)) + // return without the hostname in the message + return msg + } + + msg.Add("server_hostname", hostname) return msg } diff --git a/webapp/channels/src/actions/websocket_actions.jsx b/webapp/channels/src/actions/websocket_actions.jsx index 57e3ff5fd03..b54586c5c7f 100644 --- a/webapp/channels/src/actions/websocket_actions.jsx +++ b/webapp/channels/src/actions/websocket_actions.jsx @@ -1313,6 +1313,7 @@ export function handleStatusChangedEvent(msg) { function handleHelloEvent(msg) { dispatch(setServerVersion(msg.data.server_version)); dispatch(setConnectionId(msg.data.connection_id)); + dispatch(setServerHostname(msg.data.server_hostname)); } function handleReactionAddedEvent(msg) { @@ -1333,6 +1334,13 @@ function setConnectionId(connectionId) { }; } +function setServerHostname(serverHostname) { + return { + type: GeneralTypes.SET_SERVER_HOSTNAME, + payload: {serverHostname}, + }; +} + function handleAddEmoji(msg) { const data = JSON.parse(msg.data.emoji); diff --git a/webapp/channels/src/components/about_build_modal/about_build_modal.test.tsx b/webapp/channels/src/components/about_build_modal/about_build_modal.test.tsx index 5827c3b1a06..ab039f7284c 100644 --- a/webapp/channels/src/components/about_build_modal/about_build_modal.test.tsx +++ b/webapp/channels/src/components/about_build_modal/about_build_modal.test.tsx @@ -25,11 +25,19 @@ describe('components/AboutBuildModal', () => { let config: Partial = {}; let license: ClientLicense = {}; + let socketStatus = { + connected: false, + serverHostname: '', + }; afterEach(() => { global.Date = RealDate; config = {}; license = {}; + socketStatus = { + connected: false, + serverHostname: '', + }; }); beforeEach(() => { @@ -51,10 +59,14 @@ describe('components/AboutBuildModal', () => { IsLicensed: 'true', Company: 'Mattermost Inc', }; + socketStatus = { + connected: true, + serverHostname: 'mock.localhost', + }; }); test('should match snapshot for enterprise edition', () => { - renderAboutBuildModal({config, license}); + renderAboutBuildModal({config, license, socketStatus}); expect(screen.getByTestId('aboutModalVersion')).toHaveTextContent('Mattermost Version: 3.6.0'); expect(screen.getByTestId('aboutModalDBVersionString')).toHaveTextContent('Database Schema Version: 77'); expect(screen.getByTestId('aboutModalBuildNumber')).toHaveTextContent('Build Number: 123456'); @@ -62,6 +74,7 @@ describe('components/AboutBuildModal', () => { expect(screen.getByText('Modern communication from behind your firewall.')).toBeInTheDocument(); expect(screen.getByRole('link', {name: 'mattermost.com'})).toHaveAttribute('href', 'https://mattermost.com/?utm_source=mattermost&utm_medium=in-product&utm_content=about_build_modal&uid=&sid='); expect(screen.getByText('EE Build Hash: 0123456789abcdef', {exact: false})).toBeInTheDocument(); + expect(screen.queryByText('Hostname: mock.localhost', {exact: false})).toBeInTheDocument(); expect(screen.getByRole('link', {name: 'server'})).toHaveAttribute('href', 'https://github.com/mattermost/mattermost-server/blob/master/NOTICE.txt'); expect(screen.getByRole('link', {name: 'desktop'})).toHaveAttribute('href', 'https://github.com/mattermost/desktop/blob/master/NOTICE.txt'); @@ -75,7 +88,7 @@ describe('components/AboutBuildModal', () => { BuildHashEnterprise: '', }; - renderAboutBuildModal({config: teamConfig, license: {}}); + renderAboutBuildModal({config: teamConfig, license: {}, socketStatus: {connected: false}}); expect(screen.getByTestId('aboutModalVersion')).toHaveTextContent('Mattermost Version: 3.6.0'); expect(screen.getByTestId('aboutModalDBVersionString')).toHaveTextContent('Database Schema Version: 77'); expect(screen.getByTestId('aboutModalBuildNumber')).toHaveTextContent('Build Number: 123456'); @@ -83,6 +96,7 @@ describe('components/AboutBuildModal', () => { expect(screen.getByText('All your team communication in one place, instantly searchable and accessible anywhere.')).toBeInTheDocument(); expect(screen.getByRole('link', {name: 'mattermost.com/community/'})).toHaveAttribute('href', 'https://mattermost.com/community/?utm_source=mattermost&utm_medium=in-product&utm_content=about_build_modal&uid=&sid='); expect(screen.queryByText('EE Build Hash: 0123456789abcdef')).not.toBeInTheDocument(); + expect(screen.queryByText('Hostname: disconnected', {exact: false})).toBeInTheDocument(); expect(screen.getByRole('link', {name: 'server'})).toHaveAttribute('href', 'https://github.com/mattermost/mattermost-server/blob/master/NOTICE.txt'); expect(screen.getByRole('link', {name: 'desktop'})).toHaveAttribute('href', 'https://github.com/mattermost/desktop/blob/master/NOTICE.txt'); @@ -123,7 +137,7 @@ describe('components/AboutBuildModal', () => { BuildNumber: 'dev', }; - renderAboutBuildModal({config: sameBuildConfig, license: {}}); + renderAboutBuildModal({config: sameBuildConfig, license: {}, socketStatus: {connected: true}}); expect(screen.getByTestId('aboutModalVersion')).toHaveTextContent('Mattermost Version: dev'); expect(screen.getByTestId('aboutModalDBVersionString')).toHaveTextContent('Database Schema Version: 77'); @@ -132,6 +146,7 @@ describe('components/AboutBuildModal', () => { expect(screen.getByText('All your team communication in one place, instantly searchable and accessible anywhere.')).toBeInTheDocument(); expect(screen.getByRole('link', {name: 'mattermost.com/community/'})).toHaveAttribute('href', 'https://mattermost.com/community/?utm_source=mattermost&utm_medium=in-product&utm_content=about_build_modal&uid=&sid='); expect(screen.queryByText('EE Build Hash: 0123456789abcdef')).not.toBeInTheDocument(); + expect(screen.queryByText('Hostname: server did not provide hostname', {exact: false})).toBeInTheDocument(); expect(screen.getByRole('link', {name: 'server'})).toHaveAttribute('href', 'https://github.com/mattermost/mattermost-server/blob/master/NOTICE.txt'); expect(screen.getByRole('link', {name: 'desktop'})).toHaveAttribute('href', 'https://github.com/mattermost/desktop/blob/master/NOTICE.txt'); @@ -158,6 +173,7 @@ describe('components/AboutBuildModal', () => { , state, @@ -185,6 +201,7 @@ describe('components/AboutBuildModal', () => { , state, @@ -207,6 +224,7 @@ describe('components/AboutBuildModal', () => { onExited, config, license, + socketStatus, ...props, }; diff --git a/webapp/channels/src/components/about_build_modal/about_build_modal.tsx b/webapp/channels/src/components/about_build_modal/about_build_modal.tsx index 5d5bb754955..57aab101921 100644 --- a/webapp/channels/src/components/about_build_modal/about_build_modal.tsx +++ b/webapp/channels/src/components/about_build_modal/about_build_modal.tsx @@ -15,6 +15,11 @@ import {AboutLinks} from 'utils/constants'; import AboutBuildModalCloud from './about_build_modal_cloud/about_build_modal_cloud'; +type SocketStatus = { + connected: boolean; + serverHostname: string | undefined; +} + type Props = { /** @@ -31,6 +36,8 @@ type Props = { * Global license object */ license: ClientLicense; + + socketStatus: SocketStatus; }; type State = { @@ -182,6 +189,48 @@ export default class AboutBuildModal extends React.PureComponent { const mmversion: string | undefined = config.BuildNumber === 'dev' ? config.BuildNumber : config.Version; + let serverHostname; + if (!this.props.socketStatus.connected) { + serverHostname = ( +
+ + + +
+ ); + } else if (this.props.socketStatus.serverHostname) { + serverHostname = ( +
+ + + {this.props.socketStatus.serverHostname} +
+ ); + } else { + serverHostname = ( +
+ + + +
+ ); + } + return ( { /> {'\u00a0' + config.SQLDriverName} + {serverHostname} {licensee} diff --git a/webapp/channels/src/components/about_build_modal/index.ts b/webapp/channels/src/components/about_build_modal/index.ts index 71664588cdd..5defc4e8039 100644 --- a/webapp/channels/src/components/about_build_modal/index.ts +++ b/webapp/channels/src/components/about_build_modal/index.ts @@ -5,6 +5,8 @@ import {connect} from 'react-redux'; import {getConfig, getLicense} from 'mattermost-redux/selectors/entities/general'; +import {getSocketStatus} from 'selectors/views/websocket'; + import type {GlobalState} from 'types/store'; import AboutBuildModal from './about_build_modal'; @@ -13,6 +15,7 @@ function mapStateToProps(state: GlobalState) { return { config: getConfig(state), license: getLicense(state), + socketStatus: getSocketStatus(state), }; } diff --git a/webapp/channels/src/components/post_view/post_body_additional_content/__snapshots__/post_body_additional_content.test.tsx.snap b/webapp/channels/src/components/post_view/post_body_additional_content/__snapshots__/post_body_additional_content.test.tsx.snap index 7afaaccb562..05e879c4793 100644 --- a/webapp/channels/src/components/post_view/post_body_additional_content/__snapshots__/post_body_additional_content.test.tsx.snap +++ b/webapp/channels/src/components/post_view/post_body_additional_content/__snapshots__/post_body_additional_content.test.tsx.snap @@ -116,6 +116,7 @@ exports[`PostBodyAdditionalContent with a normal link Should render the plugin c "reconnectListeners": Set {}, "responseCallbacks": Object {}, "responseSequence": 1, + "serverHostname": "", "serverSequence": 0, } } @@ -163,6 +164,7 @@ exports[`PostBodyAdditionalContent with a normal link Should render the plugin c "reconnectListeners": Set {}, "responseCallbacks": Object {}, "responseSequence": 1, + "serverHostname": "", "serverSequence": 0, } } diff --git a/webapp/channels/src/components/product_notices_modal/product_notices.test.tsx b/webapp/channels/src/components/product_notices_modal/product_notices.test.tsx index 611560c3736..7226925fc6c 100644 --- a/webapp/channels/src/components/product_notices_modal/product_notices.test.tsx +++ b/webapp/channels/src/components/product_notices_modal/product_notices.test.tsx @@ -41,6 +41,7 @@ describe('ProductNoticesModal', () => { connectionId: '', lastConnectAt: 1599760193593, lastDisconnectAt: 0, + serverHostname: '', }, actions: { getInProductNotices: jest.fn().mockResolvedValue({data: noticesData}), diff --git a/webapp/channels/src/i18n/en.json b/webapp/channels/src/i18n/en.json index aab13955a47..2638e4396c4 100644 --- a/webapp/channels/src/i18n/en.json +++ b/webapp/channels/src/i18n/en.json @@ -14,6 +14,9 @@ "about.licensed": "Licensed to:", "about.notice": "Mattermost is made possible by the open source software used in our server, desktop and mobile apps.", "about.privacy": "Privacy Policy", + "about.serverDisconnected": "disconnected", + "about.serverHostname": "Hostname:", + "about.serverUnknown": "server did not provide hostname", "about.teamEditionLearn": "Join the Mattermost community at ", "about.teamEditionSt": "All your team communication in one place, instantly searchable and accessible anywhere.", "about.teamEditiont0": "Team Edition", diff --git a/webapp/channels/src/packages/mattermost-redux/src/action_types/general.ts b/webapp/channels/src/packages/mattermost-redux/src/action_types/general.ts index 7ec5d71b254..c94d5d6beb1 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/action_types/general.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/action_types/general.ts @@ -21,6 +21,7 @@ export default keyMirror({ WEBSOCKET_FAILURE: null, WEBSOCKET_CLOSED: null, SET_CONNECTION_ID: null, + SET_SERVER_HOSTNAME: null, SET_CONFIG_AND_LICENSE: null, diff --git a/webapp/channels/src/packages/mattermost-redux/src/reducers/websocket.ts b/webapp/channels/src/packages/mattermost-redux/src/reducers/websocket.ts index 33786531bd3..3d2012dc5eb 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/reducers/websocket.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/reducers/websocket.ts @@ -11,6 +11,7 @@ function getInitialState() { lastConnectAt: 0, lastDisconnectAt: 0, connectionId: '', + serverHostname: '', }; } @@ -26,6 +27,7 @@ export default function reducer(state = getInitialState(), action: AnyAction) { ...state, connected: false, lastDisconnectAt: action.timestamp, + serverHostname: '', }; } @@ -44,5 +46,12 @@ export default function reducer(state = getInitialState(), action: AnyAction) { }; } + if (action.type === GeneralTypes.SET_SERVER_HOSTNAME) { + return { + ...state, + serverHostname: action.payload.serverHostname, + }; + } + return state; } diff --git a/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts b/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts index a00cfd8744e..e6e63a2d965 100644 --- a/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts +++ b/webapp/channels/src/packages/mattermost-redux/src/store/initial_state.ts @@ -318,6 +318,7 @@ const state: GlobalState = { lastConnectAt: 0, lastDisconnectAt: 0, connectionId: '', + serverHostname: '', }, }; export default state; diff --git a/webapp/channels/src/plugins/pluggable/__snapshots__/pluggable.test.tsx.snap b/webapp/channels/src/plugins/pluggable/__snapshots__/pluggable.test.tsx.snap index 651bf5faa5c..cce8b71c8a7 100644 --- a/webapp/channels/src/plugins/pluggable/__snapshots__/pluggable.test.tsx.snap +++ b/webapp/channels/src/plugins/pluggable/__snapshots__/pluggable.test.tsx.snap @@ -124,6 +124,7 @@ exports[`plugins/Pluggable should match snapshot with extended component 1`] = ` "reconnectListeners": Set {}, "responseCallbacks": Object {}, "responseSequence": 1, + "serverHostname": "", "serverSequence": 0, } } @@ -262,6 +263,7 @@ exports[`plugins/Pluggable should match snapshot with extended component with pl "reconnectListeners": Set {}, "responseCallbacks": Object {}, "responseSequence": 1, + "serverHostname": "", "serverSequence": 0, } } @@ -534,6 +536,7 @@ exports[`plugins/Pluggable should match snapshot with null pluggableId 1`] = ` "reconnectListeners": Set {}, "responseCallbacks": Object {}, "responseSequence": 1, + "serverHostname": "", "serverSequence": 0, } } @@ -673,6 +676,7 @@ exports[`plugins/Pluggable should match snapshot with valid pluggableId 1`] = ` "reconnectListeners": Set {}, "responseCallbacks": Object {}, "responseSequence": 1, + "serverHostname": "", "serverSequence": 0, } } diff --git a/webapp/channels/src/sass/responsive/_mobile.scss b/webapp/channels/src/sass/responsive/_mobile.scss index a41f92576f3..48f0a79772e 100644 --- a/webapp/channels/src/sass/responsive/_mobile.scss +++ b/webapp/channels/src/sass/responsive/_mobile.scss @@ -1903,6 +1903,7 @@ .about-modal__content { display: block; + overflow-wrap: anywhere; } .about-modal__hash { diff --git a/webapp/channels/src/sass/routes/_about-modal.scss b/webapp/channels/src/sass/routes/_about-modal.scss index b9c00c841ed..4eae3be0f7f 100644 --- a/webapp/channels/src/sass/routes/_about-modal.scss +++ b/webapp/channels/src/sass/routes/_about-modal.scss @@ -57,6 +57,7 @@ display: flex; flex-direction: row; padding: 1em 0 3em; + overflow-wrap: anywhere; } .about-modal__copyright { diff --git a/webapp/platform/client/src/websocket.ts b/webapp/platform/client/src/websocket.ts index 8b97f7abe8e..bd478f291bb 100644 --- a/webapp/platform/client/src/websocket.ts +++ b/webapp/platform/client/src/websocket.ts @@ -68,6 +68,7 @@ export default class WebSocketClient { private closeListeners = new Set(); private connectionId: string | null; + private serverHostname: string | null; private postedAck: boolean; constructor() { @@ -78,6 +79,7 @@ export default class WebSocketClient { this.connectFailCount = 0; this.responseCallbacks = {}; this.connectionId = ''; + this.serverHostname = ''; this.postedAck = false; } @@ -210,6 +212,9 @@ export default class WebSocketClient { // If it's a fresh connection, we have to set the connectionId regardless. // And if it's an existing connection, setting it again is harmless, and keeps the code simple. this.connectionId = msg.data.connection_id; + + // Also update the server hostname + this.serverHostname = msg.data.server_hostname; } // Now we check for sequence number, and if it does not match, diff --git a/webapp/platform/types/src/store.ts b/webapp/platform/types/src/store.ts index ac313d12447..25d9f7eb2f3 100644 --- a/webapp/platform/types/src/store.ts +++ b/webapp/platform/types/src/store.ts @@ -94,5 +94,6 @@ export type GlobalState = { lastConnectAt: number; lastDisconnectAt: number; connectionId: string; + serverHostname: string; }; };