Skip to content
This repository was archived by the owner on May 18, 2026. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
2daac23
fix: change color if not dev
ananastii Aug 7, 2025
3431713
feat: add tam and olya to admin user names
Tabarzin Aug 11, 2025
19ce93e
feat: add tam and olya to hadrcodedadmins
Tabarzin Aug 11, 2025
7733828
refactor: change quotations
Tabarzin Aug 12, 2025
4c839ef
feat: add sorting to admin page
Tabarzin Aug 12, 2025
93de3cd
fix: restore .eslinrc
Tabarzin Aug 19, 2025
2deea14
restore stylelint
Tabarzin Aug 19, 2025
c7c5a99
restore AdminPage
Tabarzin Aug 19, 2025
d27d58c
restore users.ts
Tabarzin Aug 19, 2025
7a5e7ea
refactor: change formatting
Tabarzin Aug 20, 2025
f7f7205
feat: set dev or prod by env in Docker
ananastii Aug 25, 2025
8f0aeef
fix: separate config to env variables
ananastii Aug 25, 2025
6b4325a
fix: do not send dev or prod from server
ananastii Aug 25, 2025
c177e5a
fix: unused import
ananastii Aug 26, 2025
4e2e5c1
Merge pull request #70 from sysblok/fix/sort-admin-board-users
alexeyqu Aug 27, 2025
9accc6c
Merge pull request #69 from sysblok/fix/dev-color-sidebar
alexeyqu Aug 27, 2025
60cf5cc
fix: admins from frontend to env
ananastii Aug 25, 2025
ca3726b
fix: admins from back to env
ananastii Aug 28, 2025
a51e879
fix: pass env admins to backend
ananastii Sep 9, 2025
b0ed5dd
fix: sticky users in card details
ananastii Aug 7, 2025
3ad7af7
refactor: sticky users logic in one place
ananastii Aug 7, 2025
4b8642e
Merge pull request #68 from sysblok/feat/sticked_members
alexeyqu Sep 10, 2025
40959b1
Merge pull request #71 from sysblok/fix/env-admins
alexeyqu Sep 10, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -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* ./

Expand All @@ -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}

Expand Down
2 changes: 2 additions & 0 deletions docker/Dockerfile.back.dev
Original file line number Diff line number Diff line change
Expand Up @@ -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}

Expand Down
7 changes: 7 additions & 0 deletions docker/Dockerfile.front.dev
Original file line number Diff line number Diff line change
@@ -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
Expand Down
6 changes: 6 additions & 0 deletions docker/docker-compose-db-nginx-dev.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand All @@ -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
Expand Down
6 changes: 5 additions & 1 deletion docker/docker-compose-db-nginx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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:
Expand Down Expand Up @@ -58,7 +62,7 @@ services:
restart: "always"
depends_on:
- "proxy"
networks:
networks:
- proxy

focalboard-db:
Expand Down
2 changes: 2 additions & 0 deletions docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ services:
build:
context: ../
dockerfile: docker/Dockerfile
args:
FOCALBOARD_ENVIRONMENT: ${FOCALBOARD_ENVIRONMENT:-prod}
container_name: focalboard
volumes:
- fbdata:/opt/focalboard/data
Expand Down
2 changes: 1 addition & 1 deletion server/api/auth.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 6 additions & 8 deletions server/services/config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package config

import (
"log"
"os"

"github.com/spf13/viper"
)
Expand All @@ -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"`
Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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
Expand All @@ -121,18 +123,14 @@ 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
}

func removeSecurityData(config Configuration) Configuration {
clean := config
clean.Admins = nil
return clean
}
5 changes: 3 additions & 2 deletions webapp/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
"parser": "@typescript-eslint/parser",
"env": {
"jest": true,
"cypress/globals": true
"cypress/globals": true,
"node": true
},
"settings": {
"import/resolver": "webpack",
Expand Down Expand Up @@ -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": [
{
Expand Down
27 changes: 23 additions & 4 deletions webapp/src/components/personSelector.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'
Expand Down Expand Up @@ -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

Expand All @@ -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<IUser[]>((state) => getBoardUsersListWithSticky(state, stickedUsers));
const boardUsersRaw = useAppSelector<IUser[]>(getBoardUsersList)
const boardUsersKey = Object.keys(boardUsersById) ? Utils.hashCode(JSON.stringify(Object.keys(boardUsersById))) : 0
const me = useAppSelector<IUser|null>(getMe)

const boardUsers = useMemo(() => applyStickyUserSorting(boardUsersRaw, stickedUsers), [boardUsersRaw, stickedUsers])

const formatOptionLabel = (user: any): JSX.Element => {
if (!user) {
return <div/>
Expand Down Expand Up @@ -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'
Expand Down
5 changes: 2 additions & 3 deletions webapp/src/components/sidebar/sidebarUserMenu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand All @@ -33,7 +33,6 @@ const SidebarUserMenu = () => {
const user = useAppSelector<IUser|null>(getMe)
const isUserAdmin = useAppSelector<boolean>(isAdmin);
const intl = useIntl()
const clientConfig = useAppSelector(getClientConfig)

if (Utils.isFocalboardPlugin()) {
return <></>
Expand All @@ -45,7 +44,7 @@ const SidebarUserMenu = () => {
<div className='logo'>
<div className='logo-title'>
<FocalboardLogoIcon/>
<span>{clientConfig.featureFlags['FOCALBOARD_ENVIRONMENT'] == 'prod' ? 'Борда' : 'Focalboard'}</span>
<span>{isProduction ? 'Борда' : 'Focalboard'}</span>
<div className='versionFrame'>
<div
className='version'
Expand Down
5 changes: 5 additions & 0 deletions webapp/src/config/envConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
const environment = process.env.FOCALBOARD_ENVIRONMENT || 'dev'
export const isProduction = environment === 'prod'

export const adminsUsernamesString = process.env.FOCALBOARD_ADMINS || '';
export const adminUsernames = adminsUsernamesString ? adminsUsernamesString.split(',').map(username => username.trim()) : [];
11 changes: 5 additions & 6 deletions webapp/src/main.tsx
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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'

Expand All @@ -32,12 +32,11 @@ initThemes()

const MainApp = () => {
const me = useAppSelector<IUser|null>(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 (
Expand Down
Loading
Loading