mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-23 17:08:34 +00:00
Merge pull request #10626 from ToolJet/release/platformv19.2
Release Platform v19.2
This commit is contained in:
commit
b05fc6ed00
11 changed files with 420 additions and 263 deletions
|
|
@ -1,4 +1,4 @@
|
|||
import React from 'react';
|
||||
import React, { useState } from 'react';
|
||||
import { appService, appsService, authenticationService } from '@/_services';
|
||||
import Modal from 'react-bootstrap/Modal';
|
||||
import { toast } from 'react-hot-toast';
|
||||
|
|
@ -15,6 +15,7 @@ import { ToolTip } from '@/_components/ToolTip';
|
|||
import { TOOLTIP_MESSAGES } from '@/_helpers/constants';
|
||||
import { useAppDataStore } from '@/_stores/appDataStore';
|
||||
import { retrieveWhiteLabelText } from '@white-label/whiteLabelling';
|
||||
import InfoIcon from '@assets/images/icons/info.svg';
|
||||
|
||||
class ManageAppUsersComponent extends React.Component {
|
||||
constructor(props) {
|
||||
|
|
@ -32,6 +33,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
value: null,
|
||||
error: '',
|
||||
},
|
||||
isHovered: false,
|
||||
isSlugUpdated: false,
|
||||
};
|
||||
}
|
||||
|
|
@ -85,7 +87,6 @@ class ManageAppUsersComponent extends React.Component {
|
|||
toast.error(error);
|
||||
});
|
||||
};
|
||||
|
||||
toggleAppVisibility = () => {
|
||||
const newState = !this.props.isPublic;
|
||||
this.setState({
|
||||
|
|
@ -170,7 +171,13 @@ class ManageAppUsersComponent extends React.Component {
|
|||
});
|
||||
}
|
||||
};
|
||||
handleMouseEnter = () => {
|
||||
this.setState({ isHovered: true });
|
||||
};
|
||||
|
||||
handleMouseLeave = () => {
|
||||
this.setState({ isHovered: false });
|
||||
};
|
||||
render() {
|
||||
const { appId, isSlugVerificationInProgress, newSlug, isSlugUpdated } = this.state;
|
||||
|
||||
|
|
@ -178,66 +185,104 @@ class ManageAppUsersComponent extends React.Component {
|
|||
const shareableLink = appLink + (this.props.slug || appId);
|
||||
const slugButtonClass = !_.isEmpty(newSlug.error) ? 'is-invalid' : 'is-valid';
|
||||
const embeddableLink = `<iframe width="560" height="315" src="${appLink}${this.props.slug}" title="${this.whiteLabelText} app - ${this.props.slug}" frameborder="0" allowfullscreen></iframe>`;
|
||||
const shouldWeDisableShareModal = !this.props.isVersionReleased;
|
||||
const { isHovered } = this.state.isHovered;
|
||||
|
||||
return (
|
||||
<ToolTip
|
||||
message={TOOLTIP_MESSAGES.SHARE_URL_UNAVAILABLE}
|
||||
placement={!this.props.isVersionReleased ? 'bottom' : 'left'}
|
||||
show={shouldWeDisableShareModal}
|
||||
>
|
||||
<div
|
||||
title={!shouldWeDisableShareModal ? 'Share' : ''}
|
||||
className="manage-app-users editor-header-icon tj-secondary-btn"
|
||||
data-cy="share-button-link"
|
||||
<div title={'Share'} className="manage-app-users" data-cy="share-button-link">
|
||||
<span
|
||||
className="manage-app-users tj-secondary-btn editor-header-icon cursor-pointer"
|
||||
onClick={() => {
|
||||
this.validateThePreExistingSlugs();
|
||||
this.setState({ showModal: true });
|
||||
}}
|
||||
>
|
||||
<span
|
||||
className={cx('d-flex', {
|
||||
'share-disabled': shouldWeDisableShareModal,
|
||||
'share-disabled': false,
|
||||
})}
|
||||
onClick={() => {
|
||||
this.validateThePreExistingSlugs();
|
||||
!shouldWeDisableShareModal && this.setState({ showModal: true });
|
||||
}}
|
||||
>
|
||||
<SolidIcon name="share" width="14" className="cursor-pointer" fill="#3E63DD" />
|
||||
</span>
|
||||
<Modal
|
||||
show={this.state.showModal}
|
||||
size="lg"
|
||||
backdrop="static"
|
||||
centered={true}
|
||||
keyboard={true}
|
||||
animation={false}
|
||||
onEscapeKeyDown={this.hideModal}
|
||||
className={`app-sharing-modal animation-fade ${this.props.darkMode ? 'dark-theme' : ''}`}
|
||||
contentClassName={this.props.darkMode ? 'dark-theme' : ''}
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title data-cy="modal-header">{this.props.t('editor.share', 'Share')}</Modal.Title>
|
||||
<span onClick={this.hideModal} data-cy="modal-close-button">
|
||||
<SolidIcon name="remove" className="cursor-pointer" aria-label="Close" />
|
||||
</span>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{
|
||||
<div class="shareable-link-container">
|
||||
<div className="make-public mb-3">
|
||||
<div className="form-check form-switch d-flex align-items-center">
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
onClick={this.toggleAppVisibility}
|
||||
checked={this?.props?.isPublic}
|
||||
disabled={this.state.ischangingVisibility}
|
||||
data-cy="make-public-app-toggle"
|
||||
/>
|
||||
<span className="form-check-label field-name" data-cy="make-public-app-label">
|
||||
{this.props.t('editor.shareModal.makeApplicationPublic', 'Make application public')}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<Modal
|
||||
show={this.state.showModal}
|
||||
size="lg"
|
||||
backdrop="static"
|
||||
centered={true}
|
||||
keyboard={true}
|
||||
animation={false}
|
||||
onEscapeKeyDown={this.hideModal}
|
||||
className={`app-sharing-modal animation-fade ${this.props.darkMode ? 'dark-theme' : ''}`}
|
||||
contentClassName={this.props.darkMode ? 'dark-theme' : ''}
|
||||
>
|
||||
<Modal.Header>
|
||||
<Modal.Title data-cy="modal-header">{this.props.t('editor.share', 'Share')}</Modal.Title>
|
||||
<span onClick={this.hideModal} data-cy="modal-close-button">
|
||||
<SolidIcon name="remove" className="cursor-pointer" aria-label="Close" />
|
||||
</span>
|
||||
</Modal.Header>
|
||||
<Modal.Body>
|
||||
{
|
||||
<div class="shareable-link-container">
|
||||
<div className="make-public mb-3">
|
||||
<div className="form-check form-switch d-flex align-items-center">
|
||||
{this.props.isVersionReleased ? (
|
||||
<div>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
onClick={this.toggleAppVisibility}
|
||||
checked={this?.props?.isPublic}
|
||||
disabled={this.state.ischangingVisibility}
|
||||
data-cy="make-public-app-toggle"
|
||||
/>
|
||||
<span className="form-check-label field-name" data-cy="make-public-app-label">
|
||||
{this.props.t('editor.shareModal.makeApplicationPublic', 'Make application public')}
|
||||
</span>
|
||||
</div>
|
||||
) : (
|
||||
<div style={{ display: 'flex', alignItems: 'left', gap: '8px' }}>
|
||||
<ToolTip
|
||||
message={TOOLTIP_MESSAGES.RELEASE_VERSION_URL_UNAVAILABLE}
|
||||
placement={'top'}
|
||||
show={isHovered}
|
||||
>
|
||||
<div
|
||||
onMouseEnter={this.handleMouseEnter}
|
||||
onMouseLeave={this.handleMouseLeave}
|
||||
style={{
|
||||
width: '32px',
|
||||
height: '18px',
|
||||
marginLeft: '-40px',
|
||||
}}
|
||||
>
|
||||
<input
|
||||
className="form-check-input"
|
||||
type="checkbox"
|
||||
disabled
|
||||
style={{
|
||||
opacity: 0.3,
|
||||
cursor: 'default',
|
||||
margin: 0,
|
||||
padding: 0,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</ToolTip>
|
||||
|
||||
<span
|
||||
className="form-check-label field-name"
|
||||
data-cy="make-public-app-label"
|
||||
style={{ opacity: 0.6 }}
|
||||
>
|
||||
{this.props.t('editor.shareModal.makeApplicationPublic', 'Make application public')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{this.props.isVersionReleased ? (
|
||||
<div className="shareable-link tj-app-input mb-2">
|
||||
<label data-cy="shareable-app-link-label" className="field-name">
|
||||
{this.props.t('editor.shareModal.shareableLink', 'Shareable app link')}
|
||||
|
|
@ -259,6 +304,7 @@ class ManageAppUsersComponent extends React.Component {
|
|||
style={{ maxWidth: '150px' }}
|
||||
defaultValue={this.props.slug}
|
||||
data-cy="app-name-slug-input"
|
||||
disabled={!this.props.isVersionReleased}
|
||||
/>
|
||||
{isSlugVerificationInProgress && (
|
||||
<div className="icon-container">
|
||||
|
|
@ -344,60 +390,69 @@ class ManageAppUsersComponent extends React.Component {
|
|||
>{`URL-friendly 'slug' consists of lowercase letters, numbers, and hyphens`}</label>
|
||||
)}
|
||||
</div>
|
||||
{(this?.props?.isPublic || window?.public_config?.ENABLE_PRIVATE_APP_EMBED === 'true') && (
|
||||
<div className="tj-app-input">
|
||||
<label className="field-name" data-cy="iframe-link-label">
|
||||
Embedded app link
|
||||
</label>
|
||||
<span className={`tj-text-input justify-content-between ${this.props.darkMode ? 'dark' : ''}`}>
|
||||
<span data-cy="iframe-link">{embeddableLink}</span>
|
||||
<span className="copy-container">
|
||||
<CopyToClipboard
|
||||
text={embeddableLink}
|
||||
onCopy={() => toast.success('Link copied to clipboard')}
|
||||
>
|
||||
<svg
|
||||
className="cursor-pointer"
|
||||
width="17"
|
||||
height="18"
|
||||
viewBox="0 0 17 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
data-cy="iframe-link-copy-button"
|
||||
>
|
||||
<path
|
||||
d="M9.11154 5.18031H5.88668V4.83302C5.88668 3.29859 7.13059 2.05469 8.66502 2.05469H12.8325C14.3669 2.05469 15.6109 3.29859 15.6109 4.83302V9.00052C15.6109 10.535 14.3669 11.7789 12.8325 11.7789H12.4852V8.554C12.4852 6.69076 10.9748 5.18031 9.11154 5.18031Z"
|
||||
fill="#889096"
|
||||
/>
|
||||
<path
|
||||
d="M8.66502 15.9464H4.49752C2.96309 15.9464 1.71918 14.7025 1.71918 13.168V9.00052C1.71918 7.46609 2.96309 6.22219 4.49752 6.22219H8.66502C10.1994 6.22219 11.4434 7.46609 11.4434 9.00052V13.168C11.4434 14.7025 10.1994 15.9464 8.66502 15.9464Z"
|
||||
fill="#889096"
|
||||
/>
|
||||
</svg>
|
||||
</CopyToClipboard>
|
||||
</span>
|
||||
</span>
|
||||
) : (
|
||||
<div className="shareable-link tj-app-input mb-2">
|
||||
<label data-cy="shareable-app-link-label" className="field-name">
|
||||
{this.props.t('editor.shareModal.shareableLink', 'Shareable app link')}
|
||||
</label>
|
||||
<div className="empty-version">
|
||||
<InfoIcon style={{ width: '12px', marginRight: '5px' }} />
|
||||
<span>This version has not been released yet</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
</Modal.Body>
|
||||
</div>
|
||||
)}
|
||||
|
||||
<Modal.Footer className="manage-app-users-footer">
|
||||
{this.isUserAdmin && (
|
||||
<Link
|
||||
to={getPrivateRoute('workspace_settings')}
|
||||
target="_blank"
|
||||
className={`btn border-0 default-secondary-button float-right1`}
|
||||
data-cy="manage-users-button"
|
||||
>
|
||||
Manage users
|
||||
</Link>
|
||||
)}
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
</ToolTip>
|
||||
{((this?.props?.isVersionReleased && this?.props?.isPublic) ||
|
||||
window?.public_config?.ENABLE_PRIVATE_APP_EMBED === 'true') && (
|
||||
<div className="tj-app-input">
|
||||
<label className="field-name" data-cy="iframe-link-label">
|
||||
Embedded app link
|
||||
</label>
|
||||
<span className={`tj-text-input justify-content-between ${this.props.darkMode ? 'dark' : ''}`}>
|
||||
<span data-cy="iframe-link">{embeddableLink}</span>
|
||||
<span className="copy-container">
|
||||
<CopyToClipboard text={embeddableLink} onCopy={() => toast.success('Link copied to clipboard')}>
|
||||
<svg
|
||||
className="cursor-pointer"
|
||||
width="17"
|
||||
height="18"
|
||||
viewBox="0 0 17 18"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
data-cy="iframe-link-copy-button"
|
||||
>
|
||||
<path
|
||||
d="M9.11154 5.18031H5.88668V4.83302C5.88668 3.29859 7.13059 2.05469 8.66502 2.05469H12.8325C14.3669 2.05469 15.6109 3.29859 15.6109 4.83302V9.00052C15.6109 10.535 14.3669 11.7789 12.8325 11.7789H12.4852V8.554C12.4852 6.69076 10.9748 5.18031 9.11154 5.18031Z"
|
||||
fill="#889096"
|
||||
/>
|
||||
<path
|
||||
d="M8.66502 15.9464H4.49752C2.96309 15.9464 1.71918 14.7025 1.71918 13.168V9.00052C1.71918 7.46609 2.96309 6.22219 4.49752 6.22219H8.66502C10.1994 6.22219 11.4434 7.46609 11.4434 9.00052V13.168C11.4434 14.7025 10.1994 15.9464 8.66502 15.9464Z"
|
||||
fill="#889096"
|
||||
/>
|
||||
</svg>
|
||||
</CopyToClipboard>
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
}
|
||||
</Modal.Body>
|
||||
|
||||
<Modal.Footer className="manage-app-users-footer">
|
||||
{this.isUserAdmin && (
|
||||
<Link
|
||||
to={getPrivateRoute('workspace_settings')}
|
||||
target="_blank"
|
||||
className={`btn border-0 default-secondary-button float-right1`}
|
||||
data-cy="manage-users-button"
|
||||
>
|
||||
Manage users
|
||||
</Link>
|
||||
)}
|
||||
</Modal.Footer>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,7 +31,6 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
|
|||
const [addingDataSource, setAddingDataSource] = useState(false);
|
||||
const [suggestingDataSource, setSuggestingDataSource] = useState(false);
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const { admin } = authenticationService.currentSessionValue;
|
||||
const marketplaceEnabled = admin && window.public_config?.ENABLE_MARKETPLACE_FEATURE == 'true';
|
||||
const [modalProps, setModalProps] = useState({
|
||||
|
|
@ -296,34 +295,6 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
|
|||
};
|
||||
|
||||
const renderCardGroup = (source, type) => {
|
||||
if (type === 'Plugins' && source.length === 0) {
|
||||
return (
|
||||
<div className="add-plugins-container">
|
||||
<div className="warning-container mb-2">
|
||||
<SolidIcon name="warning" />
|
||||
</div>
|
||||
<div className="tj-text-sm font-weight-500 tj-text">No plugins added</div>
|
||||
{admin && (
|
||||
<>
|
||||
<div className="tj-text-xsm font-weight-400 mt-2 mb-3">
|
||||
Browse through plugins in marketplace to add them as a Data Source.{' '}
|
||||
</div>
|
||||
<ButtonSolid
|
||||
onClick={() => {
|
||||
marketplaceEnabled
|
||||
? navigate('/integrations')
|
||||
: toast.error('Please enable marketplace to add plugins');
|
||||
}}
|
||||
style={{ margin: 'auto' }}
|
||||
variant="secondary"
|
||||
>
|
||||
Add plugins
|
||||
</ButtonSolid>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
const addDataSourceBtn = (item) => (
|
||||
<ButtonSolid
|
||||
disabled={addingDataSource}
|
||||
|
|
@ -366,6 +337,36 @@ export const GlobalDataSourcesPage = ({ darkMode = false, updateSelectedDatasour
|
|||
titleClassName={'datasource-card-title'}
|
||||
/>
|
||||
))}
|
||||
{type === 'Plugins' && (
|
||||
<div style={{ height: '122px', width: '164px' }} className={`col-md-2 mb-4 `}>
|
||||
<div
|
||||
className="card add-plugin-card"
|
||||
role="button"
|
||||
onClick={() => {
|
||||
if (marketplaceEnabled) {
|
||||
window.open('/integrations', '_blank');
|
||||
} else {
|
||||
toast.error('Please enable marketplace to add plugins');
|
||||
}
|
||||
}}
|
||||
data-cy={`data-source-add-plugin`}
|
||||
>
|
||||
<div className="card-body">
|
||||
<center style={{ paddingTop: '5px' }}>
|
||||
<SolidIcon
|
||||
name="plus"
|
||||
fill={'var(--icon-default)'}
|
||||
width={35}
|
||||
height={35}
|
||||
style={{ marginBottom: '8px' }}
|
||||
/>
|
||||
<br></br>
|
||||
<span className={`datasource-card-title add-plugin-card-title`}>Add plugin</span>
|
||||
</center>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
|
|
|||
6
frontend/src/OrganizationSettingsPage/constant.js
Normal file
6
frontend/src/OrganizationSettingsPage/constant.js
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export const workspaceSettingsLinks = [
|
||||
{ id: 'users', name: 'Users', route: 'users', conditions: ['admin'] },
|
||||
{ id: 'groups', name: 'Groups', route: 'groups', conditions: ['admin'] },
|
||||
{ id: 'workspacelogin', name: 'Workspace login', route: 'workspace-login', conditions: ['admin'] },
|
||||
{ id: 'workspacevariables', name: 'Workspace variables', route: 'workspace-variables', conditions: ['admin'] },
|
||||
];
|
||||
|
|
@ -1,57 +1,59 @@
|
|||
import React, { useEffect, useState, useContext } from 'react';
|
||||
import cx from 'classnames';
|
||||
import { useParams, Outlet, Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
import { Outlet, Link, useNavigate, useLocation } from 'react-router-dom';
|
||||
|
||||
import Layout from '@/_ui/Layout';
|
||||
import { authenticationService } from '@/_services';
|
||||
import { BreadCrumbContext } from '../App/App';
|
||||
import FolderList from '@/_ui/FolderList/FolderList';
|
||||
import { OrganizationList } from '../_components/OrganizationManager/List';
|
||||
import { getWorkspaceId } from '@/_helpers/utils';
|
||||
import { getSubpath } from '@/_helpers/routes';
|
||||
import { workspaceSettingsLinks } from './constant';
|
||||
|
||||
export function OrganizationSettings(props) {
|
||||
const [admin, setAdmin] = useState(authenticationService.currentSessionValue?.admin);
|
||||
const [selectedTab, setSelectedTab] = useState(admin ? 'Users & permissions' : 'manageEnvVars');
|
||||
const admin = authenticationService.currentSessionValue?.admin;
|
||||
const [selectedTab, setSelectedTab] = useState(admin ? workspaceSettingsLinks[0].id : 'workspacevariables');
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const { updateSidebarNAV } = useContext(BreadCrumbContext);
|
||||
const { workspaceId } = useParams();
|
||||
const [conditionObj, setConditionObj] = useState({ admin: authenticationService.currentSessionValue?.admin });
|
||||
|
||||
const sideBarNavs = ['Users', 'Groups', 'Workspace login', 'Workspace variables'];
|
||||
const defaultOrgName = (groupName) => {
|
||||
switch (groupName) {
|
||||
case 'users':
|
||||
return 'Users';
|
||||
case 'groups':
|
||||
return 'Groups';
|
||||
case 'workspace-login':
|
||||
return 'Workspace login';
|
||||
case 'workspace-variables':
|
||||
return 'Workspace variables';
|
||||
default:
|
||||
return groupName;
|
||||
const checkConditions = (conditions, conditionsObj) => {
|
||||
if (!conditions || conditions.length === 0) {
|
||||
return true;
|
||||
}
|
||||
return conditions.every((condition) => conditionsObj?.[condition] === true);
|
||||
};
|
||||
|
||||
//Filtered Links from the workspace settings links array
|
||||
const filteredLinks = () =>
|
||||
workspaceSettingsLinks.filter((item) => {
|
||||
return checkConditions(item.conditions, conditionObj);
|
||||
});
|
||||
|
||||
const getMenuFromRoute = (route) => {
|
||||
return workspaceSettingsLinks?.find((e) => e.route === route) || {};
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
const subscription = authenticationService.currentSession.subscribe((newOrd) => {
|
||||
setAdmin(newOrd?.admin);
|
||||
setConditionObj({ admin: newOrd?.admin });
|
||||
});
|
||||
admin ? updateSidebarNAV('Users') : updateSidebarNAV('Workspace variables');
|
||||
|
||||
() => subscription.unsubsciption();
|
||||
const selectedTabFromRoute = location.pathname.split('/').pop();
|
||||
if (selectedTabFromRoute === 'workspace-settings') {
|
||||
setSelectedTab(admin ? 'Users' : 'Workspace variables');
|
||||
const subPath = getSubpath();
|
||||
const path = subPath ? `${subPath}/${workspaceId}/workspace-settings` : `/${workspaceId}/workspace-settings`;
|
||||
window.location.href = admin ? `${path}/users` : `${path}/workspace-variables`;
|
||||
// No Sub routes added loading first one
|
||||
setSelectedTab(admin ? workspaceSettingsLinks[0].id : 'workspacevariables');
|
||||
} else {
|
||||
setSelectedTab(defaultOrgName(selectedTabFromRoute));
|
||||
setSelectedTab(getMenuFromRoute(selectedTabFromRoute)?.id);
|
||||
}
|
||||
updateSidebarNAV(defaultOrgName(selectedTabFromRoute));
|
||||
}, [navigate, workspaceId, authenticationService.currentSessionValue?.admin]);
|
||||
|
||||
return () => subscription.unsubscribe();
|
||||
}, [authenticationService.currentSessionValue?.admin]);
|
||||
|
||||
useEffect(() => {
|
||||
const menu = workspaceSettingsLinks?.find((m) => m.id === selectedTab);
|
||||
updateSidebarNAV(menu?.name || '');
|
||||
navigate(menu?.route || '');
|
||||
}, [selectedTab]);
|
||||
|
||||
return (
|
||||
<Layout switchDarkMode={props.switchDarkMode} darkMode={props.darkMode}>
|
||||
|
|
@ -59,12 +61,11 @@ export function OrganizationSettings(props) {
|
|||
<div className="row gx-0">
|
||||
<div className="organization-page-sidebar col ">
|
||||
<div className="workspace-nav-list-wrap">
|
||||
{sideBarNavs.map((item, index) => {
|
||||
{filteredLinks().map((item, index) => {
|
||||
const Wrapper = ({ children }) => <>{children}</>;
|
||||
return (
|
||||
<Wrapper key={index}>
|
||||
<Link
|
||||
to={`/${workspaceId}/workspace-settings/${item.toLowerCase().replace(/\s+/g, '-')}`} // Update the URL path here
|
||||
key={index}
|
||||
style={{
|
||||
textDecoration: 'none',
|
||||
|
|
@ -74,30 +75,26 @@ export function OrganizationSettings(props) {
|
|||
backgroundColor: 'inherit',
|
||||
}}
|
||||
>
|
||||
{admin && (
|
||||
<FolderList
|
||||
className="workspace-settings-nav-items"
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setSelectedTab(defaultOrgName(item));
|
||||
if (item == 'Users') updateSidebarNAV('Users');
|
||||
else updateSidebarNAV(item);
|
||||
}}
|
||||
selectedItem={selectedTab == defaultOrgName(item)}
|
||||
renderBadgeForItems={['Workspace constants']}
|
||||
renderBadge={() => (
|
||||
<span
|
||||
style={{ width: '40px', textTransform: 'lowercase' }}
|
||||
className="badge bg-color-primary badge-pill"
|
||||
>
|
||||
new
|
||||
</span>
|
||||
)}
|
||||
dataCy={item.toLowerCase().replace(/\s+/g, '-')}
|
||||
>
|
||||
{item}
|
||||
</FolderList>
|
||||
)}
|
||||
<FolderList
|
||||
className="workspace-settings-nav-items"
|
||||
key={index}
|
||||
onClick={() => {
|
||||
setSelectedTab(item.id);
|
||||
}}
|
||||
selectedItem={selectedTab == item.id}
|
||||
renderBadgeForItems={[]}
|
||||
renderBadge={() => (
|
||||
<span
|
||||
style={{ width: '40px', textTransform: 'lowercase' }}
|
||||
className="badge bg-color-primary badge-pill"
|
||||
>
|
||||
new
|
||||
</span>
|
||||
)}
|
||||
dataCy={item.name.toLowerCase().replace(/\s+/g, '-')}
|
||||
>
|
||||
{item.name}
|
||||
</FolderList>
|
||||
</Link>
|
||||
</Wrapper>
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
import React, { useState } from 'react';
|
||||
import React, { useState, useRef, useEffect } from 'react';
|
||||
import { organizationService } from '@/_services';
|
||||
import AlertDialog from '@/_ui/AlertDialog';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
|
@ -18,10 +18,11 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
|
|||
const [isSlugDisabled, setSlugDisabled] = useState(true);
|
||||
const darkMode = localStorage.getItem('darkMode') === 'true';
|
||||
const { t } = useTranslation();
|
||||
const isSlugSet = useRef(false); // Flag to track if slug has been initially set
|
||||
const sluginput = useRef('');
|
||||
|
||||
const createOrganization = () => {
|
||||
let emptyError = false;
|
||||
|
||||
[name, slug].map((field, index) => {
|
||||
if (!field?.value?.trim()) {
|
||||
index === 0
|
||||
|
|
@ -77,7 +78,6 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
|
|||
!(field === 'slug'),
|
||||
field === 'slug'
|
||||
);
|
||||
|
||||
/* If the basic validation is passing. then check the uniqueness */
|
||||
if (error?.status === true) {
|
||||
try {
|
||||
|
|
@ -92,7 +92,6 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
|
|||
};
|
||||
}
|
||||
}
|
||||
|
||||
const disabled = !error?.status;
|
||||
const updatedValue = {
|
||||
value,
|
||||
|
|
@ -126,17 +125,47 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
|
|||
setShowCreateOrg(false);
|
||||
setNameDisabled(true);
|
||||
setSlugDisabled(true);
|
||||
isSlugSet.current = false;
|
||||
};
|
||||
|
||||
const delayedSlugChange = _.debounce(async (value) => {
|
||||
setSlugProgress(true);
|
||||
await handleInputChange(value, 'slug');
|
||||
}, 300);
|
||||
|
||||
const delayedNameChange = _.debounce(async (value) => {
|
||||
setWorkspaceNameProgress(true);
|
||||
await handleInputChange(value, 'name');
|
||||
}, 300);
|
||||
useEffect(() => {
|
||||
if (!isSlugSet.current && name.value && !slugProgress && !workspaceNameProgress) {
|
||||
const defaultValue =
|
||||
name?.value
|
||||
.replace(/\s+/g, '')
|
||||
.toLowerCase()
|
||||
.replace(/[^a-z0-9-\s]/g, '') || '';
|
||||
setSlug({ value: defaultValue, error: '' });
|
||||
|
||||
const checkWorkspaceUniqueness = async () => {
|
||||
try {
|
||||
await organizationService.checkWorkspaceUniqueness(null, defaultValue);
|
||||
sluginput.current.value = defaultValue;
|
||||
} catch (errResponse) {
|
||||
let error = {
|
||||
status: false,
|
||||
errorMsg: errResponse?.error,
|
||||
};
|
||||
setSlug({ value: defaultValue, error: error?.errorMsg });
|
||||
}
|
||||
};
|
||||
checkWorkspaceUniqueness();
|
||||
setSlugDisabled(false);
|
||||
setSlugProgress(false);
|
||||
}
|
||||
if (slugProgress && !isSlugSet.current) {
|
||||
// this is to denote that the user has tried editing the slug -- so now slug and name are independent of each other
|
||||
isSlugSet.current = true;
|
||||
}
|
||||
}, [name.value, slug.value, slugProgress, workspaceNameProgress, isSlugSet]);
|
||||
|
||||
const isDisabled = isCreating || isNameDisabled || isSlugDisabled || slugProgress || workspaceNameProgress;
|
||||
|
||||
|
|
@ -168,6 +197,8 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
|
|||
<label className="label tj-input-error" data-cy="workspace-error-label">
|
||||
{name?.error || ''}
|
||||
</label>
|
||||
) : name.value && !workspaceNameProgress ? (
|
||||
<label className="label label-success" data-cy="slug-sucess-label">{`Workspace name accepted!`}</label>
|
||||
) : (
|
||||
<label className="label label-info" data-cy="workspace-name-info-label">
|
||||
Name must be unique and max 50 characters
|
||||
|
|
@ -183,6 +214,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
|
|||
className={`form-control ${slug?.error ? 'is-invalid' : 'is-valid'}`}
|
||||
placeholder={t('header.organization.workspaceSlug', 'Unique workspace slug')}
|
||||
disabled={isCreating}
|
||||
ref={sluginput}
|
||||
maxLength={50}
|
||||
onChange={async (e) => {
|
||||
e.persist();
|
||||
|
|
@ -203,6 +235,7 @@ export const CreateOrganization = ({ showCreateOrg, setShowCreateOrg }) => {
|
|||
</svg>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{slug?.error ? (
|
||||
<label className="label tj-input-error" data-cy="input-label-error">
|
||||
{slug?.error || ''}
|
||||
|
|
|
|||
|
|
@ -91,6 +91,7 @@ export const ERROR_MESSAGES = {
|
|||
|
||||
export const TOOLTIP_MESSAGES = {
|
||||
SHARE_URL_UNAVAILABLE: 'Share URL is unavailable until current version is released',
|
||||
RELEASE_VERSION_URL_UNAVAILABLE: 'Release the version to make it public',
|
||||
};
|
||||
|
||||
export const DATA_SOURCE_TYPE = {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
export const APP_ERROR_TYPE = {
|
||||
IMPORT_EXPORT_SERVICE: {
|
||||
UNSUPPORTED_VERSION_ERROR: "Can't import higher version application to lower version",
|
||||
UNSUPPORTED_VERSION_ERROR: 'Apps built on later versions of ToolJet cannot be imported',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
|
|
@ -7,13 +7,15 @@ import { useEditorStore } from '@/_stores/editorStore';
|
|||
import { useQueryPanelStore } from '@/_stores/queryPanelStore';
|
||||
import update from 'immutability-helper';
|
||||
const { diff } = require('deep-object-diff');
|
||||
|
||||
import { useAppDataStore } from './appDataStore';
|
||||
import { authenticationService } from '@/_services';
|
||||
const initialState = {
|
||||
queries: {},
|
||||
components: {},
|
||||
globals: {
|
||||
theme: { name: 'light' },
|
||||
urlparams: null,
|
||||
currentUser: {},
|
||||
},
|
||||
errors: {},
|
||||
variables: {},
|
||||
|
|
@ -53,9 +55,22 @@ export const useCurrentStateStore = create(
|
|||
},
|
||||
setEditorReady: (isEditorReady) => set({ isEditorReady }),
|
||||
initializeCurrentStateOnVersionSwitch: () => {
|
||||
//fetch user for current app
|
||||
const currentUser = useAppDataStore.getState().currentUser;
|
||||
const userVars = {
|
||||
email: currentUser?.email,
|
||||
firstName: currentUser?.first_name,
|
||||
lastName: currentUser?.last_name,
|
||||
groups: authenticationService.currentSessionValue.group_permissions?.map((group) => group.group),
|
||||
ssoUserInfo: currentUser?.sso_user_info,
|
||||
};
|
||||
const newInitialState = {
|
||||
...initialState,
|
||||
constants: get().constants,
|
||||
globals: {
|
||||
...get().globals,
|
||||
currentUser: userVars,
|
||||
},
|
||||
};
|
||||
set({ ...newInitialState }, false, {
|
||||
type: 'INITIALIZE_CURRENT_STATE_ON_VERSION_SWITCH',
|
||||
|
|
|
|||
|
|
@ -300,4 +300,37 @@
|
|||
border-radius: 6px;
|
||||
margin: auto;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.add-plugin-card{
|
||||
border: 1px dashed var(--border-default, #CCD1D5);
|
||||
background-color: var(--interactive-weak);
|
||||
|
||||
&:hover{
|
||||
border: 1px dashed var(--border-strong, #CCD1D5);
|
||||
background-color: var(--interactive-default);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
.add-plugin-card-title{
|
||||
font-weight: 500;
|
||||
font-size: 14px;
|
||||
line-height: 20px;
|
||||
color: var(--text-default);
|
||||
}
|
||||
|
||||
|
||||
.dark-theme{
|
||||
.add-plugin-card{
|
||||
border: 1px dashed var(--border-default, #CCD1D5);
|
||||
background-color: var(--interactive-weak);
|
||||
|
||||
&:hover{
|
||||
border: 1px dashed var(--border-strong, #CCD1D5);
|
||||
background-color: var(--interactive-default);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +1,94 @@
|
|||
input.form-control,
|
||||
textarea,
|
||||
.input-control {
|
||||
gap: 16px !important;
|
||||
background: var(--base) !important;
|
||||
border: 1px solid var(--slate7) !important;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 4px !important;
|
||||
color: var(--slate12) !important;
|
||||
transition: none;
|
||||
height: 35px;
|
||||
padding-left: 0.4375rem;
|
||||
padding-right: 0.4375rem;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
overflow-x: 'auto';
|
||||
white-space: 'nowrap';
|
||||
textarea,
|
||||
.input-control {
|
||||
gap: 16px !important;
|
||||
background: var(--base) !important;
|
||||
border: 1px solid var(--slate7) !important;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 4px !important;
|
||||
color: var(--slate12) !important;
|
||||
transition: none;
|
||||
height: 35px;
|
||||
padding-left: 0.4375rem;
|
||||
padding-right: 0.4375rem;
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
overflow-x: 'auto';
|
||||
white-space: 'nowrap';
|
||||
|
||||
|
||||
&:hover {
|
||||
background: var(--slate1) !important;
|
||||
border: 1px solid var(--slate8) !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background: var(--indigo2) !important;
|
||||
border: 1px solid var(--indigo9) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.input-error-border {
|
||||
border-color: #DB4324 !important;
|
||||
}
|
||||
|
||||
&:-webkit-autofill {
|
||||
box-shadow: 0 0 0 1000px var(--base) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
|
||||
&:hover {
|
||||
background: var(--slate1) !important;
|
||||
border: 1px solid var(--slate8) !important;
|
||||
-webkit-box-shadow: none !important;
|
||||
box-shadow: none !important;
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 1000px var(--slate1) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
background: var(--indigo2) !important;
|
||||
border: 1px solid var(--indigo9) !important;
|
||||
box-shadow: none !important;
|
||||
}
|
||||
|
||||
&.input-error-border {
|
||||
border-color: #DB4324 !important;
|
||||
}
|
||||
|
||||
&:-webkit-autofill {
|
||||
box-shadow: 0 0 0 1000px var(--base) inset !important;
|
||||
box-shadow: 0 0 0 1000px var(--indigo2) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1000px var(--slate1) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
}
|
||||
|
||||
&:focus-visible {
|
||||
box-shadow: 0 0 0 1000px var(--indigo2) inset !important;
|
||||
-webkit-text-fill-color: var(--slate12) !important;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.empty-key-value {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 625px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
color: #687076;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
border: 1px dashed #E6E8EB;
|
||||
}
|
||||
}
|
||||
|
||||
.trash {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: 'center';
|
||||
align-items: 'center';
|
||||
}
|
||||
|
||||
.empty-key-value {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: 625px;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
color: #687076;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
border: 1px dashed #E6E8EB;
|
||||
}
|
||||
|
||||
.trash {
|
||||
height: 32px;
|
||||
display: flex;
|
||||
justify-content: 'center';
|
||||
align-items: 'center';
|
||||
}
|
||||
|
||||
.empty-version {
|
||||
border-radius: 6px;
|
||||
padding: 10px;
|
||||
text-align: center;
|
||||
width: auto;
|
||||
height: 32px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 10px;
|
||||
color: #687076;
|
||||
font-size: 12px;
|
||||
font-weight: 400;
|
||||
line-height: 20px;
|
||||
border: 1px dashed #E6E8EB;
|
||||
}
|
||||
|
|
@ -1,5 +1,5 @@
|
|||
export const APP_ERROR_TYPE = {
|
||||
IMPORT_EXPORT_SERVICE: {
|
||||
UNSUPPORTED_VERSION_ERROR: "Can't import higher version application to lower version",
|
||||
UNSUPPORTED_VERSION_ERROR: 'Apps built on later versions of ToolJet cannot be imported',
|
||||
},
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue