mirror of
https://github.com/Unleash/unleash.git
synced 2025-07-26 13:48:33 +02:00
fix: hide project CRUD actions for oss (#416)
This commit is contained in:
parent
2990fc180a
commit
012cfc1806
@ -1,3 +1,4 @@
|
||||
/* eslint-disable react/jsx-no-target-blank */
|
||||
import { useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
@ -20,16 +21,27 @@ import {
|
||||
FormControl,
|
||||
} from '@material-ui/core';
|
||||
import { Delete } from '@material-ui/icons';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
|
||||
import AddUserComponent from './access-add-user';
|
||||
|
||||
import projectApi from '../../store/project/api';
|
||||
import PageContent from '../common/PageContent';
|
||||
import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||
|
||||
|
||||
function AccessComponent({ projectId, project }) {
|
||||
const [roles, setRoles] = useState([]);
|
||||
const [users, setUsers] = useState([]);
|
||||
const [error, setError] = useState();
|
||||
const { isOss } = useUiConfig();
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccess();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [projectId]);
|
||||
|
||||
|
||||
|
||||
const fetchAccess = async () => {
|
||||
try {
|
||||
@ -43,13 +55,14 @@ function AccessComponent({ projectId, project }) {
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAccess();
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [projectId]);
|
||||
|
||||
if (!project) {
|
||||
return <p>....</p>;
|
||||
if (isOss()) {
|
||||
return (
|
||||
<PageContent>
|
||||
<Alert severity="error">
|
||||
Controlling access to projects requires a paid version of Unleash.
|
||||
Check out <a href="https://www.getunleash.io" target="_blank">getunleash.io</a> to find out more.
|
||||
</Alert>
|
||||
</PageContent>);
|
||||
}
|
||||
|
||||
const handleRoleChange = (userId, currRoleId) => async evt => {
|
||||
|
@ -1,200 +0,0 @@
|
||||
import { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TextField, Typography } from '@material-ui/core';
|
||||
|
||||
import styles from './Project.module.scss';
|
||||
import classnames from 'classnames';
|
||||
import { FormButtons, styles as commonStyles } from '../common';
|
||||
import { trim } from '../common/util';
|
||||
import PageContent from '../common/PageContent/PageContent';
|
||||
import AccessContext from '../../contexts/AccessContext';
|
||||
import ConditionallyRender from '../common/ConditionallyRender';
|
||||
import { CREATE_PROJECT } from '../AccessProvider/permissions';
|
||||
import HeaderTitle from '../common/HeaderTitle';
|
||||
|
||||
class ProjectFormComponent extends Component {
|
||||
static contextType = AccessContext;
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
this.state = {
|
||||
project: props.project,
|
||||
errors: {},
|
||||
currentLegalValue: '',
|
||||
dirty: false,
|
||||
};
|
||||
}
|
||||
|
||||
static getDerivedStateFromProps(props, state) {
|
||||
if (!state.project.id && props.project.id) {
|
||||
return { project: props.project };
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
setValue = (field, value) => {
|
||||
const { project } = this.state;
|
||||
project[field] = value;
|
||||
this.setState({ project, dirty: true });
|
||||
};
|
||||
|
||||
validateId = async id => {
|
||||
const { errors } = this.state;
|
||||
const { validateId, editMode } = this.props;
|
||||
if (editMode) return true;
|
||||
|
||||
try {
|
||||
await validateId(id);
|
||||
errors.id = undefined;
|
||||
} catch (err) {
|
||||
errors.id = err.message;
|
||||
}
|
||||
|
||||
this.setState({ errors });
|
||||
if (errors.id) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
validateName = () => {
|
||||
const { project } = this.state;
|
||||
if (project.name.length === 0) {
|
||||
this.setState(prev => ({
|
||||
errors: { ...prev.errors, name: 'Name can not be empty.' },
|
||||
}));
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
validate = async id => {
|
||||
const validId = await this.validateId(id);
|
||||
const validName = this.validateName();
|
||||
|
||||
return validId && validName;
|
||||
};
|
||||
|
||||
onCancel = evt => {
|
||||
const { project } = this.state;
|
||||
evt.preventDefault();
|
||||
|
||||
const { editMode } = this.props;
|
||||
|
||||
if (editMode) {
|
||||
this.props.history.push(`/projects/${project.id}`);
|
||||
return;
|
||||
}
|
||||
this.props.history.goBack();
|
||||
};
|
||||
|
||||
onSubmit = async evt => {
|
||||
evt.preventDefault();
|
||||
const { project } = this.state;
|
||||
|
||||
const valid = await this.validate(project.id);
|
||||
|
||||
if (valid) {
|
||||
const { editMode } = this.props;
|
||||
const query = editMode ? 'edited=true' : 'created=true';
|
||||
await this.props.submit(project);
|
||||
this.props.history.push(`/projects/${project.id}?${query}`);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const { project, errors } = this.state;
|
||||
const { hasAccess } = this.context;
|
||||
const { editMode } = this.props;
|
||||
const submitText = editMode ? 'Update' : 'Create';
|
||||
|
||||
return (
|
||||
<PageContent
|
||||
headerContent={
|
||||
<HeaderTitle
|
||||
title={`${submitText} Project`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
style={{ marginBottom: '0.5rem' }}
|
||||
>
|
||||
Projects allows you to group feature toggles together in the
|
||||
management UI.
|
||||
</Typography>
|
||||
<form
|
||||
onSubmit={this.onSubmit}
|
||||
className={classnames(
|
||||
commonStyles.contentSpacing,
|
||||
styles.formContainer
|
||||
)}
|
||||
>
|
||||
<TextField
|
||||
label="Project Id"
|
||||
name="id"
|
||||
placeholder="A-unique-key"
|
||||
value={project.id}
|
||||
error={!!errors.id}
|
||||
helperText={errors.id}
|
||||
disabled={editMode}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onBlur={v => this.validateId(v.target.value)}
|
||||
onChange={v =>
|
||||
this.setValue('id', trim(v.target.value))
|
||||
}
|
||||
/>
|
||||
<br />
|
||||
<TextField
|
||||
label="Name"
|
||||
name="name"
|
||||
placeholder="Project name"
|
||||
value={project.name}
|
||||
error={!!errors.name}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
helperText={errors.name}
|
||||
onChange={v => this.setValue('name', v.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
className={commonStyles.fullwidth}
|
||||
placeholder="A short description"
|
||||
rowsMax={2}
|
||||
label="Description"
|
||||
error={!!errors.description}
|
||||
helperText={errors.description}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
multiline
|
||||
value={project.description}
|
||||
onChange={v =>
|
||||
this.setValue('description', v.target.value)
|
||||
}
|
||||
/>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={hasAccess(CREATE_PROJECT)}
|
||||
show={
|
||||
<div className={styles.formButtons}>
|
||||
<FormButtons
|
||||
submitText={submitText}
|
||||
onCancel={this.onCancel}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
</PageContent>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
ProjectFormComponent.propTypes = {
|
||||
project: PropTypes.object.isRequired,
|
||||
validateId: PropTypes.func.isRequired,
|
||||
submit: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
editMode: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ProjectFormComponent;
|
208
frontend/src/component/project/form-project-component.tsx
Normal file
208
frontend/src/component/project/form-project-component.tsx
Normal file
@ -0,0 +1,208 @@
|
||||
import { useContext, useEffect, useState } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { TextField, Typography } from '@material-ui/core';
|
||||
|
||||
import styles from './Project.module.scss';
|
||||
import classnames from 'classnames';
|
||||
import { FormButtons, styles as commonStyles } from '../common';
|
||||
import { trim } from '../common/util';
|
||||
import PageContent from '../common/PageContent/PageContent';
|
||||
import AccessContext from '../../contexts/AccessContext';
|
||||
import ConditionallyRender from '../common/ConditionallyRender';
|
||||
import { CREATE_PROJECT } from '../AccessProvider/permissions';
|
||||
import HeaderTitle from '../common/HeaderTitle';
|
||||
import useUiConfig from '../../hooks/api/getters/useUiConfig/useUiConfig';
|
||||
import { Alert } from '@material-ui/lab';
|
||||
import { FormEvent } from 'react-router/node_modules/@types/react';
|
||||
import useLoading from '../../hooks/useLoading';
|
||||
|
||||
interface ProjectFormComponentProps {
|
||||
editMode: boolean;
|
||||
project: any;
|
||||
validateId: (id: string) => Promise<void>;
|
||||
history: any;
|
||||
submit: (project: any) => Promise<void>;
|
||||
}
|
||||
|
||||
const ProjectFormComponent = (props: ProjectFormComponentProps) => {
|
||||
const { editMode } = props;
|
||||
|
||||
const { hasAccess } = useContext(AccessContext);
|
||||
const [project, setProject ] = useState(props.project || {});
|
||||
const [errors, setErrors ] = useState<any>({});
|
||||
const { isOss, loading } = useUiConfig();
|
||||
const ref = useLoading(loading);
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if(!project.id && props.project.id) {
|
||||
setProject(props.project);
|
||||
}
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [props.project]);
|
||||
|
||||
const setValue = (field: string, value: string) => {
|
||||
const p = {...project}
|
||||
p[field] = value;
|
||||
setProject(p)
|
||||
};
|
||||
|
||||
const validateId = async (id: string) => {
|
||||
if (editMode) return true;
|
||||
|
||||
const e = {...errors};
|
||||
try {
|
||||
await props.validateId(id);
|
||||
e.id = undefined;
|
||||
} catch (err: any) {
|
||||
e.id = err.message;
|
||||
}
|
||||
|
||||
setErrors(e)
|
||||
if (e.id) return false;
|
||||
return true;
|
||||
};
|
||||
|
||||
const validateName = () => {
|
||||
if (project.name.length === 0) {
|
||||
setErrors({...errors, name: 'Name can not be empty.' })
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
const validate = async (id: string) => {
|
||||
const validId = await validateId(id);
|
||||
const validName = validateName();
|
||||
|
||||
return validId && validName;
|
||||
};
|
||||
|
||||
const onCancel = (evt: Event) => {
|
||||
evt.preventDefault();
|
||||
|
||||
if (editMode) {
|
||||
props.history.push(`/projects/${project.id}`);
|
||||
return;
|
||||
}
|
||||
props.history.push(`/projects/`);
|
||||
};
|
||||
|
||||
const onSubmit = async (evt: FormEvent<HTMLFormElement>) => {
|
||||
evt.preventDefault();
|
||||
|
||||
const valid = await validate(project.id);
|
||||
|
||||
if (valid) {
|
||||
const query = editMode ? 'edited=true' : 'created=true';
|
||||
await props.submit(project);
|
||||
props.history.push(`/projects/${project.id}?${query}`);
|
||||
}
|
||||
};
|
||||
|
||||
const submitText = editMode ? 'Update' : 'Create';
|
||||
|
||||
return (
|
||||
<div ref={ref}>
|
||||
<PageContent
|
||||
headerContent={
|
||||
<HeaderTitle
|
||||
title={`${submitText} Project`}
|
||||
/>
|
||||
}
|
||||
>
|
||||
|
||||
<ConditionallyRender condition={isOss()} show={
|
||||
<Alert data-loading severity="error">
|
||||
Creating and updating projects requires a paid version of Unleash.
|
||||
Check out <a href="https://www.getunleash.io" target="_blank" rel="noreferrer">getunleash.io</a>
|
||||
to find out more.
|
||||
</Alert>
|
||||
} elseShow={
|
||||
<>
|
||||
<Typography
|
||||
variant="subtitle1"
|
||||
style={{ marginBottom: '0.5rem' }}
|
||||
>
|
||||
Projects allows you to group feature toggles together in the
|
||||
management UI.
|
||||
</Typography>
|
||||
<form
|
||||
data-loading
|
||||
onSubmit={onSubmit}
|
||||
className={classnames(
|
||||
commonStyles.contentSpacing,
|
||||
styles.formContainer
|
||||
)}
|
||||
>
|
||||
<TextField
|
||||
label="Project Id"
|
||||
name="id"
|
||||
placeholder="A-unique-key"
|
||||
value={project.id}
|
||||
error={!!errors.id}
|
||||
helperText={errors.id}
|
||||
disabled={editMode}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
onBlur={v => validateId(v.target.value)}
|
||||
onChange={v =>
|
||||
setValue('id', trim(v.target.value))
|
||||
}
|
||||
/>
|
||||
<br />
|
||||
<TextField
|
||||
label="Name"
|
||||
name="name"
|
||||
placeholder="Project name"
|
||||
value={project.name}
|
||||
error={!!errors.name}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
helperText={errors.name}
|
||||
onChange={v => setValue('name', v.target.value)}
|
||||
/>
|
||||
<TextField
|
||||
className={commonStyles.fullwidth}
|
||||
placeholder="A short description"
|
||||
maxRows={2}
|
||||
label="Description"
|
||||
error={!!errors.description}
|
||||
helperText={errors.description}
|
||||
variant="outlined"
|
||||
size="small"
|
||||
multiline
|
||||
value={project.description}
|
||||
onChange={v =>
|
||||
setValue('description', v.target.value)
|
||||
}
|
||||
/>
|
||||
|
||||
<ConditionallyRender
|
||||
condition={hasAccess(CREATE_PROJECT)}
|
||||
show={
|
||||
<div className={styles.formButtons}>
|
||||
<FormButtons
|
||||
submitText={submitText}
|
||||
onCancel={onCancel}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</form>
|
||||
</>
|
||||
} />
|
||||
</PageContent>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ProjectFormComponent.propTypes = {
|
||||
project: PropTypes.object.isRequired,
|
||||
validateId: PropTypes.func.isRequired,
|
||||
submit: PropTypes.func.isRequired,
|
||||
history: PropTypes.object.isRequired,
|
||||
editMode: PropTypes.bool.isRequired,
|
||||
};
|
||||
|
||||
export default ProjectFormComponent;
|
@ -26,6 +26,8 @@ const useUiConfig = () => {
|
||||
const isOss = () => {
|
||||
if (data?.versionInfo?.current?.enterprise) {
|
||||
return false;
|
||||
} else if (!data || !data.versionInfo) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
Loading…
Reference in New Issue
Block a user