diff --git a/docker/Dockerfile b/docker/Dockerfile index 9c98ab5d2bd..1eb82dbca2e 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,8 +1,14 @@ ### Webapp build FROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db06c7a9a4 as nodebuild + +ARG FOCALBOARD_ENVIRONMENT +ARG FOCALBOARD_ADMINS WORKDIR /webapp ENV NODE_OPTIONS=--max_old_space_size=4096 +ENV FOCALBOARD_ENVIRONMENT=$FOCALBOARD_ENVIRONMENT +ENV FOCALBOARD_ADMINS=$FOCALBOARD_ADMINS + COPY webapp/package.json webapp/package-lock.json* ./ @@ -29,9 +35,9 @@ RUN cd server && go mod download # Copy all code COPY . /go/src/focalboard -# Get target architecture +# Get target architecture ARG TARGETOS -ARG TARGETARCH +ARG TARGETARCH RUN --mount=type=cache,target="/root/.cache/go-build" EXCLUDE_PLUGIN=true EXCLUDE_SERVER=true EXCLUDE_ENTERPRISE=true make server-docker os=${TARGETOS} arch=${TARGETARCH} diff --git a/docker/Dockerfile.back.dev b/docker/Dockerfile.back.dev index b1ead28205f..7aa4bbb920f 100644 --- a/docker/Dockerfile.back.dev +++ b/docker/Dockerfile.back.dev @@ -7,6 +7,8 @@ ADD . /go/src/focalboard # Get target architecture ARG TARGETOS ARG TARGETARCH +ARG FOCALBOARD_ADMINS +ENV FOCALBOARD_ADMINS=$FOCALBOARD_ADMINS RUN --mount=type=cache,target="/root/.cache/go-build" EXCLUDE_PLUGIN=true EXCLUDE_SERVER=true EXCLUDE_ENTERPRISE=true make server-docker os=${TARGETOS} arch=${TARGETARCH} diff --git a/docker/Dockerfile.front.dev b/docker/Dockerfile.front.dev index 6aceecca489..1ca5cd4bdab 100644 --- a/docker/Dockerfile.front.dev +++ b/docker/Dockerfile.front.dev @@ -1,6 +1,13 @@ ### Webapp build FROM node:16.3.0@sha256:ca6daf1543242acb0ca59ff425509eab7defb9452f6ae07c156893db06c7a9a4 as nodebuild +# Accept build argument +ARG FOCALBOARD_ENVIRONMENT=dev +ARG FOCALBOARD_ADMINS +# Make it available to the build process and runtime +ENV FOCALBOARD_ENVIRONMENT=$FOCALBOARD_ENVIRONMENT +ENV FOCALBOARD_ADMINS=$FOCALBOARD_ADMINS + WORKDIR /webapp # package.json is required by npm at build stage # need additional setup for node_modules diff --git a/docker/docker-compose-db-nginx-dev.yml b/docker/docker-compose-db-nginx-dev.yml index 0c432668ce5..13932e626d0 100644 --- a/docker/docker-compose-db-nginx-dev.yml +++ b/docker/docker-compose-db-nginx-dev.yml @@ -6,6 +6,8 @@ services: build: context: ../ dockerfile: docker/Dockerfile.back.dev + args: + FOCALBOARD_ADMINS: ${FOCALBOARD_ADMINS:-} container_name: focalboard-back depends_on: - focalboard-db @@ -16,6 +18,7 @@ services: - VIRTUAL_HOST=${FOCALBOARD_HOST} - LETSENCRYPT_HOST=${FOCALBOARD_HOST} - FOCALBOARD_ENVIRONMENT=${FOCALBOARD_ENVIRONMENT:-dev} + - FOCALBOARD_ADMINS=${FOCALBOARD_ADMINS:-} - VIRTUAL_PORT=8000 - VIRTUAL_PROTO=http volumes: @@ -31,6 +34,9 @@ services: build: context: ../ dockerfile: docker/Dockerfile.front.dev + args: + FOCALBOARD_ENVIRONMENT: ${FOCALBOARD_ENVIRONMENT:-dev} + FOCALBOARD_ADMINS: ${FOCALBOARD_ADMINS:-} container_name: focalboard-front volumes: - ../webapp/src:/webapp/src diff --git a/docker/docker-compose-db-nginx.yml b/docker/docker-compose-db-nginx.yml index beb855a6840..99ddd779f5f 100644 --- a/docker/docker-compose-db-nginx.yml +++ b/docker/docker-compose-db-nginx.yml @@ -5,6 +5,9 @@ services: build: context: ../ dockerfile: docker/Dockerfile + args: + FOCALBOARD_ENVIRONMENT: ${FOCALBOARD_ENVIRONMENT:-prod} + FOCALBOARD_ADMINS: ${FOCALBOARD_ADMINS:-} container_name: focalboard depends_on: - focalboard-db @@ -14,6 +17,7 @@ services: - VIRTUAL_HOST=${FOCALBOARD_HOST} - LETSENCRYPT_HOST=${FOCALBOARD_HOST} - FOCALBOARD_ENVIRONMENT=${FOCALBOARD_ENVIRONMENT:-dev} + - FOCALBOARD_ADMINS=${FOCALBOARD_ADMINS:-} - VIRTUAL_PORT=8000 - VIRTUAL_PROTO=http volumes: @@ -58,7 +62,7 @@ services: restart: "always" depends_on: - "proxy" - networks: + networks: - proxy focalboard-db: diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index ed0780b8268..ba775aa47c6 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -4,6 +4,8 @@ services: build: context: ../ dockerfile: docker/Dockerfile + args: + FOCALBOARD_ENVIRONMENT: ${FOCALBOARD_ENVIRONMENT:-prod} container_name: focalboard volumes: - fbdata:/opt/focalboard/data diff --git a/server/api/auth.go b/server/api/auth.go index 35bbae1594e..c7f4d2b4943 100644 --- a/server/api/auth.go +++ b/server/api/auth.go @@ -765,7 +765,7 @@ func (a *API) isHardcodedAdmin(userID string) (bool, error) { return false, err } - hardcodedAdmins := []string{"admin", "bulgak0v", "nastasia75"} + hardcodedAdmins := a.app.GetConfig().Admins for _, adminUsername := range hardcodedAdmins { if user.Username == adminUsername { return true, nil diff --git a/server/services/config/config.go b/server/services/config/config.go index e5dcce103e6..b5b9a1f1fea 100644 --- a/server/services/config/config.go +++ b/server/services/config/config.go @@ -2,7 +2,6 @@ package config import ( "log" - "os" "github.com/spf13/viper" ) @@ -26,7 +25,7 @@ type AmazonS3Config struct { Timeout int64 } -// Configuration is the app configuration stored in a json file. +// Configuration is the app configuration stored in a json file and in env type Configuration struct { ServerRoot string `json:"serverRoot" mapstructure:"serverRoot"` Port int `json:"port" mapstructure:"port"` @@ -68,6 +67,8 @@ type Configuration struct { NotifyFreqCardSeconds int `json:"notify_freq_card_seconds" mapstructure:"notify_freq_card_seconds"` NotifyFreqBoardSeconds int `json:"notify_freq_board_seconds" mapstructure:"notify_freq_board_seconds"` + + Admins []string `mapstructure:"admins"` } // ReadConfigFile read the configuration from the filesystem. @@ -108,6 +109,7 @@ func ReadConfigFile(configFilePath string) (*Configuration, error) { viper.SetDefault("TeammateNameDisplay", "username") viper.SetDefault("ShowEmailAddress", false) viper.SetDefault("ShowFullName", false) + viper.SetDefault("Admins", []string{}) err := viper.ReadInConfig() // Find and read the config file if err != nil { // Handle errors reading the config file @@ -121,12 +123,7 @@ func ReadConfigFile(configFilePath string) (*Configuration, error) { return nil, err } - env := os.Getenv("FOCALBOARD_ENVIRONMENT") - if env != "" { - configuration.FeatureFlags["FOCALBOARD_ENVIRONMENT"] = env - } - - log.Println("readConfigFile") + log.Println("readConfigFile (sensitive data removed)") log.Printf("%+v", removeSecurityData(configuration)) return &configuration, nil @@ -134,5 +131,6 @@ func ReadConfigFile(configFilePath string) (*Configuration, error) { func removeSecurityData(config Configuration) Configuration { clean := config + clean.Admins = nil return clean } diff --git a/webapp/.eslintrc.json b/webapp/.eslintrc.json index e7b0749d999..c2fd8ffec3c 100644 --- a/webapp/.eslintrc.json +++ b/webapp/.eslintrc.json @@ -16,7 +16,8 @@ "parser": "@typescript-eslint/parser", "env": { "jest": true, - "cypress/globals": true + "cypress/globals": true, + "node": true }, "settings": { "import/resolver": "webpack", @@ -63,7 +64,7 @@ "no-only-tests/no-only-tests": ["error", {"focus": ["only", "skip"]}], "max-nested-callbacks": ["error", {"max": 5}], "no-shadow": "off", - "@typescript-eslint/no-shadow": "error" + "@typescript-eslint/no-shadow": "error" }, "overrides": [ { diff --git a/webapp/src/components/personSelector.tsx b/webapp/src/components/personSelector.tsx index 72aeaa8e87a..55b5e521958 100644 --- a/webapp/src/components/personSelector.tsx +++ b/webapp/src/components/personSelector.tsx @@ -1,7 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useCallback} from 'react' +import React, {useCallback, useMemo} from 'react' import {useIntl} from 'react-intl' import Select from 'react-select/async' import {CSSObject} from '@emotion/serialize' @@ -12,7 +12,7 @@ import {getSelectBaseStyle} from '../theme' import {IUser} from '../user' import {Utils} from '../utils' import {useAppSelector} from '../store/hooks' -import {getBoardUsers, getBoardUsersListWithSticky, getMe} from '../store/users' +import {getBoardUsers, getBoardUsersList, getMe} from '../store/users' import {ClientConfig} from '../config/clientConfig' import {getClientConfig} from '../store/clientConfig' @@ -73,6 +73,22 @@ const selectStyles = { }), } +const applyStickyUserSorting = (users: IUser[], stickyUsernames: string[]): IUser[] => { + if (stickyUsernames.length === 0) { + return users.sort((a, b) => a.username.localeCompare(b.username)) + } + + const stickyUsers = stickyUsernames + .map(username => users.find(user => user.username === username)) + .filter(Boolean) as IUser[] + + const remainingUsers = users + .filter(user => !stickyUsernames.includes(user.username)) + .sort((a, b) => a.username.localeCompare(b.username)) + + return [...stickyUsers, ...remainingUsers] +} + const PersonSelector = (props: Props): JSX.Element => { const {readOnly, userIDs, allowAddUsers, isMulti, closeMenuOnSelect = true, emptyDisplayValue, showMe = false, onChange} = props @@ -81,10 +97,12 @@ const PersonSelector = (props: Props): JSX.Element => { const stickedUsers: string[] = ['bulgak0v', 'g.-ekaterina', 'alexeyqu', 'daria_u', 'danya_s', 'kolpashchikova', 'olya_dushkina', 'michael_deev', 'diidary', 'riyatriana.rivera', 'kimihail', 'affendi', 'annaoskina2'] const boardUsersById = useAppSelector<{[key: string]: IUser}>(getBoardUsers) - const boardUsers = useAppSelector((state) => getBoardUsersListWithSticky(state, stickedUsers)); + const boardUsersRaw = useAppSelector(getBoardUsersList) const boardUsersKey = Object.keys(boardUsersById) ? Utils.hashCode(JSON.stringify(Object.keys(boardUsersById))) : 0 const me = useAppSelector(getMe) + const boardUsers = useMemo(() => applyStickyUserSorting(boardUsersRaw, stickedUsers), [boardUsersRaw, stickedUsers]) + const formatOptionLabel = (user: any): JSX.Element => { if (!user) { return
@@ -160,7 +178,8 @@ const PersonSelector = (props: Props): JSX.Element => { usersInsideBoard.push(u) } } - return usersInsideBoard; + + return applyStickyUserSorting(usersInsideBoard, stickedUsers) }, [boardUsers, allowAddUsers, boardUsersById, me]) let primaryClass = 'Person' diff --git a/webapp/src/components/sidebar/sidebarUserMenu.tsx b/webapp/src/components/sidebar/sidebarUserMenu.tsx index 7affa5dceba..dafeb4ef872 100644 --- a/webapp/src/components/sidebar/sidebarUserMenu.tsx +++ b/webapp/src/components/sidebar/sidebarUserMenu.tsx @@ -14,7 +14,7 @@ import MenuWrapper from '../../widgets/menuWrapper' import {getMe, isAdmin, setMe} from '../../store/users' import {useAppSelector, useAppDispatch} from '../../store/hooks' import {Utils} from '../../utils' -import {getClientConfig} from '../../store/clientConfig' +import {isProduction} from '../../config/envConfig' import ModalWrapper from '../modalWrapper' @@ -33,7 +33,6 @@ const SidebarUserMenu = () => { const user = useAppSelector(getMe) const isUserAdmin = useAppSelector(isAdmin); const intl = useIntl() - const clientConfig = useAppSelector(getClientConfig) if (Utils.isFocalboardPlugin()) { return <> @@ -45,7 +44,7 @@ const SidebarUserMenu = () => {
- {clientConfig.featureFlags['FOCALBOARD_ENVIRONMENT'] == 'prod' ? 'Борда' : 'Focalboard'} + {isProduction ? 'Борда' : 'Focalboard'}
username.trim()) : []; diff --git a/webapp/src/main.tsx b/webapp/src/main.tsx index d92951851bd..4b7af1f7226 100644 --- a/webapp/src/main.tsx +++ b/webapp/src/main.tsx @@ -1,6 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React from 'react' +import React, {useEffect} from 'react' import ReactDOM from 'react-dom' import {Provider as ReduxProvider} from 'react-redux' import {store as emojiMartStore} from 'emoji-mart' @@ -13,7 +13,7 @@ import {UserSettings} from './userSettings' import {IUser} from './user' import {getMe} from './store/users' import {useAppSelector} from './store/hooks' -import {getClientConfig} from './store/clientConfig' +import { isProduction } from './config/envConfig' import '@mattermost/compass-icons/css/compass-icons.css' @@ -32,12 +32,11 @@ initThemes() const MainApp = () => { const me = useAppSelector(getMe) - const clientConfig = useAppSelector(getClientConfig) - if (clientConfig.featureFlags['FOCALBOARD_ENVIRONMENT'] != 'prod') { - // TODO also set this when changing a theme - // for some reason useAppSelector doesn't work in theme.ts + if (!isProduction) { document.documentElement.style.setProperty('--sidebar-bg-rgb', '92, 50, 30') + } else { + document.documentElement.style.setProperty('--sidebar-bg-rgb', '30, 50, 92') } return ( diff --git a/webapp/src/pages/adminPage.tsx b/webapp/src/pages/adminPage.tsx index 64910b1ea31..1cf638afb75 100644 --- a/webapp/src/pages/adminPage.tsx +++ b/webapp/src/pages/adminPage.tsx @@ -4,61 +4,79 @@ import React, {useCallback, useEffect, useMemo, useState} from 'react' import './adminPage.scss' import octoClient from '../octoClient' -import {IUser} from '../user'; -import Sidebar from '../components/sidebar/sidebar'; -import BoardTemplateSelector from '../components/boardTemplateSelector/boardTemplateSelector'; -import ChangePasswordDialog from '../components/changePasswordDialog/changePasswordDialog'; -import User from '../components/user/user'; -import UserSearchForm from '../components/userSearchForm/userSearchForm'; - -import {useAppSelector} from '../store/hooks'; -import {getMe} from '../store/users'; -import {getClientConfig} from '../store/clientConfig'; -import {ClientConfig} from '../config/clientConfig'; +import {IUser} from '../user' +import Sidebar from '../components/sidebar/sidebar' +import BoardTemplateSelector from '../components/boardTemplateSelector/boardTemplateSelector' +import ChangePasswordDialog from '../components/changePasswordDialog/changePasswordDialog' +import User from '../components/user/user' +import UserSearchForm from '../components/userSearchForm/userSearchForm' + +import {useAppSelector} from '../store/hooks' +import {getMe} from '../store/users' +import {getClientConfig} from '../store/clientConfig' +import {ClientConfig} from '../config/clientConfig' const AdminPage = () => { const clientConfig = useAppSelector(getClientConfig) - const me = useAppSelector(getMe) - const [users, setUsers] = useState([]); + const me = useAppSelector(getMe) + const [users, setUsers] = useState([]) const [searchTerm, setSearchTerm] = useState('') - const [boardTemplateSelectorOpen, setBoardTemplateSelectorOpen] = useState(false) - const [changePasswordUser, setChangePasswordUser] = useState(null); - const [errorMessage, setErrorMessage] = useState<{messageId: string, defaultMessage: string} | null>(null) - const [successMessage, setSuccessMessage] = useState<{messageId: string, defaultMessage: string} | null>(null) + const [boardTemplateSelectorOpen, setBoardTemplateSelectorOpen] = + useState(false) + const [changePasswordUser, setChangePasswordUser] = useState( + null, + ) + const [errorMessage, setErrorMessage] = useState<{ + messageId: string + defaultMessage: string + } | null>(null) + const [successMessage, setSuccessMessage] = useState<{ + messageId: string + defaultMessage: string + } | null>(null) const [isSubmitting, setIsSubmitting] = useState(false) const fetchUsers = async () => { - const result = await octoClient.getTeamUsers(); + const result = await octoClient.getTeamUsers() setUsers(result) - }; + } useEffect(() => { - fetchUsers(); - }, []); + fetchUsers() + }, []) const filteredUsers = useMemo(() => { if (!searchTerm.trim()) { - return users + return [...users].sort((a, b) => + a.username. + trim(). + toLowerCase(). + localeCompare(b.username.trim().toLowerCase(), 'en', { + sensitivity: 'base', + }), + ) } const lowercaseSearch = searchTerm.toLowerCase().trim() - return users.filter(user => { - // Search in email (if available) - const emailMatch = user.email?.toLowerCase().includes(lowercaseSearch) - - // Search in nickname - const nicknameMatch = user.nickname?.toLowerCase().includes(lowercaseSearch) - - // Search in username as fallback - const usernameMatch = user.username?.toLowerCase().includes(lowercaseSearch) - - // Search in first/last name - const firstNameMatch = user.firstname?.toLowerCase().includes(lowercaseSearch) - const lastNameMatch = user.lastname?.toLowerCase().includes(lowercaseSearch) - - return emailMatch || nicknameMatch || usernameMatch || firstNameMatch || lastNameMatch + const arr = users.filter((user) => { + return ( + user.email?.toLowerCase().includes(lowercaseSearch) || + user.nickname?.toLowerCase().includes(lowercaseSearch) || + user.username?.toLowerCase().includes(lowercaseSearch) || + user.firstname?.toLowerCase().includes(lowercaseSearch) || + user.lastname?.toLowerCase().includes(lowercaseSearch) + ) }) + + return arr.sort((a, b) => + a.username. + trim(). + toLowerCase(). + localeCompare(b.username.trim().toLowerCase(), 'en', { + sensitivity: 'base', + }), + ) }, [users, searchTerm]) const handleSearchChange = useCallback((value: string) => { @@ -82,39 +100,44 @@ const AdminPage = () => { setErrorMessage(null) setSuccessMessage(null) setIsSubmitting(false) - setChangePasswordUser(null); + setChangePasswordUser(null) }, []) - const handleChangePassword = useCallback(async (userId: string, newPassword: string) => { - - try { - const success = await octoClient.changeUserPassword(userId, newPassword) - - if (success) { - console.log('success') - setSuccessMessage({ - messageId: 'change-password.success', - defaultMessage: 'Password changed successfully' - }) - setErrorMessage(null) - - } else { + const handleChangePassword = useCallback( + async (userId: string, newPassword: string) => { + try { + const success = await octoClient.changeUserPassword( + userId, + newPassword, + ) + + if (success) { + console.log('success') + setSuccessMessage({ + messageId: 'change-password.success', + defaultMessage: 'Password changed successfully', + }) + setErrorMessage(null) + } else { + setErrorMessage({ + messageId: 'change-password.failed', + defaultMessage: + 'Failed to change password. Please try again.', + }) + setSuccessMessage(null) + } + } catch (error) { setErrorMessage({ - messageId: 'change-password.failed', - defaultMessage: 'Failed to change password. Please try again.' + messageId: 'change-password.error', + defaultMessage: 'An error occurred while changing password', }) setSuccessMessage(null) + } finally { + setIsSubmitting(false) } - } catch (error) { - setErrorMessage({ - messageId: 'change-password.error', - defaultMessage: 'An error occurred while changing password' - }) - setSuccessMessage(null) - } finally { - setIsSubmitting(false) - } - }, [closeChangePasswordDialog]); + }, + [closeChangePasswordDialog], + ) return (
@@ -123,32 +146,38 @@ const AdminPage = () => { onBoardTemplateSelectorClose={closeBoardTemplateSelector} />
- {boardTemplateSelectorOpen && - - } -

Team Users

+ {boardTemplateSelectorOpen && ( + + )} +

{'Team Users'}

{users.length > 0 && (

- {searchTerm - ? `Showing ${filteredUsers.length} of ${users.length} users` - : `Total users: ${users.length}` - } + {searchTerm ? `Showing ${filteredUsers.length} of ${users.length} users` : `Total users: ${users.length}`}

)}
    {filteredUsers.map((user) => ( -
  • +
  • openChangePasswordDialog(user)} + onChangePassword={() => + openChangePasswordDialog(user) + } />
  • ))} @@ -156,8 +185,10 @@ const AdminPage = () => { {searchTerm && filteredUsers.length === 0 && (
    -

    No users found matching "{searchTerm}"

    -

    Try searching by email, nickname, username, or name.

    +

    {'No users found matching \'' + searchTerm + '\''}

    +

    + {'Try searching by email, nickname, username, or name.'} +

    )} @@ -173,7 +204,7 @@ const AdminPage = () => { )}
- ); -}; + ) +} export default React.memo(AdminPage) diff --git a/webapp/src/store/users.ts b/webapp/src/store/users.ts index 8342bce146a..4ffe23587c2 100644 --- a/webapp/src/store/users.ts +++ b/webapp/src/store/users.ts @@ -5,6 +5,7 @@ import {createSlice, createAsyncThunk, PayloadAction, createSelector} from '@red import {default as client} from '../octoClient' import {IUser, parseUserProps, UserPreference} from '../user' +import {adminUsernames} from '../config/envConfig' import {Utils} from '../utils' @@ -131,25 +132,6 @@ export const getBoardUsersList = createSelector( (boardUsers) => Object.values(boardUsers).sort((a, b) => a.username.localeCompare(b.username)), ) -export const getBoardUsersListWithSticky = createSelector( - [getBoardUsers, (state: RootState, stickedUsernames: string[]) => stickedUsernames], - (boardUsers, stickedUsernames) => { - const allUsers = Object.values(boardUsers); - - // Get sticked users in their fixed order - const stickedUsers = stickedUsernames - .map(username => allUsers.find(user => user.username === username)) - .filter(Boolean) as IUser[]; - - // Get remaining users and sort alphabetically - const remainingUsers = allUsers - .filter(user => !stickedUsernames.includes(user.username)) - .sort((a, b) => a.username.localeCompare(b.username)); - - return [...stickedUsers, ...remainingUsers]; - } -); - export const getUser = (userId: string): (state: RootState) => IUser|undefined => { return (state: RootState): IUser|undefined => { const users = getBoardUsers(state) @@ -230,7 +212,5 @@ export const isAdmin = createSelector( getMe, (user): boolean => { if (!user) return false; - const adminUsernames = ['admin', 'bulgak0v', 'nastasia75']; - - return adminUsernames.includes(user.username); -}) + return adminUsernames.includes(user.username) + }) diff --git a/webapp/src/styles/shared-variables.scss b/webapp/src/styles/shared-variables.scss index 8b5c9a4e5a7..c87908a645f 100644 --- a/webapp/src/styles/shared-variables.scss +++ b/webapp/src/styles/shared-variables.scss @@ -2,7 +2,6 @@ --center-channel-bg-rgb: 255, 255, 255; --center-channel-color-rgb: 63, 67, 80; --sidebar-bg-rgb: 30, 50, 92; - --sidebar-bg-dev: 92, 50, 30; --sidebar-text-rgb: 255, 255, 255; --button-color-rgb: 255, 255, 255; --button-bg-rgb: 28, 88, 217; diff --git a/webapp/webpack.common.js b/webapp/webpack.common.js index b003265cec6..16ce7d6e313 100644 --- a/webapp/webpack.common.js +++ b/webapp/webpack.common.js @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. const path = require('path'); +const webpack = require('webpack'); const tsTransformer = require('@formatjs/ts-transformer'); const CopyPlugin = require('copy-webpack-plugin'); @@ -8,6 +9,8 @@ var HtmlWebpackPlugin = require('html-webpack-plugin'); const outpath = path.resolve(__dirname, 'pack'); +console.log('🔧 FOCALBOARD_ENVIRONMENT from Docker:', process.env.FOCALBOARD_ENVIRONMENT); + function makeCommonConfig() { const commonConfig = { target: 'web', @@ -99,6 +102,10 @@ function makeCommonConfig() { publicPath: '{{.BaseURL}}/', hash: true, }), + new webpack.DefinePlugin({ + 'process.env.FOCALBOARD_ENVIRONMENT': JSON.stringify(process.env.FOCALBOARD_ENVIRONMENT || 'dev'), + 'process.env.FOCALBOARD_ADMINS': JSON.stringify(process.env.FOCALBOARD_ADMINS || ''), + }), ], entry: ['./src/main.tsx', './src/userSettings.ts'], output: {