mirror of
https://github.com/ToolJet/ToolJet
synced 2026-05-24 09:28:31 +00:00
commit
7b51a11597
16 changed files with 476 additions and 52 deletions
|
|
@ -74,9 +74,10 @@ Follow these steps to setup and run ToolJet on Ubuntu. Open terminal and run the
|
|||
```bash
|
||||
cd ./frontend && npm start
|
||||
```
|
||||
|
||||
|
||||
|
||||
:::info
|
||||
The client will start running on the port 8082, you can access the client by visiting: [https://localhost:8082](https://localhost:8082)
|
||||
:::
|
||||
|
||||
9. Create login credentials
|
||||
|
||||
|
|
|
|||
|
|
@ -75,7 +75,7 @@ All the plugins live under the `/plugins` directory. The structure of a plugin l
|
|||
manifest.json
|
||||
```
|
||||
|
||||
5. Add data source config paramets to manifest.json
|
||||
5. Add data source config parameters to manifest.json
|
||||
|
||||
Our BigQuery plugin needs private key of a GCP service account to connect to BigQuery. Let's add `private_key` as a property for the data source.
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
id: introduction
|
||||
title: Introduction
|
||||
description: ToolJet is an **open-source low-code framework** to build and deploy custom internal tools. ToolJet can connect to your data sources such as databases ( PostgreSQL, MongoDB, MS SQL Server, Snowflake, , BigQuery, etc ), API/GraphQL endpoints, SaaS tools ( Airtable, Stripe, Google Sheets, etc ) and cloud object storage services ( AWS S3, Google Cloud Storage and Minio ). Once the data sources are connected, ToolJet can run queries on these data sources to fetch and update data. The data fetched from data sources can be visualised and modified using the UI widgets such as tables, charts, forms, etc.
|
||||
description: ToolJet is an **open-source low-code framework** to build and deploy custom internal tools. ToolJet can connect to your data sources such as databases (PostgreSQL, MongoDB, MS SQL Server, Snowflake, , BigQuery, etc), API/GraphQL endpoints, SaaS tools (Airtable, Stripe, Google Sheets, etc) and cloud object storage services (AWS S3, Google Cloud Storage and Minio). Once the data sources are connected, ToolJet can run queries on these data sources to fetch and update data. The data fetched from data sources can be visualised and modified using the UI widgets such as tables, charts, forms, etc.
|
||||
slug: /
|
||||
---
|
||||
|
||||
|
|
|
|||
|
|
@ -69,6 +69,6 @@ If you want to seed the database with a sample user, please SSH into a pod and r
|
|||
|
||||
This seeds the database with a default user with the following credentials:
|
||||
|
||||
**emai**: `dev@tooljet.io`
|
||||
**email**: `dev@tooljet.io`
|
||||
|
||||
**password**: `password`
|
||||
|
|
|
|||
|
|
@ -20,4 +20,4 @@ Self-hosted version of ToolJet pings our server to fetch the latest product upda
|
|||
|
||||
ToolJet tracks anonymous usage data such as page loads and clicks. ToolJet tracks only the events and doesn't capture data from data sources.
|
||||
|
||||
Tracking can be disabled by setting the value environment variable `ENABLE_TRACKING` to `0`.
|
||||
Tracking can be disabled by setting the value [environment variable](/docs/setup/env-vars/) ENABLE_TRACKING to 0.
|
||||
|
|
|
|||
|
|
@ -26,11 +26,11 @@ You will be prompted to select the data source that you wish to add. Let's selec
|
|||
The name of the data source must be unique (within the app) and can be changed by clicking on the data source name at the top of the prompt. Click on `Test Connection` button to verify the connection, this might take a couple of minutes. Once verified, save the data source.
|
||||
|
||||
:::tip
|
||||
If you are using ToolJet cloud and if your data source is not publicly accessible, please white-list our IP address ( shown while creating a new data source ).
|
||||
If you are using ToolJet cloud (https://app.tooljet.com) and if your data source is not publicly accessible, please white-list our IP address ( shown while creating a new data source ).
|
||||
:::
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||
<img className="screenshot-full" src="/img/tutorial/adding-datasource/postgres.png" alt="postgre add datasource" />
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -26,6 +26,6 @@ You will be redirected to the visual app editor once the app has been created. C
|
|||
|
||||
The main components of an app:
|
||||
|
||||
- **[Widgets](https://docs.tooljet.com/docs/tutorial/adding-widget)** - UI components such as tables, buttons, dropdowns.
|
||||
- **[Components](https://docs.tooljet.com/docs/tutorial/adding-widget)** - UI components such as tables, buttons, dropdowns.
|
||||
- **[Data sources](https://docs.tooljet.com/docs/tutorial/adding-a-datasource)** - ToolJet can connect to databases, APIs and external services to fetch and modify data.
|
||||
- **[Queries](https://docs.tooljet.com/docs/tutorial/building-queries)** - Queries are used to access the connected data sources.
|
||||
|
|
@ -5,42 +5,37 @@ title: Multi-Workspace
|
|||
|
||||
# Multi-Workspace
|
||||
|
||||
User can create their own workspaces, user who created workspace will be having admin privileges for the workspace.
|
||||
|
||||
|
||||
Users can create their own workspaces, a user who created workspace will be having **Admin** privileges for the workspace.
|
||||
|
||||
<img className="screenshot-full" src="/img/multiworkspace/multi-workspace.gif" alt="multi workspace" />
|
||||
|
||||
|
||||
## Hierarchy
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||
|
||||
|
||||
<img className="screenshot-full" src="/img/multiworkspace/Tooljet-workspace.png" alt="tooljet workspace" />
|
||||
|
||||
|
||||
</div>
|
||||
|
||||
## Permissions
|
||||
|
||||
- The administrator can manage [users and groups](/docs/tutorial/manage-users-groups) of each workspace
|
||||
- Applications and settings can not be shared between workspaces
|
||||
- A user authorised to login to Tooljet will not have access to all workspaces, Usesr should be invited or signed up to a workspace to log-in to it.
|
||||
- A user authorised to login to ToolJet will not have access to all workspaces, User should be invited or signed up to a workspace to access it.
|
||||
|
||||
## Enabling Multi-Workspace
|
||||
|
||||
Set environment variable **DISABLE_MULTI_WORKSPACE** value to **false** to enable the feature, and **true** to disable it.
|
||||
Set environment variable **DISABLE_MULTI_WORKSPACE** value to **false** to enable the feature, and **true** to disable it.
|
||||
|
||||
### When enabled
|
||||
|
||||
- When Multi-Workspace feature is enabled, user should login with username and password to log in to Tooljet.
|
||||
- When Multi-Workspace feature is enabled, user should login with username and password to log in to ToolJet.
|
||||
- Administrator can configure authentication methods for their workspaces.
|
||||
- If password login is enabled, switching to the workspace will happen without any other authorization since the user is already authorized with password login.
|
||||
- User logged in to Toojet and trying to switch to a workspace where SSO is enabled and password login is disabled, will be redirected to workspace login page and enabled SSO options will be shown
|
||||
- User logged in to TooJet and trying to switch to a workspace where SSO is enabled and password login is disabled, will be redirected to workspace login page and enabled SSO options will be shown
|
||||
- User can directly login to a workspace using workspace login URL, Administrator can view the URL **Manage SSO -> General Settings -> Login URL**.
|
||||
|
||||
### When disabled
|
||||
|
||||
- If Multi-Workspace is disabled, Create workspace feature won’t be available.
|
||||
- No separate login page for workspace and SSO configured for the workspace will be reflected to the main login page/login.
|
||||
- No separate login page for workspace and SSO configured for the workspace will be reflected to the main login page/login.
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ Workspace Variables are the variables with some value(usually tokens/secret keys
|
|||
Suppose there is an `API key` or a value that you want to use in the queries or widgets in the multiple apps of the same Workspace then the Workspace admin or the user with permissions can add an environment variable.
|
||||
|
||||
#### Adding the environment variable
|
||||
- Go to the ToolJet Dashboard, and click on the dropdown on the navigation bar to show `Workspace` options
|
||||
- Go to ToolJet Dashboard, and click on the dropdown on the navigation bar to show `Workspace` options
|
||||
- Select `Manage Environment Variables`
|
||||
- Click on `Add New Variable` button
|
||||
- Give a `Name` to the variable, set the value, choose `Type`, toggle `Encryption`, and click **Add Variable** button
|
||||
|
|
@ -22,12 +22,12 @@ Suppose there is an `API key` or a value that you want to use in the queries or
|
|||
|
||||
### Types of variables
|
||||
|
||||
- **Client**: The client variable can be used in widgets and queries.
|
||||
- **Client**: The client variables can be used in widgets and queries.
|
||||
|
||||
- **Server**: The server variables can be used with all the queries except the `RunJS`. The reason why we don't allow the server variables to be used with the widgets is that these variables are only resolved during the runtime so they're highly secured.
|
||||
- **Server**: The server variables can be used with all the queries except the `RunJS` or 'RunPy'. The reason why we don't allow the server variables to be used with the components is that these variables are only resolved during the runtime so they're highly secured.
|
||||
|
||||
:::info
|
||||
Variable Type cannot be changed once it has been created.
|
||||
Variable's Type cannot be changed once it has been created.
|
||||
:::
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,8 @@ title: Calendar
|
|||
---
|
||||
# Calendar
|
||||
|
||||
Calendar widget allows you to build functionality such as displaying events, editing event details scheduling new events. It also supports advanced feaures like resource-scheduling.
|
||||
|
||||
Calendar widget comes with the following features:
|
||||
- **Day, month and week level views**
|
||||
- **Events**
|
||||
|
|
|
|||
|
|
@ -4,7 +4,7 @@ title: Multiselect
|
|||
---
|
||||
# Multiselect
|
||||
|
||||
Multiselect widget can be used to collect multiple user inputs from a list of options.
|
||||
Multiselect widget can be used to allow users to select one or more options.
|
||||
|
||||
<div style={{textAlign: 'center'}}>
|
||||
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 490 B After Width: | Height: | Size: 490 B |
|
|
@ -75,43 +75,53 @@ export const Multiselect = function Multiselect({
|
|||
registerAction(
|
||||
'selectOption',
|
||||
async function (value) {
|
||||
const newSelected = [
|
||||
...selected,
|
||||
...selectOptions.filter(
|
||||
(option) => option.value === value && !selected.map((selectedOption) => selectedOption.value).includes(value)
|
||||
),
|
||||
];
|
||||
setSelected(newSelected);
|
||||
setExposedVariable(
|
||||
'values',
|
||||
newSelected.map((item) => item.value)
|
||||
).then(() => fireEvent('onSelect'));
|
||||
if (
|
||||
selectOptions.some((option) => option.value === value) &&
|
||||
!selected.some((option) => option.value === value)
|
||||
) {
|
||||
const newSelected = [
|
||||
...selected,
|
||||
...selectOptions.filter(
|
||||
(option) =>
|
||||
option.value === value && !selected.map((selectedOption) => selectedOption.value).includes(value)
|
||||
),
|
||||
];
|
||||
setSelected(newSelected);
|
||||
setExposedVariable(
|
||||
'values',
|
||||
newSelected.map((item) => item.value)
|
||||
).then(() => fireEvent('onSelect'));
|
||||
}
|
||||
},
|
||||
[selected, setSelected]
|
||||
);
|
||||
registerAction(
|
||||
'deselectOption',
|
||||
async function (value) {
|
||||
const newSelected = [
|
||||
...selected.filter(function (item) {
|
||||
return item.value !== value;
|
||||
}),
|
||||
];
|
||||
setSelected(newSelected);
|
||||
setExposedVariable(
|
||||
'values',
|
||||
newSelected.map((item) => item.value)
|
||||
).then(() => fireEvent('onSelect'));
|
||||
if (selectOptions.some((option) => option.value === value) && selected.some((option) => option.value === value)) {
|
||||
const newSelected = [
|
||||
...selected.filter(function (item) {
|
||||
return item.value !== value;
|
||||
}),
|
||||
];
|
||||
setSelected(newSelected);
|
||||
setExposedVariable(
|
||||
'values',
|
||||
newSelected.map((item) => item.value)
|
||||
).then(() => fireEvent('onSelect'));
|
||||
}
|
||||
},
|
||||
[selected, setSelected]
|
||||
);
|
||||
registerAction(
|
||||
'clearSelections',
|
||||
async function () {
|
||||
setSelected([]);
|
||||
setExposedVariable('values', []).then(() => fireEvent('onSelect'));
|
||||
if (selected.length >= 1) {
|
||||
setSelected([]);
|
||||
setExposedVariable('values', []).then(() => fireEvent('onSelect'));
|
||||
}
|
||||
},
|
||||
[setSelected]
|
||||
[selected, setSelected]
|
||||
);
|
||||
|
||||
return (
|
||||
|
|
|
|||
|
|
@ -126,6 +126,13 @@ export const Inspector = ({
|
|||
if (param.type === 'select' && defaultValue) {
|
||||
allParams[defaultValue.paramName]['value'] = defaultValue.value;
|
||||
}
|
||||
if (param.name === 'secondarySignDisplay') {
|
||||
if (value === 'negative') {
|
||||
newDefinition['styles']['secondaryTextColour']['value'] = '#EE2C4D';
|
||||
} else if (value === 'positive') {
|
||||
newDefinition['styles']['secondaryTextColour']['value'] = '#36AF8B';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
allParams[param.name] = value;
|
||||
}
|
||||
|
|
|
|||
406
frontend/src/_components/Organization.jsx
Normal file
406
frontend/src/_components/Organization.jsx
Normal file
|
|
@ -0,0 +1,406 @@
|
|||
import React, { useState, useEffect } from 'react';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { authenticationService, organizationService } from '@/_services';
|
||||
import Modal from '../HomePage/Modal';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import { SearchBox } from './SearchBox';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export const Organization = function Organization({ darkMode }) {
|
||||
const isSingleOrganization = window.public_config?.DISABLE_MULTI_WORKSPACE === 'true';
|
||||
const { admin, organization_id } = authenticationService.currentUserValue;
|
||||
const [organization, setOrganization] = useState(authenticationService.currentUserValue?.organization);
|
||||
const [showCreateOrg, setShowCreateOrg] = useState(false);
|
||||
const [showEditOrg, setShowEditOrg] = useState(false);
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
const [searchText, setSearchText] = useState('');
|
||||
const [organizationList, setOrganizationList] = useState([]);
|
||||
const [getOrgStatus, setGetOrgStatus] = useState('loading');
|
||||
const [isListOrganizations, setIsListOrganizations] = useState(false);
|
||||
const [newOrgName, setNewOrgName] = useState('');
|
||||
const { t } = useTranslation();
|
||||
|
||||
const getAvatar = (organization) => {
|
||||
if (!organization) return;
|
||||
|
||||
const orgName = organization.split(' ').filter((e) => e && !!e.trim());
|
||||
if (orgName.length > 1) {
|
||||
return `${orgName[0]?.[0]}${orgName[1]?.[0]}`;
|
||||
} else if (organization.length >= 2) {
|
||||
return `${organization[0]}${organization[1]}`;
|
||||
} else {
|
||||
return `${organization[0]}${organization[0]}`;
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
!isSingleOrganization && getOrganizations();
|
||||
}, [isSingleOrganization]);
|
||||
|
||||
const getOrganizations = () => {
|
||||
setGetOrgStatus('loading');
|
||||
organizationService.getOrganizations().then(
|
||||
(data) => {
|
||||
setOrganizationList(data.organizations);
|
||||
setGetOrgStatus('success');
|
||||
},
|
||||
() => {
|
||||
setGetOrgStatus('failure');
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const showEditModal = () => {
|
||||
setNewOrgName(organization);
|
||||
setShowEditOrg(true);
|
||||
};
|
||||
|
||||
const showCreateModal = () => {
|
||||
setNewOrgName('');
|
||||
setShowCreateOrg(true);
|
||||
};
|
||||
|
||||
const duplicateOrganizationCheck = () => organizationList.some((org) => org.name === newOrgName.trim());
|
||||
|
||||
const createOrganization = () => {
|
||||
const organizationNameExists = duplicateOrganizationCheck();
|
||||
|
||||
if (!(newOrgName && newOrgName.trim())) {
|
||||
toast.error('Workspace name cannot be empty.', {
|
||||
position: 'top-center',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (newOrgName.length > 25) {
|
||||
toast.error('Workspace name cannot be longer than 25 characters.', {
|
||||
position: 'top-center',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (organizationNameExists) {
|
||||
toast.error(`${newOrgName} already exists.`, {
|
||||
position: 'top-center',
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsCreating(true);
|
||||
organizationService.createOrganization(newOrgName).then(
|
||||
(data) => {
|
||||
setIsCreating(false);
|
||||
authenticationService.updateCurrentUserDetails(data);
|
||||
window.location.href = '';
|
||||
},
|
||||
() => {
|
||||
setIsCreating(false);
|
||||
toast.error('Error while creating workspace', {
|
||||
position: 'top-center',
|
||||
});
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const editOrganization = () => {
|
||||
const organizationNameExists = duplicateOrganizationCheck();
|
||||
|
||||
if (!(newOrgName && newOrgName.trim())) {
|
||||
toast.error('Workspace name can not be empty.', {
|
||||
position: 'top-center',
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (organizationNameExists) {
|
||||
toast.error(`The workspace ${newOrgName} already exists.`, {
|
||||
position: 'top-center',
|
||||
});
|
||||
return;
|
||||
}
|
||||
setIsCreating(true);
|
||||
organizationService.editOrganization({ name: newOrgName }).then(
|
||||
() => {
|
||||
authenticationService.updateCurrentUserDetails({ organization: newOrgName });
|
||||
toast.success('Workspace updated', {
|
||||
position: 'top-center',
|
||||
});
|
||||
setOrganization(newOrgName);
|
||||
getOrganizations();
|
||||
},
|
||||
() => {
|
||||
toast.error('Error while editing workspace', {
|
||||
position: 'top-center',
|
||||
});
|
||||
}
|
||||
);
|
||||
setIsCreating(false);
|
||||
setShowEditOrg(false);
|
||||
};
|
||||
|
||||
const switchOrganization = (orgId) => {
|
||||
organizationService.switchOrganization(orgId).then(
|
||||
(data) => {
|
||||
authenticationService.updateCurrentUserDetails(data);
|
||||
window.location.href = '';
|
||||
},
|
||||
() => {
|
||||
return (window.location.href = `login/${orgId}`);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
const listOrganization = () => {
|
||||
return (
|
||||
organizationList &&
|
||||
organizationList
|
||||
.filter((org) => org.name?.toLowerCase().includes(searchText ? searchText.toLowerCase() : ''))
|
||||
.map((org) => {
|
||||
return (
|
||||
<div
|
||||
key={org.id}
|
||||
onClick={organization_id === org.id ? undefined : () => switchOrganization(org.id)}
|
||||
className="dropdown-item org-list-item"
|
||||
>
|
||||
<div className="col-3">
|
||||
<span className="avatar bg-secondary-lt">{getAvatar(org.name)}</span>
|
||||
</div>
|
||||
<div className="col-8">
|
||||
<div className="org-name">{org.name}</div>
|
||||
</div>
|
||||
<div className="col-1">
|
||||
{organization_id === org.id && (
|
||||
<div className="tick-ico">
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="icon icon-tabler icon-tabler-check"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<path d="M5 12l5 5l10 -10"></path>
|
||||
</svg>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
const searchOrganizations = (text) => {
|
||||
setSearchText(text);
|
||||
};
|
||||
|
||||
const getListOrganizations = () => {
|
||||
return (
|
||||
<div className="organization-switchlist">
|
||||
<div className="dd-item-padding">
|
||||
<div className="d-flex">
|
||||
<div className="back-ico" onClick={() => setIsListOrganizations(false)}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="icon icon-tabler icon-tabler-chevron-left"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<polyline points="15 6 9 12 15 18"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
<div className="back-btn" onClick={() => setIsListOrganizations(false)}>
|
||||
{t('globals.back', 'Back')}
|
||||
</div>
|
||||
</div>
|
||||
<div className="search-box">
|
||||
<SearchBox
|
||||
onSubmit={searchOrganizations}
|
||||
debounceDelay={100}
|
||||
width="14rem"
|
||||
placeholder={t('globals.search', 'Search')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="org-list">
|
||||
{getOrgStatus === 'success' ? (
|
||||
listOrganization()
|
||||
) : (
|
||||
<div className="text-center">
|
||||
<a
|
||||
onClick={getOrganizations}
|
||||
href="#"
|
||||
className={`btn btn-primary mb-2 ${getOrgStatus === 'loading' ? 'btn-loading' : ''}`}
|
||||
>
|
||||
{t('header.organization.loadOrganizations', 'Load Organizations')}
|
||||
</a>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const getOrganizationMenu = () => {
|
||||
return (
|
||||
<div>
|
||||
<div className="dropdown-item org-avatar">
|
||||
<div className="row">
|
||||
<div className="col-3">
|
||||
<span className="avatar bg-secondary-lt">{getAvatar(organization)}</span>
|
||||
</div>
|
||||
<div className={`col-${isSingleOrganization ? '9' : '7'}`}>
|
||||
<div className="org-name" style={{ padding: `${admin ? '0px' : '0.6rem'} 0px` }}>
|
||||
{organization}
|
||||
</div>
|
||||
{admin && (
|
||||
<div className="org-edit">
|
||||
<span onClick={showEditModal} data-cy="edit-workspace-name">
|
||||
{t('globals.edit', 'Edit')}
|
||||
</span>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
{!isSingleOrganization && (
|
||||
<div className="col-2">
|
||||
<div className="arrow-container" onClick={() => setIsListOrganizations(true)}>
|
||||
<svg
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
className="icon icon-tabler icon-tabler-chevron-right"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
strokeWidth="2"
|
||||
stroke="currentColor"
|
||||
fill="none"
|
||||
strokeLinecap="round"
|
||||
strokeLinejoin="round"
|
||||
data-cy="workspace-arrow-icon"
|
||||
>
|
||||
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
|
||||
<polyline points="9 6 15 12 9 18"></polyline>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
{!isSingleOrganization && (
|
||||
<div className="dropdown-item org-actions">
|
||||
<div onClick={showCreateModal}>{t('header.organization.menus.addWorkspace', 'Add workspace')}</div>
|
||||
</div>
|
||||
)}
|
||||
<div className="dropdown-divider"></div>
|
||||
{admin && (
|
||||
<>
|
||||
<Link data-testid="settingsBtn" to="/users" className="dropdown-item" data-cy="manage-users">
|
||||
{t('header.organization.menus.menusList.manageUsers', 'Manage Users')}
|
||||
</Link>
|
||||
<Link data-tesid="settingsBtn" to="/groups" className="dropdown-item" data-cy="manage-groups">
|
||||
{t('header.organization.menus.menusList.manageGroups', 'Manage Groups')}
|
||||
</Link>
|
||||
<Link data-tesid="settingsBtn" to="/manage-sso" className="dropdown-item" data-cy="manage-sso">
|
||||
{t('header.organization.menus.menusList.manageSso', 'Manage SSO')}
|
||||
</Link>
|
||||
</>
|
||||
)}
|
||||
<Link data-tesid="settingsBtn" to="/manage-environment-vars" className="dropdown-item">
|
||||
{admin
|
||||
? t('header.organization.menus.menusList.manageEnv', 'Manage Workspace Variables')
|
||||
: t('globals.environmentVar', 'Workspace Variables')}
|
||||
</Link>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div>
|
||||
<div className="dropdown organization-list" data-cy="dropdown-organization-list">
|
||||
<a
|
||||
href="#"
|
||||
className={`btn ${!isSingleOrganization || admin ? 'dropdown-toggle' : ''} ${darkMode && 'text-muted'}`}
|
||||
onMouseOver={() => setIsListOrganizations(false)}
|
||||
style={{ height: '38px' }}
|
||||
>
|
||||
<div>{organization}</div>
|
||||
</a>
|
||||
<div className="dropdown-menu end-0" data-cy="workspace-dropdown">
|
||||
{!isSingleOrganization || admin
|
||||
? isListOrganizations
|
||||
? getListOrganizations()
|
||||
: getOrganizationMenu()
|
||||
: getOrganizationMenu()}
|
||||
</div>
|
||||
</div>
|
||||
<Modal
|
||||
show={showCreateOrg}
|
||||
closeModal={() => setShowCreateOrg(false)}
|
||||
title={t('header.organization.createWorkspace', 'Create workspace')}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col modal-main">
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => setNewOrgName(e.target.value)}
|
||||
className="form-control"
|
||||
placeholder={t('header.organization.workspaceName', 'workspace name')}
|
||||
disabled={isCreating}
|
||||
maxLength={25}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<button className="btn btn-light" onClick={() => setShowCreateOrg(false)}>
|
||||
{t('globals.cancel', 'Cancel')}
|
||||
</button>
|
||||
<button
|
||||
disabled={isCreating}
|
||||
className={`btn btn-primary ${isCreating ? 'btn-loading' : ''}`}
|
||||
onClick={createOrganization}
|
||||
>
|
||||
{t('header.organization.createWorkspace', 'Create workspace')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
<Modal
|
||||
show={showEditOrg}
|
||||
closeModal={() => setShowEditOrg(false)}
|
||||
title={t('header.organization.editWorkspace', 'Edit workspace')}
|
||||
>
|
||||
<div className="row">
|
||||
<div className="col modal-main">
|
||||
<input
|
||||
type="text"
|
||||
onChange={(e) => setNewOrgName(e.target.value)}
|
||||
className="form-control"
|
||||
placeholder={t('header.organization.workspaceName', 'workspace name')}
|
||||
disabled={isCreating}
|
||||
value={newOrgName}
|
||||
maxLength={25}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="row">
|
||||
<div className="col d-flex modal-footer-btn">
|
||||
<button className="btn btn-light" onClick={() => setShowEditOrg(false)}>
|
||||
{t('globals.cancel', 'Cancel')}
|
||||
</button>
|
||||
<button className={`btn btn-primary ${isCreating ? 'btn-loading' : ''}`} onClick={editOrganization}>
|
||||
{t('globals.save', 'Save')}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</Modal>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
|
@ -4944,7 +4944,7 @@ div#driver-page-overlay {
|
|||
}
|
||||
|
||||
.organization-list {
|
||||
margin-top: 5px;
|
||||
margin-top: 4px;
|
||||
|
||||
.btn {
|
||||
border: 0px;
|
||||
|
|
@ -7330,6 +7330,7 @@ tbody {
|
|||
}
|
||||
|
||||
}
|
||||
<<<<<<< HEAD
|
||||
|
||||
.user-group-table {
|
||||
.selected-row {
|
||||
|
|
@ -7343,4 +7344,6 @@ tbody {
|
|||
.empty-subtitle ,.card-footer>span,.empty-title{
|
||||
color: white !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
=======
|
||||
>>>>>>> develop
|
||||
|
|
|
|||
Loading…
Reference in a new issue