feat: Add internal runtime configuration
This commit is contained in:
Vendored
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ActionTypes from '../constants/ActionTypes';
|
||||||
|
|
||||||
|
const handleBootstrapUpdate = (bootstrap) => ({
|
||||||
|
type: ActionTypes.BOOTSTRAP_UPDATE_HANDLE,
|
||||||
|
payload: {
|
||||||
|
bootstrap,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
handleBootstrapUpdate,
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import bootstrap from './bootstrap';
|
||||||
import login from './login';
|
import login from './login';
|
||||||
import core from './core';
|
import core from './core';
|
||||||
import modals from './modals';
|
import modals from './modals';
|
||||||
@@ -34,6 +35,7 @@ import notificationServices from './notification-services';
|
|||||||
export default {
|
export default {
|
||||||
...router,
|
...router,
|
||||||
...socket,
|
...socket,
|
||||||
|
...bootstrap,
|
||||||
...login,
|
...login,
|
||||||
...core,
|
...core,
|
||||||
...modals,
|
...modals,
|
||||||
|
|||||||
@@ -5,6 +5,13 @@
|
|||||||
|
|
||||||
import ActionTypes from '../constants/ActionTypes';
|
import ActionTypes from '../constants/ActionTypes';
|
||||||
|
|
||||||
|
const handleUsersReset = (users) => ({
|
||||||
|
type: ActionTypes.USERS_RESET_HANDLE,
|
||||||
|
payload: {
|
||||||
|
users,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const createUser = (data) => ({
|
const createUser = (data) => ({
|
||||||
type: ActionTypes.USER_CREATE,
|
type: ActionTypes.USER_CREATE,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -399,6 +406,7 @@ const removeUserFromBoardFilter = (id, boardId, currentListId) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
handleUsersReset,
|
||||||
createUser,
|
createUser,
|
||||||
handleUserCreate,
|
handleUserCreate,
|
||||||
clearUserCreateError,
|
clearUserCreateError,
|
||||||
|
|||||||
@@ -38,8 +38,8 @@ const StepTypes = {
|
|||||||
const ActionsStep = React.memo(({ userId, onClose }) => {
|
const ActionsStep = React.memo(({ userId, onClose }) => {
|
||||||
const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);
|
const selectUserById = useMemo(() => selectors.makeSelectUserById(), []);
|
||||||
|
|
||||||
const activeUserLimit = useSelector(selectors.selectActiveUserLimit);
|
const activeUsersLimit = useSelector(selectors.selectActiveUsersLimit);
|
||||||
const activeUserTotal = useSelector(selectors.selectActiveUserTotal);
|
const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);
|
||||||
const user = useSelector((state) => selectUserById(state, userId));
|
const user = useSelector((state) => selectUserById(state, userId));
|
||||||
const isCurrentUser = useSelector((state) => user.id === selectors.selectCurrentUserId(state));
|
const isCurrentUser = useSelector((state) => user.id === selectors.selectCurrentUserId(state));
|
||||||
|
|
||||||
@@ -237,8 +237,8 @@ const ActionsStep = React.memo(({ userId, onClose }) => {
|
|||||||
<Menu.Item
|
<Menu.Item
|
||||||
disabled={
|
disabled={
|
||||||
user.isDeactivated &&
|
user.isDeactivated &&
|
||||||
activeUserLimit !== null &&
|
activeUsersLimit !== null &&
|
||||||
activeUserTotal >= activeUserLimit
|
activeUsersTotal >= activeUsersLimit
|
||||||
}
|
}
|
||||||
className={styles.menuItem}
|
className={styles.menuItem}
|
||||||
onClick={user.isDeactivated ? handleActivateClick : handleDeactivateClick}
|
onClick={user.isDeactivated ? handleActivateClick : handleDeactivateClick}
|
||||||
|
|||||||
@@ -17,8 +17,8 @@ import AddStep from './AddStep';
|
|||||||
import styles from './UsersPane.module.scss';
|
import styles from './UsersPane.module.scss';
|
||||||
|
|
||||||
const UsersPane = React.memo(() => {
|
const UsersPane = React.memo(() => {
|
||||||
const activeUserTotal = useSelector(selectors.selectActiveUserTotal);
|
const activeUsersLimit = useSelector(selectors.selectActiveUsersLimit);
|
||||||
const activeUserLimit = useSelector(selectors.selectActiveUserLimit);
|
const activeUsersTotal = useSelector(selectors.selectActiveUsersTotal);
|
||||||
const users = useSelector(selectors.selectUsers);
|
const users = useSelector(selectors.selectUsers);
|
||||||
|
|
||||||
const canAdd = useSelector((state) => {
|
const canAdd = useSelector((state) => {
|
||||||
@@ -106,13 +106,13 @@ const UsersPane = React.memo(() => {
|
|||||||
<AddPopup>
|
<AddPopup>
|
||||||
<Button
|
<Button
|
||||||
positive
|
positive
|
||||||
disabled={activeUserLimit !== null && activeUserTotal >= activeUserLimit}
|
disabled={activeUsersLimit !== null && activeUsersTotal >= activeUsersLimit}
|
||||||
className={styles.addButton}
|
className={styles.addButton}
|
||||||
>
|
>
|
||||||
{t('action.addUser')}
|
{t('action.addUser')}
|
||||||
{activeUserLimit !== null && (
|
{activeUsersLimit !== null && (
|
||||||
<span className={styles.addButtonCounter}>
|
<span className={styles.addButtonCounter}>
|
||||||
{activeUserTotal}/{activeUserLimit}
|
{activeUsersTotal}/{activeUsersLimit}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</Button>
|
</Button>
|
||||||
|
|||||||
@@ -64,10 +64,10 @@ const createMessage = (error) => {
|
|||||||
type: 'error',
|
type: 'error',
|
||||||
content: 'common.usernameAlreadyInUse',
|
content: 'common.usernameAlreadyInUse',
|
||||||
};
|
};
|
||||||
case 'Active user limit reached':
|
case 'Active users limit reached':
|
||||||
return {
|
return {
|
||||||
type: 'error',
|
type: 'error',
|
||||||
content: 'common.activeUserLimitReached',
|
content: 'common.activeUsersLimitReached',
|
||||||
};
|
};
|
||||||
case 'Failed to fetch':
|
case 'Failed to fetch':
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ export default {
|
|||||||
SOCKET_RECONNECT_HANDLE: 'SOCKET_RECONNECT_HANDLE',
|
SOCKET_RECONNECT_HANDLE: 'SOCKET_RECONNECT_HANDLE',
|
||||||
SOCKET_RECONNECT_HANDLE__CORE_FETCH: 'SOCKET_RECONNECT_HANDLE__CORE_FETCH',
|
SOCKET_RECONNECT_HANDLE__CORE_FETCH: 'SOCKET_RECONNECT_HANDLE__CORE_FETCH',
|
||||||
|
|
||||||
|
/* Bootstrap */
|
||||||
|
|
||||||
|
BOOTSTRAP_UPDATE_HANDLE: 'BOOTSTRAP_UPDATE_HANDLE',
|
||||||
|
|
||||||
/* Login */
|
/* Login */
|
||||||
|
|
||||||
LOGIN_INITIALIZE: 'LOGIN_INITIALIZE',
|
LOGIN_INITIALIZE: 'LOGIN_INITIALIZE',
|
||||||
@@ -78,6 +82,7 @@ export default {
|
|||||||
|
|
||||||
/* Users */
|
/* Users */
|
||||||
|
|
||||||
|
USERS_RESET_HANDLE: 'USERS_RESET_HANDLE',
|
||||||
USER_CREATE: 'USER_CREATE',
|
USER_CREATE: 'USER_CREATE',
|
||||||
USER_CREATE__SUCCESS: 'USER_CREATE__SUCCESS',
|
USER_CREATE__SUCCESS: 'USER_CREATE__SUCCESS',
|
||||||
USER_CREATE__FAILURE: 'USER_CREATE__FAILURE',
|
USER_CREATE__FAILURE: 'USER_CREATE__FAILURE',
|
||||||
|
|||||||
@@ -13,6 +13,10 @@ export default {
|
|||||||
SOCKET_DISCONNECT_HANDLE: `${PREFIX}/SOCKET_DISCONNECT_HANDLE`,
|
SOCKET_DISCONNECT_HANDLE: `${PREFIX}/SOCKET_DISCONNECT_HANDLE`,
|
||||||
SOCKET_RECONNECT_HANDLE: `${PREFIX}/SOCKET_RECONNECT_HANDLE`,
|
SOCKET_RECONNECT_HANDLE: `${PREFIX}/SOCKET_RECONNECT_HANDLE`,
|
||||||
|
|
||||||
|
/* Bootstrap */
|
||||||
|
|
||||||
|
BOOTSTRAP_UPDATE_HANDLE: `${PREFIX}/BOOTSTRAP_UPDATE_HANDLE`,
|
||||||
|
|
||||||
/* Login */
|
/* Login */
|
||||||
|
|
||||||
AUTHENTICATE: `${PREFIX}/AUTHENTICATE`,
|
AUTHENTICATE: `${PREFIX}/AUTHENTICATE`,
|
||||||
@@ -51,6 +55,7 @@ export default {
|
|||||||
|
|
||||||
/* Users */
|
/* Users */
|
||||||
|
|
||||||
|
USERS_RESET_HANDLE: `${PREFIX}/USERS_RESET_HANDLE`,
|
||||||
USER_CREATE: `${PREFIX}/USER_CREATE`,
|
USER_CREATE: `${PREFIX}/USER_CREATE`,
|
||||||
USER_CREATE_HANDLE: `${PREFIX}/USER_CREATE_HANDLE`,
|
USER_CREATE_HANDLE: `${PREFIX}/USER_CREATE_HANDLE`,
|
||||||
USER_CREATE_ERROR_CLEAR: `${PREFIX}/USER_CREATE_ERROR_CLEAR`,
|
USER_CREATE_ERROR_CLEAR: `${PREFIX}/USER_CREATE_ERROR_CLEAR`,
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import EntryActionTypes from '../constants/EntryActionTypes';
|
||||||
|
|
||||||
|
const handleBootstrapUpdate = (bootstrap) => ({
|
||||||
|
type: EntryActionTypes.BOOTSTRAP_UPDATE_HANDLE,
|
||||||
|
payload: {
|
||||||
|
bootstrap,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export default {
|
||||||
|
handleBootstrapUpdate,
|
||||||
|
};
|
||||||
@@ -4,6 +4,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import bootstrap from './bootstrap';
|
||||||
import login from './login';
|
import login from './login';
|
||||||
import core from './core';
|
import core from './core';
|
||||||
import modals from './modals';
|
import modals from './modals';
|
||||||
@@ -32,6 +33,7 @@ import notificationServices from './notification-services';
|
|||||||
|
|
||||||
export default {
|
export default {
|
||||||
...socket,
|
...socket,
|
||||||
|
...bootstrap,
|
||||||
...login,
|
...login,
|
||||||
...core,
|
...core,
|
||||||
...modals,
|
...modals,
|
||||||
|
|||||||
@@ -5,6 +5,11 @@
|
|||||||
|
|
||||||
import EntryActionTypes from '../constants/EntryActionTypes';
|
import EntryActionTypes from '../constants/EntryActionTypes';
|
||||||
|
|
||||||
|
const handleUsersReset = () => ({
|
||||||
|
type: EntryActionTypes.USERS_RESET_HANDLE,
|
||||||
|
payload: {},
|
||||||
|
});
|
||||||
|
|
||||||
const createUser = (data) => ({
|
const createUser = (data) => ({
|
||||||
type: EntryActionTypes.USER_CREATE,
|
type: EntryActionTypes.USER_CREATE,
|
||||||
payload: {
|
payload: {
|
||||||
@@ -246,6 +251,7 @@ const removeUserFromFilterInCurrentBoard = (id) => ({
|
|||||||
});
|
});
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
handleUsersReset,
|
||||||
createUser,
|
createUser,
|
||||||
handleUserCreate,
|
handleUserCreate,
|
||||||
clearUserCreateError,
|
clearUserCreateError,
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'تم الوصول إلى حد المستخدمين النشطين',
|
activeUsersLimitReached: 'تم الوصول إلى حد المستخدمين النشطين',
|
||||||
adminLoginRequiredToInitializeInstance: 'مطلوب تسجيل دخول المدير لتهيئة المثيل',
|
adminLoginRequiredToInitializeInstance: 'مطلوب تسجيل دخول المدير لتهيئة المثيل',
|
||||||
emailAlreadyInUse: 'البريد الإلكتروني مستخدم بالفعل',
|
emailAlreadyInUse: 'البريد الإلكتروني مستخدم بالفعل',
|
||||||
emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',
|
emailOrUsername: 'البريد الإلكتروني أو اسم المستخدم',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Достигнат е лимитът на активни потребители',
|
activeUsersLimitReached: 'Достигнат е лимитът на активни потребители',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Необходимо е влизане на администратор за инициализиране на инстанцията',
|
'Необходимо е влизане на администратор за инициализиране на инстанцията',
|
||||||
emailAlreadyInUse: 'Имейлът вече се използва',
|
emailAlreadyInUse: 'Имейлът вече се използва',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: "S'ha assolit el límit d'usuaris actius",
|
activeUsersLimitReached: "S'ha assolit el límit d'usuaris actius",
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
"Es requereix inici de sessió d'administrador per inicialitzar la instància",
|
"Es requereix inici de sessió d'administrador per inicialitzar la instància",
|
||||||
emailAlreadyInUse: 'Correu electrònic ja en ús',
|
emailAlreadyInUse: 'Correu electrònic ja en ús',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Dosažený limit aktivních uživatelů',
|
activeUsersLimitReached: 'Dosažený limit aktivních uživatelů',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'K inicializaci instance je nutné přihlášení správce.',
|
'K inicializaci instance je nutné přihlášení správce.',
|
||||||
emailAlreadyInUse: 'E-mail se již používá',
|
emailAlreadyInUse: 'E-mail se již používá',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Grænsen for aktive brugere er nået',
|
activeUsersLimitReached: 'Grænsen for aktive brugere er nået',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Administrator login påkrævet for at initialisere instans',
|
'Administrator login påkrævet for at initialisere instans',
|
||||||
emailAlreadyInUse: 'E-mail allerede i brug',
|
emailAlreadyInUse: 'E-mail allerede i brug',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Maximale Anzahl aktiver Benutzer erreicht',
|
activeUsersLimitReached: 'Maximale Anzahl aktiver Benutzer erreicht',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Admin-Anmeldung erforderlich zur Initialisierung der Instanz',
|
'Admin-Anmeldung erforderlich zur Initialisierung der Instanz',
|
||||||
emailAlreadyInUse: 'E-mail Adresse wird bereits benutzt',
|
emailAlreadyInUse: 'E-mail Adresse wird bereits benutzt',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Έχει επιτευχθεί το όριο ενεργών χρηστών',
|
activeUsersLimitReached: 'Έχει επιτευχθεί το όριο ενεργών χρηστών',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Απαιτείται σύνδεση διαχειριστή για την αρχικοποίηση της εφαρμογής',
|
'Απαιτείται σύνδεση διαχειριστή για την αρχικοποίηση της εφαρμογής',
|
||||||
emailAlreadyInUse: 'Το e-mail χρησιμοποιείται ήδη',
|
emailAlreadyInUse: 'Το e-mail χρησιμοποιείται ήδη',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Active user limit reached',
|
activeUsersLimitReached: 'Active users limit reached',
|
||||||
adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',
|
adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',
|
||||||
emailAlreadyInUse: 'E-mail already in use',
|
emailAlreadyInUse: 'E-mail already in use',
|
||||||
emailOrUsername: 'E-mail or username',
|
emailOrUsername: 'E-mail or username',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Active user limit reached',
|
activeUsersLimitReached: 'Active users limit reached',
|
||||||
adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',
|
adminLoginRequiredToInitializeInstance: 'Admin login required to initialize instance',
|
||||||
emailAlreadyInUse: 'E-mail already in use',
|
emailAlreadyInUse: 'E-mail already in use',
|
||||||
emailOrUsername: 'E-mail or username',
|
emailOrUsername: 'E-mail or username',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Se ha alcanzado el límite de usuarios activos',
|
activeUsersLimitReached: 'Se ha alcanzado el límite de usuarios activos',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Se requiere inicio de sesión de administrador para inicializar la instancia',
|
'Se requiere inicio de sesión de administrador para inicializar la instancia',
|
||||||
emailAlreadyInUse: 'Correo electrónico ya en uso',
|
emailAlreadyInUse: 'Correo electrónico ya en uso',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Aktiivsete kasutajate limiit on täis',
|
activeUsersLimitReached: 'Aktiivsete kasutajate limiit on täis',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Administraatori sisselogimine on vajalik rakenduse käivitamiseks',
|
'Administraatori sisselogimine on vajalik rakenduse käivitamiseks',
|
||||||
emailAlreadyInUse: 'E-post on juba kasutusel',
|
emailAlreadyInUse: 'E-post on juba kasutusel',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'حد کاربران فعال به پایان رسیده است',
|
activeUsersLimitReached: 'حد کاربران فعال به پایان رسیده است',
|
||||||
adminLoginRequiredToInitializeInstance: 'ورود مدیر برای راهاندازی نمونه مورد نیاز است',
|
adminLoginRequiredToInitializeInstance: 'ورود مدیر برای راهاندازی نمونه مورد نیاز است',
|
||||||
emailAlreadyInUse: 'ایمیل قبلا استفاده شده است',
|
emailAlreadyInUse: 'ایمیل قبلا استفاده شده است',
|
||||||
emailOrUsername: 'ایمیل یا نام کاربری',
|
emailOrUsername: 'ایمیل یا نام کاربری',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Aktiivisten käyttäjien raja saavutettu',
|
activeUsersLimitReached: 'Aktiivisten käyttäjien raja saavutettu',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Järjestelmänvalvojan kirjautuminen vaaditaan instanssin alustamiseksi',
|
'Järjestelmänvalvojan kirjautuminen vaaditaan instanssin alustamiseksi',
|
||||||
emailAlreadyInUse: 'Sähköposti on jo käytössä',
|
emailAlreadyInUse: 'Sähköposti on jo käytössä',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'La limite d’utilisateurs actifs a été atteinte',
|
activeUsersLimitReached: 'La limite d’utilisateurs actifs a été atteinte',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
"Connexion administrateur requise pour initialiser l'instance",
|
"Connexion administrateur requise pour initialiser l'instance",
|
||||||
emailAlreadyInUse: 'E-mail déjà utilisé',
|
emailAlreadyInUse: 'E-mail déjà utilisé',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Aktív felhasználók korlátja elérve',
|
activeUsersLimitReached: 'Aktív felhasználók korlátja elérve',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Rendszergazdai bejelentkezés szükséges a példány inicializálásához',
|
'Rendszergazdai bejelentkezés szükséges a példány inicializálásához',
|
||||||
emailAlreadyInUse: 'Az e-mail cím már használatban van',
|
emailAlreadyInUse: 'Az e-mail cím már használatban van',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Batas pengguna aktif tercapai',
|
activeUsersLimitReached: 'Batas pengguna aktif tercapai',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Login admin diperlukan untuk menginisialisasi instance',
|
'Login admin diperlukan untuk menginisialisasi instance',
|
||||||
emailAlreadyInUse: 'E-mail telah digunakan',
|
emailAlreadyInUse: 'E-mail telah digunakan',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Limite utenti attivi raggiunto',
|
activeUsersLimitReached: 'Limite utenti attivi raggiunto',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
"Login amministratore richiesto per inizializzare l'istanza",
|
"Login amministratore richiesto per inizializzare l'istanza",
|
||||||
emailAlreadyInUse: 'E-mail già in uso',
|
emailAlreadyInUse: 'E-mail già in uso',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'アクティブユーザーの上限に達しました',
|
activeUsersLimitReached: 'アクティブユーザーの上限に達しました',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'インスタンスを初期化するには管理者ログインが必要です',
|
'インスタンスを初期化するには管理者ログインが必要です',
|
||||||
emailAlreadyInUse: 'Eメールは既に使われています',
|
emailAlreadyInUse: 'Eメールは既に使われています',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: '활성 사용자 한도에 도달했습니다',
|
activeUsersLimitReached: '활성 사용자 한도에 도달했습니다',
|
||||||
adminLoginRequiredToInitializeInstance: '인스턴스 초기화를 위해 관리자 로그인이 필요합니다',
|
adminLoginRequiredToInitializeInstance: '인스턴스 초기화를 위해 관리자 로그인이 필요합니다',
|
||||||
emailAlreadyInUse: '이미 사용 중인 이메일',
|
emailAlreadyInUse: '이미 사용 중인 이메일',
|
||||||
emailOrUsername: '이메일 또는 사용자 이름',
|
emailOrUsername: '이메일 또는 사용자 이름',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Limiet voor actieve gebruikers bereikt',
|
activeUsersLimitReached: 'Limiet voor actieve gebruikers bereikt',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Beheerder login vereist om instantie te initialiseren',
|
'Beheerder login vereist om instantie te initialiseren',
|
||||||
emailAlreadyInUse: 'E-mail is al in gebruik',
|
emailAlreadyInUse: 'E-mail is al in gebruik',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Osiągnięto limit aktywnych użytkowników',
|
activeUsersLimitReached: 'Osiągnięto limit aktywnych użytkowników',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Wymagane logowanie administratora do inicjalizacji instancji',
|
'Wymagane logowanie administratora do inicjalizacji instancji',
|
||||||
emailAlreadyInUse: 'E-mail jest już używany',
|
emailAlreadyInUse: 'E-mail jest już używany',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Limite de usuários ativos atingido',
|
activeUsersLimitReached: 'Limite de usuários ativos atingido',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Login de administrador necessário para inicializar a instância',
|
'Login de administrador necessário para inicializar a instância',
|
||||||
emailAlreadyInUse: 'E-mail já está em uso',
|
emailAlreadyInUse: 'E-mail já está em uso',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Limite de utilizadores activos atingido',
|
activeUsersLimitReached: 'Limite de utilizadores activos atingido',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Início de sessão de administrador necessário para inicializar a instância',
|
'Início de sessão de administrador necessário para inicializar a instância',
|
||||||
emailAlreadyInUse: 'E-mail já está em uso',
|
emailAlreadyInUse: 'E-mail já está em uso',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Limita utilizatorilor activi a fost atinsă',
|
activeUsersLimitReached: 'Limita utilizatorilor activi a fost atinsă',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Este necesară autentificarea administratorului pentru a inițializa instanța',
|
'Este necesară autentificarea administratorului pentru a inițializa instanța',
|
||||||
emailAlreadyInUse: 'E-mail deja utilizat',
|
emailAlreadyInUse: 'E-mail deja utilizat',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Достигнут лимит активных пользователей',
|
activeUsersLimitReached: 'Достигнут лимит активных пользователей',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Требуется вход администратора для инициализации экземпляра',
|
'Требуется вход администратора для инициализации экземпляра',
|
||||||
emailAlreadyInUse: 'E-mail уже занят',
|
emailAlreadyInUse: 'E-mail уже занят',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Limit aktívnych používateľov bol dosiahnutý',
|
activeUsersLimitReached: 'Limit aktívnych používateľov bol dosiahnutý',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Na inicializáciu inštancie je potrebné prihlásenie správcu',
|
'Na inicializáciu inštancie je potrebné prihlásenie správcu',
|
||||||
emailAlreadyInUse: 'E-mail je už použitý',
|
emailAlreadyInUse: 'E-mail je už použitý',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Достигнут је лимит активних корисника',
|
activeUsersLimitReached: 'Достигнут је лимит активних корисника',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Потребна је администраторска пријава за иницијализацију инстанце',
|
'Потребна је администраторска пријава за иницијализацију инстанце',
|
||||||
emailAlreadyInUse: 'Е-пошта је већ у употреби',
|
emailAlreadyInUse: 'Е-пошта је већ у употреби',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Dostignut je limit aktivnih korisnika',
|
activeUsersLimitReached: 'Dostignut je limit aktivnih korisnika',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Potrebna je administratorska prijava za inicijalizaciju instance',
|
'Potrebna je administratorska prijava za inicijalizaciju instance',
|
||||||
emailAlreadyInUse: 'E-pošta je već u upotrebi',
|
emailAlreadyInUse: 'E-pošta je već u upotrebi',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Gränsen för aktiva användare har nåtts',
|
activeUsersLimitReached: 'Gränsen för aktiva användare har nåtts',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Administratörsinloggning krävs för att initiera instansen',
|
'Administratörsinloggning krävs för att initiera instansen',
|
||||||
emailAlreadyInUse: 'E-mail används redan',
|
emailAlreadyInUse: 'E-mail används redan',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Aktif kullanıcı sınırına ulaşıldı',
|
activeUsersLimitReached: 'Aktif kullanıcı sınırına ulaşıldı',
|
||||||
adminLoginRequiredToInitializeInstance: 'Örneği başlatmak için yönetici girişi gerekli',
|
adminLoginRequiredToInitializeInstance: 'Örneği başlatmak için yönetici girişi gerekli',
|
||||||
emailAlreadyInUse: 'E-posta adresi zaten kullanımda',
|
emailAlreadyInUse: 'E-posta adresi zaten kullanımda',
|
||||||
emailOrUsername: 'E-posta adresi veya kullanıcı adı',
|
emailOrUsername: 'E-posta adresi veya kullanıcı adı',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Досягнуто ліміту активних користувачів',
|
activeUsersLimitReached: 'Досягнуто ліміту активних користувачів',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Потрібен вхід адміністратора для ініціалізації екземпляра',
|
'Потрібен вхід адміністратора для ініціалізації екземпляра',
|
||||||
emailAlreadyInUse: 'Електронна пошта вже використовується',
|
emailAlreadyInUse: 'Електронна пошта вже використовується',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Faol foydalanuvchilar chegarasiga yetildi',
|
activeUsersLimitReached: 'Faol foydalanuvchilar chegarasiga yetildi',
|
||||||
adminLoginRequiredToInitializeInstance:
|
adminLoginRequiredToInitializeInstance:
|
||||||
'Tizimni ishga tushirish uchun admin kirishi talab qilinadi',
|
'Tizimni ishga tushirish uchun admin kirishi talab qilinadi',
|
||||||
emailAlreadyInUse: 'E-mail allaqachon mavjud',
|
emailAlreadyInUse: 'E-mail allaqachon mavjud',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: 'Đã đạt giới hạn người dùng hoạt động',
|
activeUsersLimitReached: 'Đã đạt giới hạn người dùng hoạt động',
|
||||||
adminLoginRequiredToInitializeInstance: 'Cần đăng nhập quản trị để khởi tạo phiên bản',
|
adminLoginRequiredToInitializeInstance: 'Cần đăng nhập quản trị để khởi tạo phiên bản',
|
||||||
emailAlreadyInUse: 'E-mail đã được sử dụng',
|
emailAlreadyInUse: 'E-mail đã được sử dụng',
|
||||||
emailOrUsername: 'E-mail hoặc tên đăng nhập',
|
emailOrUsername: 'E-mail hoặc tên đăng nhập',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: '活跃用户数已达上限',
|
activeUsersLimitReached: '活跃用户数已达上限',
|
||||||
adminLoginRequiredToInitializeInstance: '需要管理员登录以初始化实例',
|
adminLoginRequiredToInitializeInstance: '需要管理员登录以初始化实例',
|
||||||
emailAlreadyInUse: '邮箱已使用',
|
emailAlreadyInUse: '邮箱已使用',
|
||||||
emailOrUsername: '邮箱或用户名',
|
emailOrUsername: '邮箱或用户名',
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export default {
|
export default {
|
||||||
translation: {
|
translation: {
|
||||||
common: {
|
common: {
|
||||||
activeUserLimitReached: '活躍使用者數已達上限',
|
activeUsersLimitReached: '活躍使用者數已達上限',
|
||||||
adminLoginRequiredToInitializeInstance: '需要管理員登入以初始化實例',
|
adminLoginRequiredToInitializeInstance: '需要管理員登入以初始化實例',
|
||||||
emailAlreadyInUse: '郵箱已被使用',
|
emailAlreadyInUse: '郵箱已被使用',
|
||||||
emailOrUsername: '郵箱或使用者名稱',
|
emailOrUsername: '郵箱或使用者名稱',
|
||||||
|
|||||||
+17
-16
@@ -129,6 +129,23 @@ export default class extends BaseModel {
|
|||||||
User.upsert(user);
|
User.upsert(user);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
break;
|
||||||
|
case ActionTypes.USERS_RESET_HANDLE:
|
||||||
|
case ActionTypes.PROJECT_CREATE_HANDLE:
|
||||||
|
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
||||||
|
case ActionTypes.BOARD_FETCH__SUCCESS:
|
||||||
|
case ActionTypes.CARDS_FETCH__SUCCESS:
|
||||||
|
case ActionTypes.CARD_CREATE_HANDLE:
|
||||||
|
case ActionTypes.CARD_TRANSFER__SUCCESS:
|
||||||
|
case ActionTypes.COMMENTS_FETCH__SUCCESS:
|
||||||
|
case ActionTypes.COMMENT_CREATE_HANDLE:
|
||||||
|
case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS:
|
||||||
|
case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS:
|
||||||
|
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
||||||
|
payload.users.forEach((user) => {
|
||||||
|
User.upsert(user);
|
||||||
|
});
|
||||||
|
|
||||||
break;
|
break;
|
||||||
case ActionTypes.USER_CREATE__SUCCESS:
|
case ActionTypes.USER_CREATE__SUCCESS:
|
||||||
case ActionTypes.USER_CREATE_HANDLE:
|
case ActionTypes.USER_CREATE_HANDLE:
|
||||||
@@ -347,22 +364,6 @@ export default class extends BaseModel {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
case ActionTypes.PROJECT_CREATE_HANDLE:
|
|
||||||
case ActionTypes.PROJECT_MANAGER_CREATE_HANDLE:
|
|
||||||
case ActionTypes.BOARD_FETCH__SUCCESS:
|
|
||||||
case ActionTypes.CARDS_FETCH__SUCCESS:
|
|
||||||
case ActionTypes.CARD_CREATE_HANDLE:
|
|
||||||
case ActionTypes.CARD_TRANSFER__SUCCESS:
|
|
||||||
case ActionTypes.COMMENTS_FETCH__SUCCESS:
|
|
||||||
case ActionTypes.COMMENT_CREATE_HANDLE:
|
|
||||||
case ActionTypes.ACTIVITIES_IN_BOARD_FETCH__SUCCESS:
|
|
||||||
case ActionTypes.ACTIVITIES_IN_CARD_FETCH__SUCCESS:
|
|
||||||
case ActionTypes.NOTIFICATION_CREATE_HANDLE:
|
|
||||||
payload.users.forEach((user) => {
|
|
||||||
User.upsert(user);
|
|
||||||
});
|
|
||||||
|
|
||||||
break;
|
|
||||||
default:
|
default:
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,14 @@ export default (state = initialState, { type, payload }) => {
|
|||||||
...state,
|
...state,
|
||||||
bootstrap: payload.bootstrap,
|
bootstrap: payload.bootstrap,
|
||||||
};
|
};
|
||||||
|
case ActionTypes.BOOTSTRAP_UPDATE_HANDLE:
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
bootstrap: {
|
||||||
|
...state.bootstrap,
|
||||||
|
...payload.bootstrap,
|
||||||
|
},
|
||||||
|
};
|
||||||
case ActionTypes.LOGIN_INITIALIZE:
|
case ActionTypes.LOGIN_INITIALIZE:
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
|
|||||||
+16
@@ -0,0 +1,16 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { put } from 'redux-saga/effects';
|
||||||
|
|
||||||
|
import actions from '../../../actions';
|
||||||
|
|
||||||
|
export function* handleBootstrapUpdate(bootstrap) {
|
||||||
|
yield put(actions.handleBootstrapUpdate(bootstrap));
|
||||||
|
}
|
||||||
|
|
||||||
|
export default {
|
||||||
|
handleBootstrapUpdate,
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import bootstrap from './bootstrap';
|
||||||
import core from './core';
|
import core from './core';
|
||||||
import modals from './modals';
|
import modals from './modals';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
@@ -33,6 +34,7 @@ import notificationServices from './notification-services';
|
|||||||
export default {
|
export default {
|
||||||
...router,
|
...router,
|
||||||
...socket,
|
...socket,
|
||||||
|
...bootstrap,
|
||||||
...core,
|
...core,
|
||||||
...modals,
|
...modals,
|
||||||
...config,
|
...config,
|
||||||
|
|||||||
@@ -16,6 +16,17 @@ import mergeRecords from '../../../utils/merge-records';
|
|||||||
import { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';
|
import { isUserAdminOrProjectOwner } from '../../../utils/record-helpers';
|
||||||
import { UserRoles } from '../../../constants/Enums';
|
import { UserRoles } from '../../../constants/Enums';
|
||||||
|
|
||||||
|
export function* handleUsersReset() {
|
||||||
|
let users;
|
||||||
|
try {
|
||||||
|
({ items: users } = yield call(request, api.getUsers));
|
||||||
|
} catch {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield put(actions.handleUsersReset(users));
|
||||||
|
}
|
||||||
|
|
||||||
export function* createUser(data) {
|
export function* createUser(data) {
|
||||||
yield put(actions.createUser(data));
|
yield put(actions.createUser(data));
|
||||||
|
|
||||||
@@ -491,6 +502,7 @@ export function* removeUserFromFilterInCurrentBoard(id) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
handleUsersReset,
|
||||||
createUser,
|
createUser,
|
||||||
handleUserCreate,
|
handleUserCreate,
|
||||||
clearUserCreateError,
|
clearUserCreateError,
|
||||||
|
|||||||
+17
@@ -0,0 +1,17 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { all, takeEvery } from 'redux-saga/effects';
|
||||||
|
|
||||||
|
import services from '../services';
|
||||||
|
import EntryActionTypes from '../../../constants/EntryActionTypes';
|
||||||
|
|
||||||
|
export default function* bootstrapWatchers() {
|
||||||
|
yield all([
|
||||||
|
takeEvery(EntryActionTypes.BOOTSTRAP_UPDATE_HANDLE, ({ payload: { bootstrap } }) =>
|
||||||
|
services.handleBootstrapUpdate(bootstrap),
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import router from './router';
|
import router from './router';
|
||||||
import socket from './socket';
|
import socket from './socket';
|
||||||
|
import bootstrap from './bootstrap';
|
||||||
import core from './core';
|
import core from './core';
|
||||||
import modals from './modals';
|
import modals from './modals';
|
||||||
import config from './config';
|
import config from './config';
|
||||||
@@ -33,6 +34,7 @@ import notificationServices from './notification-services';
|
|||||||
export default [
|
export default [
|
||||||
router,
|
router,
|
||||||
socket,
|
socket,
|
||||||
|
bootstrap,
|
||||||
core,
|
core,
|
||||||
modals,
|
modals,
|
||||||
config,
|
config,
|
||||||
|
|||||||
@@ -25,6 +25,10 @@ const createSocketEventsChannel = () =>
|
|||||||
emit(entryActions.logout(false));
|
emit(entryActions.logout(false));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleBootstrapUpdate = ({ item }) => {
|
||||||
|
emit(entryActions.handleBootstrapUpdate(item));
|
||||||
|
};
|
||||||
|
|
||||||
const handleConfigUpdate = ({ item }) => {
|
const handleConfigUpdate = ({ item }) => {
|
||||||
emit(entryActions.handleConfigUpdate(item));
|
emit(entryActions.handleConfigUpdate(item));
|
||||||
};
|
};
|
||||||
@@ -41,6 +45,10 @@ const createSocketEventsChannel = () =>
|
|||||||
emit(entryActions.handleWebhookDelete(item));
|
emit(entryActions.handleWebhookDelete(item));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleUsersReset = () => {
|
||||||
|
emit(entryActions.handleUsersReset());
|
||||||
|
};
|
||||||
|
|
||||||
const handleUserCreate = ({ item }) => {
|
const handleUserCreate = ({ item }) => {
|
||||||
emit(entryActions.handleUserCreate(item));
|
emit(entryActions.handleUserCreate(item));
|
||||||
};
|
};
|
||||||
@@ -290,12 +298,15 @@ const createSocketEventsChannel = () =>
|
|||||||
|
|
||||||
socket.on('logout', handleLogout);
|
socket.on('logout', handleLogout);
|
||||||
|
|
||||||
|
socket.on('bootstrapUpdate', handleBootstrapUpdate);
|
||||||
|
|
||||||
socket.on('configUpdate', handleConfigUpdate);
|
socket.on('configUpdate', handleConfigUpdate);
|
||||||
|
|
||||||
socket.on('webhookCreate', handleWebhookCreate);
|
socket.on('webhookCreate', handleWebhookCreate);
|
||||||
socket.on('webhookUpdate', handleWebhookUpdate);
|
socket.on('webhookUpdate', handleWebhookUpdate);
|
||||||
socket.on('webhookDelete', handleWebhookDelete);
|
socket.on('webhookDelete', handleWebhookDelete);
|
||||||
|
|
||||||
|
socket.on('usersReset', handleUsersReset);
|
||||||
socket.on('userCreate', handleUserCreate);
|
socket.on('userCreate', handleUserCreate);
|
||||||
socket.on('userUpdate', handleUserUpdate);
|
socket.on('userUpdate', handleUserUpdate);
|
||||||
socket.on('userDelete', handleUserDelete);
|
socket.on('userDelete', handleUserDelete);
|
||||||
@@ -384,12 +395,15 @@ const createSocketEventsChannel = () =>
|
|||||||
|
|
||||||
socket.off('logout', handleLogout);
|
socket.off('logout', handleLogout);
|
||||||
|
|
||||||
|
socket.off('bootstrapUpdate', handleBootstrapUpdate);
|
||||||
|
|
||||||
socket.off('configUpdate', handleConfigUpdate);
|
socket.off('configUpdate', handleConfigUpdate);
|
||||||
|
|
||||||
socket.off('webhookCreate', handleWebhookCreate);
|
socket.off('webhookCreate', handleWebhookCreate);
|
||||||
socket.off('webhookUpdate', handleWebhookUpdate);
|
socket.off('webhookUpdate', handleWebhookUpdate);
|
||||||
socket.off('webhookDelete', handleWebhookDelete);
|
socket.off('webhookDelete', handleWebhookDelete);
|
||||||
|
|
||||||
|
socket.off('usersReset', handleUsersReset);
|
||||||
socket.off('userCreate', handleUserCreate);
|
socket.off('userCreate', handleUserCreate);
|
||||||
socket.off('userUpdate', handleUserUpdate);
|
socket.off('userUpdate', handleUserUpdate);
|
||||||
socket.off('userDelete', handleUserDelete);
|
socket.off('userDelete', handleUserDelete);
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import EntryActionTypes from '../../../constants/EntryActionTypes';
|
|||||||
|
|
||||||
export default function* usersWatchers() {
|
export default function* usersWatchers() {
|
||||||
yield all([
|
yield all([
|
||||||
|
takeEvery(EntryActionTypes.USERS_RESET_HANDLE, () => services.handleUsersReset()),
|
||||||
takeEvery(EntryActionTypes.USER_CREATE, ({ payload: { data } }) => services.createUser(data)),
|
takeEvery(EntryActionTypes.USER_CREATE, ({ payload: { data } }) => services.createUser(data)),
|
||||||
takeEvery(EntryActionTypes.USER_CREATE_HANDLE, ({ payload: { user } }) =>
|
takeEvery(EntryActionTypes.USER_CREATE_HANDLE, ({ payload: { user } }) =>
|
||||||
services.handleUserCreate(user),
|
services.handleUserCreate(user),
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export const selectBootstrap = ({ common: { bootstrap } }) => bootstrap;
|
|||||||
|
|
||||||
export const selectOidcBootstrap = (state) => selectBootstrap(state).oidc;
|
export const selectOidcBootstrap = (state) => selectBootstrap(state).oidc;
|
||||||
|
|
||||||
export const selectActiveUserLimit = (state) => selectBootstrap(state).activeUserLimit;
|
export const selectActiveUsersLimit = (state) => selectBootstrap(state).activeUsersLimit;
|
||||||
|
|
||||||
export const selectAccessToken = ({ auth: { accessToken } }) => accessToken;
|
export const selectAccessToken = ({ auth: { accessToken } }) => accessToken;
|
||||||
|
|
||||||
@@ -28,7 +28,7 @@ export default {
|
|||||||
selectIsInitializing,
|
selectIsInitializing,
|
||||||
selectBootstrap,
|
selectBootstrap,
|
||||||
selectOidcBootstrap,
|
selectOidcBootstrap,
|
||||||
selectActiveUserLimit,
|
selectActiveUsersLimit,
|
||||||
selectAccessToken,
|
selectAccessToken,
|
||||||
selectAuthenticateForm,
|
selectAuthenticateForm,
|
||||||
selectUserCreateForm,
|
selectUserCreateForm,
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ export const selectActiveUsers = createSelector(orm, ({ User }) =>
|
|||||||
User.getActiveQuerySet().toRefArray(),
|
User.getActiveQuerySet().toRefArray(),
|
||||||
);
|
);
|
||||||
|
|
||||||
export const selectActiveUserTotal = createSelector(orm, ({ User }) =>
|
export const selectActiveUsersTotal = createSelector(orm, ({ User }) =>
|
||||||
User.getActiveQuerySet().count(),
|
User.getActiveQuerySet().count(),
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -347,7 +347,7 @@ export default {
|
|||||||
selectCurrentUserId,
|
selectCurrentUserId,
|
||||||
selectUsers,
|
selectUsers,
|
||||||
selectActiveUsers,
|
selectActiveUsers,
|
||||||
selectActiveUserTotal,
|
selectActiveUsersTotal,
|
||||||
selectActiveAdminOrProjectOwnerUsers,
|
selectActiveAdminOrProjectOwnerUsers,
|
||||||
selectCurrentUser,
|
selectCurrentUser,
|
||||||
selectProjectIdsForCurrentUser,
|
selectProjectIdsForCurrentUser,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ services:
|
|||||||
|
|
||||||
# - INTERNAL_ACCESS_TOKEN=
|
# - INTERNAL_ACCESS_TOKEN=
|
||||||
# - STORAGE_LIMIT=
|
# - STORAGE_LIMIT=
|
||||||
# - ACTIVE_USER_LIMIT=
|
# - ACTIVE_USERS_LIMIT=
|
||||||
# - CUSTOMER_PANEL_URL=
|
# - CUSTOMER_PANEL_URL=
|
||||||
# - DEMO_MODE=true
|
# - DEMO_MODE=true
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -56,7 +56,7 @@ services:
|
|||||||
|
|
||||||
# - INTERNAL_ACCESS_TOKEN=
|
# - INTERNAL_ACCESS_TOKEN=
|
||||||
# - STORAGE_LIMIT=
|
# - STORAGE_LIMIT=
|
||||||
# - ACTIVE_USER_LIMIT=
|
# - ACTIVE_USERS_LIMIT=
|
||||||
# - CUSTOMER_PANEL_URL=
|
# - CUSTOMER_PANEL_URL=
|
||||||
# - DEMO_MODE=true
|
# - DEMO_MODE=true
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -33,7 +33,7 @@ SECRET_KEY=notsecretkey
|
|||||||
|
|
||||||
# INTERNAL_ACCESS_TOKEN=
|
# INTERNAL_ACCESS_TOKEN=
|
||||||
# STORAGE_LIMIT=
|
# STORAGE_LIMIT=
|
||||||
# ACTIVE_USER_LIMIT=
|
# ACTIVE_USERS_LIMIT=
|
||||||
# CUSTOMER_PANEL_URL=
|
# CUSTOMER_PANEL_URL=
|
||||||
# DEMO_MODE=true
|
# DEMO_MODE=true
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,51 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
const Errors = {
|
||||||
|
NOT_ENOUGH_RIGHTS: {
|
||||||
|
notEnoughRights: 'Not enough rights',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
inputs: {
|
||||||
|
storageLimit: {
|
||||||
|
type: 'string',
|
||||||
|
isNotEmptyString: true,
|
||||||
|
maxLength: 256,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
activeUsersLimit: {
|
||||||
|
type: 'number',
|
||||||
|
min: 0,
|
||||||
|
allowNull: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
exits: {
|
||||||
|
notEnoughRights: {
|
||||||
|
responseType: 'forbidden',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async fn(inputs) {
|
||||||
|
// eslint-disable-next-line no-restricted-syntax
|
||||||
|
for (const fieldName of Object.keys(inputs)) {
|
||||||
|
if (!_.isNil(sails.config.custom[fieldName])) {
|
||||||
|
throw Errors.NOT_ENOUGH_RIGHTS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = _.pick(inputs, ['storageLimit', 'activeUsersLimit']);
|
||||||
|
|
||||||
|
const internalConfig = await sails.helpers.internalConfig.updateMain.with({
|
||||||
|
values,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
item: internalConfig,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -202,11 +202,11 @@ module.exports = {
|
|||||||
({ user } = await User.qm.updateOne(user.id, values));
|
({ user } = await User.qm.updateOne(user.id, values));
|
||||||
}
|
}
|
||||||
|
|
||||||
const config = await Config.qm.getOneMain();
|
const internalConfig = await InternalConfig.qm.getOneMain();
|
||||||
|
|
||||||
if (!config.isInitialized) {
|
if (!internalConfig.isInitialized) {
|
||||||
if (user.role === User.Roles.ADMIN) {
|
if (user.role === User.Roles.ADMIN) {
|
||||||
await Config.qm.updateOneMain({
|
await InternalConfig.qm.updateOneMain({
|
||||||
isInitialized: true,
|
isInitialized: true,
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -119,7 +119,7 @@
|
|||||||
* enum:
|
* enum:
|
||||||
* - Email already in use
|
* - Email already in use
|
||||||
* - Username already in use
|
* - Username already in use
|
||||||
* - Active user limit reached
|
* - Active users limit reached
|
||||||
* description: Specific error message
|
* description: Specific error message
|
||||||
* example: Email already in use
|
* example: Email already in use
|
||||||
* 422:
|
* 422:
|
||||||
@@ -182,8 +182,8 @@ const Errors = {
|
|||||||
USERNAME_ALREADY_IN_USE: {
|
USERNAME_ALREADY_IN_USE: {
|
||||||
usernameAlreadyInUse: 'Username already in use',
|
usernameAlreadyInUse: 'Username already in use',
|
||||||
},
|
},
|
||||||
ACTIVE_USER_LIMIT_REACHED: {
|
ACTIVE_USERS_LIMIT_REACHED: {
|
||||||
activeUserLimitReached: 'Active user limit reached',
|
activeUsersLimitReached: 'Active users limit reached',
|
||||||
},
|
},
|
||||||
MISSING_VALUES: {
|
MISSING_VALUES: {
|
||||||
missingValues: 'Unable to retrieve required values (email, name)',
|
missingValues: 'Unable to retrieve required values (email, name)',
|
||||||
@@ -229,7 +229,7 @@ module.exports = {
|
|||||||
usernameAlreadyInUse: {
|
usernameAlreadyInUse: {
|
||||||
responseType: 'conflict',
|
responseType: 'conflict',
|
||||||
},
|
},
|
||||||
activeUserLimitReached: {
|
activeUsersLimitReached: {
|
||||||
responseType: 'conflict',
|
responseType: 'conflict',
|
||||||
},
|
},
|
||||||
missingValues: {
|
missingValues: {
|
||||||
@@ -250,7 +250,7 @@ module.exports = {
|
|||||||
.intercept('invalidUserinfoConfiguration', () => Errors.INVALID_USERINFO_CONFIGURATION)
|
.intercept('invalidUserinfoConfiguration', () => Errors.INVALID_USERINFO_CONFIGURATION)
|
||||||
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
|
.intercept('emailAlreadyInUse', () => Errors.EMAIL_ALREADY_IN_USE)
|
||||||
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)
|
.intercept('usernameAlreadyInUse', () => Errors.USERNAME_ALREADY_IN_USE)
|
||||||
.intercept('activeLimitReached', () => Errors.ACTIVE_USER_LIMIT_REACHED)
|
.intercept('activeLimitReached', () => Errors.ACTIVE_USERS_LIMIT_REACHED)
|
||||||
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
.intercept('missingValues', () => Errors.MISSING_VALUES);
|
||||||
|
|
||||||
return sails.helpers.accessTokens.handleSteps
|
return sails.helpers.accessTokens.handleSteps
|
||||||
|
|||||||
@@ -47,7 +47,7 @@
|
|||||||
* type: boolean
|
* type: boolean
|
||||||
* description: Whether OIDC authentication is enforced (users must use OIDC to login)
|
* description: Whether OIDC authentication is enforced (users must use OIDC to login)
|
||||||
* example: false
|
* example: false
|
||||||
* activeUserLimit:
|
* activeUsersLimit:
|
||||||
* type: number
|
* type: number
|
||||||
* nullable: true
|
* nullable: true
|
||||||
* description: Maximum number of active users allowed (conditionally added for admins if configured)
|
* description: Maximum number of active users allowed (conditionally added for admins if configured)
|
||||||
@@ -68,10 +68,11 @@ module.exports = {
|
|||||||
async fn() {
|
async fn() {
|
||||||
const { currentUser } = this.req;
|
const { currentUser } = this.req;
|
||||||
|
|
||||||
|
const internalConfig = await InternalConfig.qm.getOneMain();
|
||||||
const oidc = await sails.hooks.oidc.getBootstrap();
|
const oidc = await sails.hooks.oidc.getBootstrap();
|
||||||
|
|
||||||
return {
|
return {
|
||||||
item: sails.helpers.bootstrap.presentOne(oidc, currentUser),
|
item: sails.helpers.bootstrap.presentOne(internalConfig, oidc, currentUser),
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -42,12 +42,12 @@ module.exports = {
|
|||||||
},
|
},
|
||||||
|
|
||||||
async fn(inputs) {
|
async fn(inputs) {
|
||||||
const config = await Config.qm.getOneMain();
|
const internalConfig = await InternalConfig.qm.getOneMain();
|
||||||
|
|
||||||
if (!config.isInitialized) {
|
if (!internalConfig.isInitialized) {
|
||||||
if (inputs.user.role === User.Roles.ADMIN) {
|
if (inputs.user.role === User.Roles.ADMIN) {
|
||||||
if (inputs.user.termsSignature) {
|
if (inputs.user.termsSignature) {
|
||||||
await Config.qm.updateOneMain({
|
await InternalConfig.qm.updateOneMain({
|
||||||
isInitialized: true,
|
isInitialized: true,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,10 @@ module.exports = {
|
|||||||
sync: true,
|
sync: true,
|
||||||
|
|
||||||
inputs: {
|
inputs: {
|
||||||
|
internalConfig: {
|
||||||
|
type: 'ref',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
oidc: {
|
oidc: {
|
||||||
type: 'ref',
|
type: 'ref',
|
||||||
},
|
},
|
||||||
@@ -22,7 +26,7 @@ module.exports = {
|
|||||||
};
|
};
|
||||||
if (inputs.user && inputs.user.role === User.Roles.ADMIN) {
|
if (inputs.user && inputs.user.role === User.Roles.ADMIN) {
|
||||||
Object.assign(data, {
|
Object.assign(data, {
|
||||||
activeUserLimit: sails.config.custom.activeUserLimit,
|
activeUsersLimit: inputs.internalConfig.activeUsersLimit,
|
||||||
customerPanelUrl: sails.config.custom.customerPanelUrl,
|
customerPanelUrl: sails.config.custom.customerPanelUrl,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
inputs: {
|
||||||
|
values: {
|
||||||
|
type: 'json',
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
async fn(inputs) {
|
||||||
|
const { values } = inputs;
|
||||||
|
|
||||||
|
const { internalConfig, deactivatedUserIds, prev } =
|
||||||
|
await InternalConfig.qm.updateOneMain(values);
|
||||||
|
|
||||||
|
if (deactivatedUserIds) {
|
||||||
|
deactivatedUserIds.forEach((userId) => {
|
||||||
|
sails.sockets.broadcast(`user:${userId}`, 'logout');
|
||||||
|
sails.sockets.leaveAll(`@user:${userId}`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (internalConfig.activeUsersLimit !== prev.activeUsersLimit) {
|
||||||
|
let adminUserIds;
|
||||||
|
if (deactivatedUserIds && deactivatedUserIds.length > 0) {
|
||||||
|
const users = await User.qm.getAll({
|
||||||
|
roleOrRoles: [User.Roles.ADMIN, User.Roles.PROJECT_OWNER],
|
||||||
|
});
|
||||||
|
|
||||||
|
adminUserIds = users.flatMap((user) => {
|
||||||
|
sails.sockets.broadcast(`user:${user.id}`, 'usersReset');
|
||||||
|
|
||||||
|
return user.role === User.Roles.ADMIN ? user.id : [];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
adminUserIds = await sails.helpers.users.getAllIds(User.Roles.ADMIN);
|
||||||
|
}
|
||||||
|
|
||||||
|
adminUserIds.forEach((userId) => {
|
||||||
|
sails.sockets.broadcast(`user:${userId}`, 'bootstrapUpdate', {
|
||||||
|
item: {
|
||||||
|
activeUsersLimit: internalConfig.activeUsersLimit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return internalConfig;
|
||||||
|
},
|
||||||
|
};
|
||||||
@@ -112,14 +112,7 @@ module.exports = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!_.isUndefined(values.password) || isDeactivatedChangeToTrue) {
|
if (!_.isUndefined(values.password) || isDeactivatedChangeToTrue) {
|
||||||
sails.sockets.broadcast(
|
sails.sockets.broadcast(`user:${user.id}`, 'logout', undefined, inputs.request);
|
||||||
`user:${user.id}`,
|
|
||||||
'userDelete', // TODO: introduce separate event
|
|
||||||
{
|
|
||||||
item: sails.helpers.users.presentOne(user, user),
|
|
||||||
},
|
|
||||||
inputs.request,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!isDeactivatedChangeToTrue &&
|
!isDeactivatedChangeToTrue &&
|
||||||
|
|||||||
@@ -3,9 +3,12 @@
|
|||||||
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
const bytes = require('bytes');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
async fn() {
|
async fn() {
|
||||||
const { storageLimit } = sails.config.custom;
|
let { storageLimit } = await InternalConfig.qm.getOneMain();
|
||||||
|
storageLimit = bytes(storageLimit);
|
||||||
|
|
||||||
if (storageLimit === null) {
|
if (storageLimit === null) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -0,0 +1,66 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
const { makeRowToModelTransformer } = require('../helpers');
|
||||||
|
|
||||||
|
const transformRowToModel = makeRowToModelTransformer(InternalConfig);
|
||||||
|
|
||||||
|
/* Query methods */
|
||||||
|
|
||||||
|
const getOneMain = () => InternalConfig.findOne(InternalConfig.MAIN_ID);
|
||||||
|
|
||||||
|
const updateOneMain = (values) =>
|
||||||
|
sails.getDatastore().transaction(async (db) => {
|
||||||
|
let queryResult = await sails
|
||||||
|
.sendNativeQuery(
|
||||||
|
'SELECT active_users_limit FROM internal_config WHERE id = $1 LIMIT 1 FOR UPDATE',
|
||||||
|
[InternalConfig.MAIN_ID],
|
||||||
|
)
|
||||||
|
.usingConnection(db);
|
||||||
|
|
||||||
|
const prev = transformRowToModel(queryResult.rows[0]);
|
||||||
|
|
||||||
|
const internalConfig = await InternalConfig.updateOne(InternalConfig.MAIN_ID)
|
||||||
|
.set({ ...values })
|
||||||
|
.usingConnection(db);
|
||||||
|
|
||||||
|
let deactivatedUserIds;
|
||||||
|
if (
|
||||||
|
_.isInteger(internalConfig.activeUsersLimit) &&
|
||||||
|
(prev.activeUsersLimit === null || internalConfig.activeUsersLimit < prev.activeUsersLimit)
|
||||||
|
) {
|
||||||
|
const { defaultAdminEmail } = sails.config.custom;
|
||||||
|
|
||||||
|
const query = `
|
||||||
|
WITH user_to_deactivate AS (
|
||||||
|
SELECT id
|
||||||
|
FROM user_account
|
||||||
|
WHERE is_deactivated = false
|
||||||
|
ORDER BY
|
||||||
|
CASE ${defaultAdminEmail ? 'WHEN email = $1 THEN 0 WHEN role = $2 THEN 1' : 'WHEN role = $1 THEN 0'} ELSE ${defaultAdminEmail ? '2' : '1'} END,
|
||||||
|
id
|
||||||
|
OFFSET $${defaultAdminEmail ? 3 : 2}
|
||||||
|
)
|
||||||
|
UPDATE user_account
|
||||||
|
SET is_deactivated = true
|
||||||
|
WHERE id IN (SELECT id FROM user_to_deactivate)
|
||||||
|
RETURNING id
|
||||||
|
`;
|
||||||
|
|
||||||
|
const queryValues = defaultAdminEmail
|
||||||
|
? [defaultAdminEmail, User.Roles.ADMIN, internalConfig.activeUsersLimit]
|
||||||
|
: [User.Roles.ADMIN, internalConfig.activeUsersLimit];
|
||||||
|
|
||||||
|
queryResult = await sails.sendNativeQuery(query, queryValues).usingConnection(db);
|
||||||
|
deactivatedUserIds = queryResult.rows.map((row) => row.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { internalConfig, deactivatedUserIds, prev };
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
getOneMain,
|
||||||
|
updateOneMain,
|
||||||
|
};
|
||||||
@@ -24,8 +24,10 @@ const defaultFind = (criteria) => User.find(criteria).sort('id');
|
|||||||
|
|
||||||
/* Query methods */
|
/* Query methods */
|
||||||
|
|
||||||
const createOne = (values) => {
|
const createOne = async (values) => {
|
||||||
if (sails.config.custom.activeUserLimit !== null) {
|
const { activeUsersLimit } = await InternalConfig.qm.getOneMain();
|
||||||
|
|
||||||
|
if (activeUsersLimit !== null) {
|
||||||
return sails.getDatastore().transaction(async (db) => {
|
return sails.getDatastore().transaction(async (db) => {
|
||||||
const queryResult = await sails
|
const queryResult = await sails
|
||||||
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
|
.sendNativeQuery('SELECT NULL FROM user_account WHERE is_deactivated = $1 FOR UPDATE', [
|
||||||
@@ -33,7 +35,7 @@ const createOne = (values) => {
|
|||||||
])
|
])
|
||||||
.usingConnection(db);
|
.usingConnection(db);
|
||||||
|
|
||||||
if (queryResult.rowCount >= sails.config.custom.activeUserLimit) {
|
if (queryResult.rowCount >= activeUsersLimit) {
|
||||||
throw 'activeLimitReached';
|
throw 'activeLimitReached';
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -86,8 +88,8 @@ const getOneActiveByApiKeyHash = (apiKeyHash) =>
|
|||||||
});
|
});
|
||||||
|
|
||||||
const updateOne = async (criteria, values) => {
|
const updateOne = async (criteria, values) => {
|
||||||
const enforceActiveLimit =
|
const { activeUsersLimit } = await InternalConfig.qm.getOneMain();
|
||||||
values.isDeactivated === false && sails.config.custom.activeUserLimit !== null;
|
const enforceActiveLimit = values.isDeactivated === false && activeUsersLimit !== null;
|
||||||
|
|
||||||
if (!_.isUndefined(values.avatar) || enforceActiveLimit) {
|
if (!_.isUndefined(values.avatar) || enforceActiveLimit) {
|
||||||
return sails.getDatastore().transaction(async (db) => {
|
return sails.getDatastore().transaction(async (db) => {
|
||||||
@@ -98,7 +100,7 @@ const updateOne = async (criteria, values) => {
|
|||||||
])
|
])
|
||||||
.usingConnection(db);
|
.usingConnection(db);
|
||||||
|
|
||||||
if (queryResult.rowCount >= sails.config.custom.activeUserLimit) {
|
if (queryResult.rowCount >= activeUsersLimit) {
|
||||||
throw 'activeLimitReached';
|
throw 'activeLimitReached';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,7 +18,6 @@
|
|||||||
* type: object
|
* type: object
|
||||||
* required:
|
* required:
|
||||||
* - id
|
* - id
|
||||||
* - isInitialized
|
|
||||||
* properties:
|
* properties:
|
||||||
* id:
|
* id:
|
||||||
* type: string
|
* type: string
|
||||||
@@ -62,10 +61,6 @@
|
|||||||
* nullable: true
|
* nullable: true
|
||||||
* description: Default "from" used for outgoing SMTP emails
|
* description: Default "from" used for outgoing SMTP emails
|
||||||
* example: no-reply@example.com
|
* example: no-reply@example.com
|
||||||
* isInitialized:
|
|
||||||
* type: boolean
|
|
||||||
* description: Whether the PLANKA instance has been initialized
|
|
||||||
* example: true
|
|
||||||
* createdAt:
|
* createdAt:
|
||||||
* type: string
|
* type: string
|
||||||
* format: date-time
|
* format: date-time
|
||||||
@@ -142,11 +137,6 @@ module.exports = {
|
|||||||
allowNull: true,
|
allowNull: true,
|
||||||
columnName: 'smtp_from',
|
columnName: 'smtp_from',
|
||||||
},
|
},
|
||||||
isInitialized: {
|
|
||||||
type: 'boolean',
|
|
||||||
required: true,
|
|
||||||
columnName: 'is_initialized',
|
|
||||||
},
|
|
||||||
|
|
||||||
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
||||||
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
|
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
|
||||||
|
|||||||
@@ -0,0 +1,49 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* InternalConfig.js
|
||||||
|
*
|
||||||
|
* @description :: A model definition represents a database table/collection.
|
||||||
|
* @docs :: https://sailsjs.com/docs/concepts/models-and-orm/models
|
||||||
|
*/
|
||||||
|
|
||||||
|
const MAIN_ID = '1';
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
MAIN_ID,
|
||||||
|
|
||||||
|
attributes: {
|
||||||
|
// ╔═╗╦═╗╦╔╦╗╦╔╦╗╦╦ ╦╔═╗╔═╗
|
||||||
|
// ╠═╝╠╦╝║║║║║ ║ ║╚╗╔╝║╣ ╚═╗
|
||||||
|
// ╩ ╩╚═╩╩ ╩╩ ╩ ╩ ╚╝ ╚═╝╚═╝
|
||||||
|
|
||||||
|
storageLimit: {
|
||||||
|
type: 'string',
|
||||||
|
allowNull: true,
|
||||||
|
columnName: 'storage_limit',
|
||||||
|
},
|
||||||
|
activeUsersLimit: {
|
||||||
|
type: 'number',
|
||||||
|
allowNull: true,
|
||||||
|
columnName: 'active_users_limit',
|
||||||
|
},
|
||||||
|
isInitialized: {
|
||||||
|
type: 'boolean',
|
||||||
|
required: true,
|
||||||
|
columnName: 'is_initialized',
|
||||||
|
},
|
||||||
|
|
||||||
|
// ╔═╗╔╦╗╔╗ ╔═╗╔╦╗╔═╗
|
||||||
|
// ║╣ ║║║╠╩╗║╣ ║║╚═╗
|
||||||
|
// ╚═╝╩ ╩╚═╝╚═╝═╩╝╚═╝
|
||||||
|
|
||||||
|
// ╔═╗╔═╗╔═╗╔═╗╔═╗╦╔═╗╔╦╗╦╔═╗╔╗╔╔═╗
|
||||||
|
// ╠═╣╚═╗╚═╗║ ║║ ║╠═╣ ║ ║║ ║║║║╚═╗
|
||||||
|
// ╩ ╩╚═╝╚═╝╚═╝╚═╝╩╩ ╩ ╩ ╩╚═╝╝╚╝╚═╝
|
||||||
|
},
|
||||||
|
|
||||||
|
tableName: 'internal_config',
|
||||||
|
};
|
||||||
Executable
+12
@@ -0,0 +1,12 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
module.exports = async function isInternal(req, res, proceed) {
|
||||||
|
if (req.currentUser.id !== User.INTERNAL.id) {
|
||||||
|
return res.notFound(); // Forbidden
|
||||||
|
}
|
||||||
|
|
||||||
|
return proceed();
|
||||||
|
};
|
||||||
@@ -61,7 +61,7 @@ module.exports.custom = {
|
|||||||
|
|
||||||
internalAccessToken: process.env.INTERNAL_ACCESS_TOKEN,
|
internalAccessToken: process.env.INTERNAL_ACCESS_TOKEN,
|
||||||
storageLimit: envToBytes(process.env.STORAGE_LIMIT),
|
storageLimit: envToBytes(process.env.STORAGE_LIMIT),
|
||||||
activeUserLimit: envToNumber(process.env.ACTIVE_USER_LIMIT),
|
activeUsersLimit: envToNumber(process.env.ACTIVE_USERS_LIMIT),
|
||||||
customerPanelUrl: process.env.CUSTOMER_PANEL_URL,
|
customerPanelUrl: process.env.CUSTOMER_PANEL_URL,
|
||||||
demoMode: process.env.DEMO_MODE === 'true',
|
demoMode: process.env.DEMO_MODE === 'true',
|
||||||
|
|
||||||
|
|||||||
@@ -42,6 +42,8 @@ module.exports.policies = {
|
|||||||
|
|
||||||
'projects/create': ['is-authenticated', 'is-external', 'is-admin-or-project-owner'],
|
'projects/create': ['is-authenticated', 'is-external', 'is-admin-or-project-owner'],
|
||||||
|
|
||||||
|
'_internal/update-config': ['is-authenticated', 'is-internal'],
|
||||||
|
|
||||||
'bootstrap/show': true,
|
'bootstrap/show': true,
|
||||||
'terms/show': true,
|
'terms/show': true,
|
||||||
'access-tokens/create': true,
|
'access-tokens/create': true,
|
||||||
|
|||||||
@@ -195,6 +195,8 @@ module.exports.routes = {
|
|||||||
'POST /api/notification-services/:id/test': 'notification-services/test',
|
'POST /api/notification-services/:id/test': 'notification-services/test',
|
||||||
'DELETE /api/notification-services/:id': 'notification-services/delete',
|
'DELETE /api/notification-services/:id': 'notification-services/delete',
|
||||||
|
|
||||||
|
'PATCH /api/_internal/config': '_internal/update-config',
|
||||||
|
|
||||||
'GET /preloaded-favicons/*': {
|
'GET /preloaded-favicons/*': {
|
||||||
fn: staticDirServer('/preloaded-favicons', () =>
|
fn: staticDirServer('/preloaded-favicons', () =>
|
||||||
path.join(
|
path.join(
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
/*!
|
||||||
|
* Copyright (c) 2024 PLANKA Software GmbH
|
||||||
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
||||||
|
*/
|
||||||
|
|
||||||
|
exports.up = async (knex) => {
|
||||||
|
await knex.schema.createTable('internal_config', (table) => {
|
||||||
|
/* Columns */
|
||||||
|
|
||||||
|
table.bigInteger('id').primary().defaultTo(knex.raw('next_id()'));
|
||||||
|
|
||||||
|
table.text('storage_limit');
|
||||||
|
table.integer('active_users_limit');
|
||||||
|
table.boolean('is_initialized').notNullable();
|
||||||
|
|
||||||
|
table.timestamp('created_at', true);
|
||||||
|
table.timestamp('updated_at', true);
|
||||||
|
});
|
||||||
|
|
||||||
|
const { is_initialized: isInitialized } = await knex('config').select('is_initialized').first();
|
||||||
|
|
||||||
|
await knex('internal_config').insert({
|
||||||
|
isInitialized,
|
||||||
|
id: 1,
|
||||||
|
createdAt: new Date().toISOString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return knex.schema.alterTable('config', (table) => {
|
||||||
|
table.dropColumn('is_initialized');
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.down = async (knex) => {
|
||||||
|
const { is_initialized: isInitialized } = await knex('internal_config')
|
||||||
|
.select('is_initialized')
|
||||||
|
.first();
|
||||||
|
|
||||||
|
await knex.schema.alterTable('config', (table) => {
|
||||||
|
table.boolean('is_initialized').notNullable().default(isInitialized);
|
||||||
|
});
|
||||||
|
|
||||||
|
await knex.schema.alterTable('config', (table) => {
|
||||||
|
table.boolean('is_initialized').notNullable().alter();
|
||||||
|
});
|
||||||
|
|
||||||
|
return knex.schema.dropTable('internal_config');
|
||||||
|
};
|
||||||
+41
-13
@@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
const bcrypt = require('bcrypt');
|
const bcrypt = require('bcrypt');
|
||||||
|
|
||||||
const buildData = () => {
|
const buildUserData = () => {
|
||||||
const data = {
|
const data = {
|
||||||
role: 'admin',
|
role: 'admin',
|
||||||
isSsoUser: false,
|
isSsoUser: false,
|
||||||
@@ -25,18 +25,35 @@ const buildData = () => {
|
|||||||
return data;
|
return data;
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.seed = async (knex) => {
|
const buildInternalConfigData = () => {
|
||||||
const email = process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase();
|
const data = {};
|
||||||
|
if (process.env.STORAGE_LIMIT) {
|
||||||
|
data.storageLimit = process.env.STORAGE_LIMIT;
|
||||||
|
}
|
||||||
|
if (process.env.ACTIVE_USERS_LIMIT) {
|
||||||
|
const activeUsersLimit = parseInt(process.env.ACTIVE_USERS_LIMIT, 10);
|
||||||
|
|
||||||
if (email) {
|
if (Number.isInteger(activeUsersLimit)) {
|
||||||
const data = buildData();
|
data.activeUsersLimit = activeUsersLimit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return data;
|
||||||
|
};
|
||||||
|
|
||||||
|
exports.seed = async (knex) => {
|
||||||
|
const defaultAdminEmail =
|
||||||
|
process.env.DEFAULT_ADMIN_EMAIL && process.env.DEFAULT_ADMIN_EMAIL.toLowerCase();
|
||||||
|
|
||||||
|
if (defaultAdminEmail) {
|
||||||
|
const userData = buildUserData();
|
||||||
|
|
||||||
let userId;
|
let userId;
|
||||||
try {
|
try {
|
||||||
[{ id: userId }] = await knex('user_account').insert(
|
[{ id: userId }] = await knex('user_account').insert(
|
||||||
{
|
{
|
||||||
...data,
|
...userData,
|
||||||
email,
|
email: defaultAdminEmail,
|
||||||
subscribeToOwnCards: false,
|
subscribeToOwnCards: false,
|
||||||
subscribeToCardWhenCommenting: true,
|
subscribeToCardWhenCommenting: true,
|
||||||
turnOffRecentCardHighlighting: false,
|
turnOffRecentCardHighlighting: false,
|
||||||
@@ -53,19 +70,30 @@ exports.seed = async (knex) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!userId) {
|
if (!userId) {
|
||||||
await knex('user_account').update(data).where('email', email);
|
await knex('user_account').update(data).where('email', defaultAdminEmail);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const activeUserLimit = parseInt(process.env.ACTIVE_USER_LIMIT, 10);
|
const internalConfigData = buildInternalConfigData();
|
||||||
|
|
||||||
if (!Number.isNaN(activeUserLimit)) {
|
let activeUsersLimit;
|
||||||
|
if (Object.keys(internalConfigData).length > 0) {
|
||||||
|
[{ active_users_limit: activeUsersLimit }] = await knex('internal_config')
|
||||||
|
.update(internalConfigData)
|
||||||
|
.returning('active_users_limit');
|
||||||
|
} else {
|
||||||
|
({ active_users_limit: activeUsersLimit } = await knex('internal_config')
|
||||||
|
.select('active_users_limit')
|
||||||
|
.first());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Number.isInteger(activeUsersLimit)) {
|
||||||
let orderByQuery;
|
let orderByQuery;
|
||||||
let orderByQueryValues;
|
let orderByQueryValues;
|
||||||
|
|
||||||
if (email) {
|
if (defaultAdminEmail) {
|
||||||
orderByQuery = 'CASE WHEN email = ? THEN 0 WHEN role = ? THEN 1 ELSE 2 END';
|
orderByQuery = 'CASE WHEN email = ? THEN 0 WHEN role = ? THEN 1 ELSE 2 END';
|
||||||
orderByQueryValues = [email, 'admin'];
|
orderByQueryValues = [defaultAdminEmail, 'admin'];
|
||||||
} else {
|
} else {
|
||||||
orderByQuery = 'CASE WHEN role = ? THEN 0 ELSE 1 END';
|
orderByQuery = 'CASE WHEN role = ? THEN 0 ELSE 1 END';
|
||||||
orderByQueryValues = 'admin';
|
orderByQueryValues = 'admin';
|
||||||
@@ -76,7 +104,7 @@ exports.seed = async (knex) => {
|
|||||||
.where('is_deactivated', false)
|
.where('is_deactivated', false)
|
||||||
.orderByRaw(orderByQuery, orderByQueryValues)
|
.orderByRaw(orderByQuery, orderByQueryValues)
|
||||||
.orderBy('id')
|
.orderBy('id')
|
||||||
.offset(activeUserLimit);
|
.offset(activeUsersLimit);
|
||||||
|
|
||||||
if (users.length > 0) {
|
if (users.length > 0) {
|
||||||
const userIds = users.map(({ id }) => id);
|
const userIds = users.map(({ id }) => id);
|
||||||
|
|||||||
Reference in New Issue
Block a user