mirror of
https://github.com/LaCasemate/fab-manager.git
synced 2025-01-20 09:52:19 +01:00
Merge branch 'dev'
This commit is contained in:
commit
0b01b8109d
17
CHANGELOG.md
17
CHANGELOG.md
@ -2,6 +2,22 @@
|
||||
|
||||
## next deploy
|
||||
|
||||
## v5.4.2 2022 June 1
|
||||
|
||||
- Updated react-select to 5.3.2
|
||||
- Moved the calendar navigation buttons to the left side
|
||||
- Fix a bug: unable to remove the last training or the last tag to a member
|
||||
- Fix a bug: unable to run scripts on systems with legacy version of docker-compose
|
||||
- Fix a bug: unable to sign up if admin actived organization's additional fields with required
|
||||
- Fix a bug: undefined error in new member page
|
||||
- Fix a bug: OIDC scopes are separated by spaces, not commas
|
||||
- Fix a bug: unable to create OIDC custom scopes
|
||||
- Fix a bug: enable admins to be invited to collaborate on projects
|
||||
- Fix a bug: hide some links to create an account if public registrations is disabled
|
||||
- Fix a bug: unable to save user validation if admin click save user profile button after switch user valitation
|
||||
- Fix a bug: if multi VAT no value is filled in, the general rate can't apply
|
||||
- Fix a security issue: updated rack to 2.2.3.1 to fix [CVE-2022-30123](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30123) and [CVE-2022-30122](https://cve.mitre.org/cgi-bin/cvename.cgi?name=CVE-2022-30122)
|
||||
|
||||
## v5.4.1 2022 May 23
|
||||
|
||||
- Disable to clicking outside or pressing escape to close sign up modal
|
||||
@ -23,6 +39,7 @@
|
||||
- Option to disable the 'machines' module
|
||||
- Option to prevent users from changing their group
|
||||
- Ability to define social networks for the FabLab "about page"
|
||||
- Plan categories can have rich-text descriptions
|
||||
- Improved security when changing passwords
|
||||
- Support for OpenID Connect in Single-Sign-On authentication providers
|
||||
- ICS file attached to the reservation notification email
|
||||
|
@ -300,7 +300,7 @@ GEM
|
||||
activesupport (>= 3.0.0)
|
||||
raabro (1.4.0)
|
||||
racc (1.6.0)
|
||||
rack (2.2.3)
|
||||
rack (2.2.3.1)
|
||||
rack-oauth2 (1.19.0)
|
||||
activesupport
|
||||
attr_required
|
||||
|
@ -245,7 +245,8 @@ class API::MembersController < API::ApiController
|
||||
|
||||
members_service = Members::MembersService.new(@member)
|
||||
|
||||
if members_service.validate(user_params[:validated_at].present?)
|
||||
uparams = params.require(:user).permit(:validated_at)
|
||||
if members_service.validate(uparams[:validated_at].present?)
|
||||
render :show, status: :ok, location: member_path(@member)
|
||||
else
|
||||
render json: @member.errors, status: :unprocessable_entity
|
||||
@ -275,7 +276,7 @@ class API::MembersController < API::ApiController
|
||||
|
||||
elsif current_user.admin? || current_user.manager?
|
||||
params.require(:user).permit(:username, :email, :password, :password_confirmation, :is_allow_contact, :is_allow_newsletter, :group_id,
|
||||
:validated_at, tag_ids: [],
|
||||
tag_ids: [],
|
||||
profile_attributes: [:id, :first_name, :last_name, :phone, :interest, :software_mastered, :website, :job,
|
||||
:facebook, :twitter, :google_plus, :viadeo, :linkedin, :instagram, :youtube, :vimeo,
|
||||
:dailymotion, :github, :echosciences, :pinterest, :lastfm, :flickr,
|
||||
|
@ -23,7 +23,7 @@ export default class MemberAPI {
|
||||
}
|
||||
|
||||
static async update (user: User): Promise<User> {
|
||||
const data = serialize({ user });
|
||||
const data = serialize({ user }, { allowEmptyArrays: true });
|
||||
if (user.profile_attributes?.user_avatar_attributes?.attachment_files[0]) {
|
||||
data.set('user[profile_attributes][user_avatar_attributes][attachment]', user.profile_attributes.user_avatar_attributes.attachment_files[0]);
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ export const Oauth2Form = <TFieldValues extends FieldValues>({ register, strateg
|
||||
|
||||
// regular expression to validate the input fields
|
||||
const endpointRegex = /^\/?([-._~:?#[\]@!$&'()*+,;=%\w]+\/?)*$/;
|
||||
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z0-9.]{2,30})([/\w .-]*)*\/?$/;
|
||||
const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/;
|
||||
|
||||
/**
|
||||
* Build the callback URL, based on the strategy name.
|
||||
|
@ -10,6 +10,7 @@ import { OpenIdConnectProvider } from '../../models/authentication-provider';
|
||||
import SsoClient from '../../api/external/sso';
|
||||
import { FieldPathValue } from 'react-hook-form/dist/types/path';
|
||||
import { FormMultiSelect } from '../form/form-multi-select';
|
||||
import { difference } from 'lodash';
|
||||
|
||||
interface OpenidConnectFormProps<TFieldValues, TContext extends object> {
|
||||
register: UseFormRegister<TFieldValues>,
|
||||
@ -41,7 +42,7 @@ export const OpenidConnectForm = <TFieldValues extends FieldValues, TContext ext
|
||||
|
||||
// regular expression to validate the input fields
|
||||
const endpointRegex = /^\/?([-._~:?#[\]@!$&'()*+,;=%\w]+\/?)*$/;
|
||||
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z0-9.]{2,30})([/\w .-]*)*\/?$/;
|
||||
const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/;
|
||||
|
||||
/**
|
||||
* If the discovery endpoint is available, the user will be able to choose to use it or not.
|
||||
@ -60,6 +61,21 @@ export const OpenidConnectForm = <TFieldValues extends FieldValues, TContext ext
|
||||
];
|
||||
};
|
||||
|
||||
/**
|
||||
* Return the list of scopes that are available for the current configuration.
|
||||
* The resulting list is provided through the callback parameter.
|
||||
*/
|
||||
const loadScopes = (inputValue: string, callback: (options: Array<{ value: string, label: string }>) => void): void => {
|
||||
const current = currentFormValues.scope || [];
|
||||
if (scopesAvailable) {
|
||||
// add custom scopes to the list of available scopes
|
||||
const unlisted = difference(current, scopesAvailable);
|
||||
callback(scopesAvailable.concat(unlisted).map(scope => ({ value: scope, label: scope })));
|
||||
} else {
|
||||
current.map(scope => ({ value: scope, label: scope }));
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback that check for the existence of the .well-known/openid-configuration endpoint, for the given issuer.
|
||||
* This callback is triggered when the user changes the issuer field.
|
||||
@ -102,18 +118,12 @@ export const OpenidConnectForm = <TFieldValues extends FieldValues, TContext ext
|
||||
]}
|
||||
valueDefault={'basic'}
|
||||
control={control} />
|
||||
{!scopesAvailable && <FormInput id="providable_attributes.scope"
|
||||
register={register}
|
||||
label={t('app.admin.authentication.openid_connect_form.scope')}
|
||||
placeholder="openid,profile,email"
|
||||
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_form.scope_help_html" />} />}
|
||||
{scopesAvailable && <FormMultiSelect id="providable_attributes.scope"
|
||||
expectedResult="string"
|
||||
label={t('app.admin.authentication.openid_connect_form.scope')}
|
||||
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_form.scope_help_html" />}
|
||||
options={scopesAvailable.map((scope) => ({ value: scope, label: scope }))}
|
||||
creatable
|
||||
control={control} />}
|
||||
<FormMultiSelect id="providable_attributes.scope"
|
||||
label={t('app.admin.authentication.openid_connect_form.scope')}
|
||||
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_form.scope_help_html" />}
|
||||
loadOptions={loadScopes}
|
||||
creatable
|
||||
control={control} />
|
||||
<FormSelect id="providable_attributes.prompt"
|
||||
label={t('app.admin.authentication.openid_connect_form.prompt')}
|
||||
tooltip={<HtmlTranslate trKey="app.admin.authentication.openid_connect_form.prompt_help_html" />}
|
||||
@ -139,7 +149,7 @@ export const OpenidConnectForm = <TFieldValues extends FieldValues, TContext ext
|
||||
placeholder="https://sso.exemple.com/my-account"
|
||||
label={t('app.admin.authentication.openid_connect_form.profile_edition_url')}
|
||||
tooltip={t('app.admin.authentication.openid_connect_form.profile_edition_url_help')}
|
||||
rules={{ pattern: urlRegex }} />
|
||||
rules={{ required: false, pattern: urlRegex }} />
|
||||
<h4>{t('app.admin.authentication.openid_connect_form.client_options')}</h4>
|
||||
<FormInput id="providable_attributes.client__identifier"
|
||||
label={t('app.admin.authentication.openid_connect_form.client__identifier')}
|
||||
|
@ -1,35 +1,45 @@
|
||||
import React, { useEffect } from 'react';
|
||||
import AsyncSelect from 'react-select/async';
|
||||
import Select from 'react-select';
|
||||
import { Controller, Path } from 'react-hook-form';
|
||||
import AsyncCreatableSelect from 'react-select/async-creatable';
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import { FieldValues } from 'react-hook-form/dist/types/fields';
|
||||
import { FieldPath } from 'react-hook-form/dist/types/path';
|
||||
import { FieldPathValue, UnpackNestedValue } from 'react-hook-form/dist/types';
|
||||
import { FormControlledComponent } from '../../models/form-component';
|
||||
import { AbstractFormItem, AbstractFormItemProps } from './abstract-form-item';
|
||||
import CreatableSelect from 'react-select/creatable';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Controller, FieldPathValue, Path } from 'react-hook-form';
|
||||
import { UnpackNestedValue } from 'react-hook-form/dist/types/form';
|
||||
|
||||
interface FormSelectProps<TFieldValues, TContext extends object, TOptionValue> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
options: Array<selectOption<TOptionValue>>,
|
||||
interface CommonProps<TFieldValues, TContext extends object, TOptionValue> extends FormControlledComponent<TFieldValues, TContext>, AbstractFormItemProps<TFieldValues> {
|
||||
valuesDefault?: Array<TOptionValue>,
|
||||
onChange?: (values: Array<TOptionValue>) => void,
|
||||
placeholder?: string,
|
||||
expectedResult?: 'array' | 'string'
|
||||
creatable?: boolean,
|
||||
}
|
||||
|
||||
// we should provide either an array of options or a function that returns a promise, but not both
|
||||
type OptionsProps<TOptionValue> =
|
||||
{ options: Array<selectOption<TOptionValue>>, loadOptions?: never } |
|
||||
{ options?: never, loadOptions: (inputValue: string, callback: (options: Array<selectOption<TOptionValue>>) => void) => void };
|
||||
|
||||
type FormSelectProps<TFieldValues, TContext extends object, TOptionValue> = CommonProps<TFieldValues, TContext, TOptionValue> & OptionsProps<TOptionValue>;
|
||||
|
||||
/**
|
||||
* Option format, expected by react-select
|
||||
* @see https://github.com/JedWatson/react-select
|
||||
*/
|
||||
type selectOption<TOptionValue> = { value: TOptionValue, label: string };
|
||||
type selectOption<TOptionValue> = { value: TOptionValue, label: string, select?: boolean };
|
||||
|
||||
/**
|
||||
* This component is a wrapper around react-select to use with react-hook-form.
|
||||
* It is a multi-select component.
|
||||
*/
|
||||
export const FormMultiSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, tooltip, className, control, placeholder, options, valuesDefault, error, rules, disabled, onChange, formState, warning, expectedResult, creatable }: FormSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
||||
export const FormMultiSelect = <TFieldValues extends FieldValues, TContext extends object, TOptionValue>({ id, label, tooltip, className, control, placeholder, options, valuesDefault, error, rules, disabled, onChange, formState, warning, loadOptions, creatable }: FormSelectProps<TFieldValues, TContext, TOptionValue>) => {
|
||||
const { t } = useTranslation('shared');
|
||||
|
||||
const [isDisabled, setIsDisabled] = React.useState<boolean>(false);
|
||||
const [allOptions, setAllOptions] = React.useState<Array<selectOption<TOptionValue>>>(options);
|
||||
const [allOptions, setAllOptions] = React.useState<Array<selectOption<TOptionValue>>>(options || []);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof disabled === 'function') {
|
||||
@ -40,87 +50,102 @@ export const FormMultiSelect = <TFieldValues extends FieldValues, TContext exten
|
||||
}, [disabled]);
|
||||
|
||||
useEffect(() => {
|
||||
setAllOptions(options);
|
||||
}, [options]);
|
||||
if (typeof loadOptions === 'function') {
|
||||
loadOptions('', options => {
|
||||
setAllOptions(options);
|
||||
});
|
||||
}
|
||||
}, [loadOptions]);
|
||||
|
||||
/**
|
||||
* The following callback will trigger the onChange callback, if it was passed to this component,
|
||||
* when the selected option changes.
|
||||
* It will also update the react-hook-form's value, according to the provided 'result' property (string or array).
|
||||
* The following callback will set the new selected options in the component state.
|
||||
*/
|
||||
const onChangeCb = (newValues: Array<TOptionValue>, rhfOnChange): void => {
|
||||
const onChangeCb = (newValues: Array<TOptionValue>, rhfOnChange: (values: Array<TOptionValue>) => void): void => {
|
||||
if (typeof onChange === 'function') {
|
||||
onChange(newValues);
|
||||
}
|
||||
if (expectedResult === 'string') {
|
||||
rhfOnChange(newValues.join(','));
|
||||
} else {
|
||||
if (typeof rhfOnChange === 'function') {
|
||||
rhfOnChange(newValues);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* This function will return the currently selected options, according to the provided react-hook-form's value.
|
||||
* This function will return the currently selected options, according to the selectedOptions state.
|
||||
*/
|
||||
const getCurrentValues = (value: Array<TOptionValue>|string): Array<selectOption<TOptionValue>> => {
|
||||
let values: Array<TOptionValue> = [];
|
||||
if (typeof value === 'string') {
|
||||
values = value.split(',') as Array<unknown> as Array<TOptionValue>;
|
||||
} else {
|
||||
values = value;
|
||||
}
|
||||
return allOptions.filter(c => values?.includes(c.value));
|
||||
const getCurrentValues = (value: Array<TOptionValue>): Array<selectOption<TOptionValue>> => {
|
||||
return allOptions.filter(c => value?.includes(c.value));
|
||||
};
|
||||
|
||||
/**
|
||||
* When the select is 'creatable', this callback handle the creation and the selection of a new option.
|
||||
*/
|
||||
const handleCreate = (value: Array<TOptionValue>|string, rhfOnChange) => {
|
||||
return (inputValue: string) => {
|
||||
// add the new value to the list of options
|
||||
const newOption = { value: inputValue as unknown as TOptionValue, label: inputValue };
|
||||
setAllOptions([...allOptions, newOption]);
|
||||
|
||||
// select the new option
|
||||
const values = getCurrentValues(value);
|
||||
values.push(newOption);
|
||||
onChangeCb(values.map(c => c.value), rhfOnChange);
|
||||
};
|
||||
const handleCreate = (inputValue: string, currentSelection: Array<TOptionValue>, rhfOnChange: (values: Array<TOptionValue>) => void) => {
|
||||
// add the new value to the list of options
|
||||
const newValue = inputValue as unknown as TOptionValue;
|
||||
const newOption = { value: newValue, label: inputValue };
|
||||
setAllOptions([...allOptions, newOption]);
|
||||
if (typeof rhfOnChange === 'function') {
|
||||
rhfOnChange([...currentSelection, newValue]);
|
||||
}
|
||||
};
|
||||
|
||||
// if the user can create new options, we need to use a different component
|
||||
const AbstractSelect = creatable ? CreatableSelect : Select;
|
||||
/**
|
||||
* Translate the label for a new item when the select is "creatable"
|
||||
*/
|
||||
const formatCreateLabel = (inputValue: string): string => {
|
||||
return t('app.shared.form_multi_select.create_label', { VALUE: inputValue });
|
||||
};
|
||||
|
||||
// if the user can create new options, and/or load the options through a promise need to use different components
|
||||
const AbstractSelect = loadOptions
|
||||
? creatable
|
||||
? AsyncCreatableSelect
|
||||
: AsyncSelect
|
||||
: creatable
|
||||
? CreatableSelect
|
||||
: Select;
|
||||
|
||||
return (
|
||||
<AbstractFormItem id={id} formState={formState} label={label}
|
||||
className={`form-multi-select ${className || ''}`} tooltip={tooltip}
|
||||
disabled={disabled}
|
||||
rules={rules} error={error} warning={warning}>
|
||||
<Controller name={id as FieldPath<TFieldValues>}
|
||||
control={control}
|
||||
defaultValue={valuesDefault as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>}
|
||||
rules={rules}
|
||||
render={({ field: { onChange, value, ref } }) =>
|
||||
<AbstractSelect ref={ref}
|
||||
classNamePrefix="rs"
|
||||
className="rs"
|
||||
value={getCurrentValues(value)}
|
||||
onChange={val => {
|
||||
const values = val?.map(c => c.value);
|
||||
onChangeCb(values, onChange);
|
||||
}}
|
||||
onCreateOption={handleCreate(value, onChange)}
|
||||
placeholder={placeholder}
|
||||
options={allOptions}
|
||||
isDisabled={isDisabled}
|
||||
isMulti />
|
||||
} />
|
||||
<Controller name={id as FieldPath<TFieldValues>}
|
||||
control={control}
|
||||
defaultValue={valuesDefault as UnpackNestedValue<FieldPathValue<TFieldValues, Path<TFieldValues>>>}
|
||||
rules={rules}
|
||||
render={({ field: { onChange: rhfOnChange, value, ref } }) => {
|
||||
const selectProps = {
|
||||
classNamePrefix: 'rs',
|
||||
className: 'rs',
|
||||
ref,
|
||||
value: getCurrentValues(value),
|
||||
placeholder,
|
||||
isDisabled,
|
||||
isMulti: true,
|
||||
onChange: val => onChangeCb(val?.map(c => c.value), rhfOnChange),
|
||||
options: allOptions
|
||||
};
|
||||
|
||||
if (loadOptions) {
|
||||
Object.assign(selectProps, { loadOptions, defaultOptions: true, cacheOptions: true });
|
||||
}
|
||||
|
||||
if (creatable) {
|
||||
Object.assign(selectProps, {
|
||||
formatCreateLabel,
|
||||
onCreateOption: inputValue => handleCreate(inputValue, value, rhfOnChange)
|
||||
});
|
||||
}
|
||||
|
||||
return (<AbstractSelect {...selectProps} />);
|
||||
}}
|
||||
/>
|
||||
</AbstractFormItem>
|
||||
);
|
||||
};
|
||||
|
||||
FormMultiSelect.defaultProps = {
|
||||
expectedResult: 'array',
|
||||
creatable: false,
|
||||
disabled: false
|
||||
};
|
||||
|
@ -19,7 +19,7 @@ interface EditSocialsProps<TFieldValues> {
|
||||
export const EditSocials = <TFieldValues extends FieldValues>({ register, setValue, networks, formState, disabled }: EditSocialsProps<TFieldValues>) => {
|
||||
const { t } = useTranslation('shared');
|
||||
// regular expression to validate the the input fields
|
||||
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z\d.]{2,30})([/\w .-]*)*\/?$/;
|
||||
const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/;
|
||||
|
||||
const initSelectedNetworks = networks.filter(el => !['', null, undefined].includes(el.url));
|
||||
const [selectedNetworks, setSelectedNetworks] = useState(initSelectedNetworks);
|
||||
|
@ -58,7 +58,7 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
|
||||
// regular expression to validate the input fields
|
||||
const phoneRegex = /^((00|\+)\d{2,3})?\d{4,14}$/;
|
||||
const urlRegex = /^(https?:\/\/)([\da-z.-]+)\.([-a-z\d.]{2,30})([/\w .-]*)*\/?$/;
|
||||
const urlRegex = /^(https?:\/\/)([^.]+)\.(.{2,30})(\/.*)*\/?$/;
|
||||
|
||||
const { handleSubmit, register, control, formState, setValue, reset } = useForm<User>({ defaultValues: { ...user } });
|
||||
const output = useWatch<User>({ control });
|
||||
@ -67,8 +67,6 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
const [isLocalDatabaseProvider, setIsLocalDatabaseProvider] = useState<boolean>(false);
|
||||
const [groups, setGroups] = useState<selectOption[]>([]);
|
||||
const [termsAndConditions, setTermsAndConditions] = useState<CustomAsset>(null);
|
||||
const [trainings, setTrainings] = useState<selectOption[]>([]);
|
||||
const [tags, setTags] = useState<selectOption[]>([]);
|
||||
const [profileCustomFields, setProfileCustomFields] = useState<ProfileCustomField[]>([]);
|
||||
|
||||
useEffect(() => {
|
||||
@ -83,21 +81,11 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
if (showTermsAndConditionsInput) {
|
||||
CustomAssetAPI.get(CustomAssetName.CguFile).then(setTermsAndConditions).catch(error => onError(error));
|
||||
}
|
||||
if (showTrainingsInput) {
|
||||
TrainingAPI.index({ disabled: false }).then(data => {
|
||||
setTrainings(buildOptions(data));
|
||||
}).catch(error => onError(error));
|
||||
}
|
||||
if (showTagsInput) {
|
||||
TagAPI.index().then(data => {
|
||||
setTags(buildOptions(data));
|
||||
}).catch(error => onError(error));
|
||||
}
|
||||
ProfileCustomFieldAPI.index().then(data => {
|
||||
const fData = data.filter(f => f.actived);
|
||||
setProfileCustomFields(fData);
|
||||
const userProfileCustomFields = fData.map(f => {
|
||||
const upcf = user.invoicing_profile_attributes.user_profile_custom_fields_attributes.find(uf => uf.profile_custom_field_id === f.id);
|
||||
const upcf = user?.invoicing_profile_attributes?.user_profile_custom_fields_attributes?.find(uf => uf.profile_custom_field_id === f.id);
|
||||
return upcf || {
|
||||
value: '',
|
||||
invoicing_profile_id: user.invoicing_profile_attributes.id,
|
||||
@ -117,6 +105,24 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronously load the full list of enabled trainings to display in the drop-down select field
|
||||
*/
|
||||
const loadTrainings = (inputValue: string, callback: (options: Array<selectOption>) => void): void => {
|
||||
TrainingAPI.index({ disabled: false }).then(data => {
|
||||
callback(buildOptions(data));
|
||||
}).catch(error => onError(error));
|
||||
};
|
||||
|
||||
/**
|
||||
* Asynchronously load the full list of tags to display in the drop-down select field
|
||||
*/
|
||||
const loadTags = (inputValue: string, callback: (options: Array<selectOption>) => void): void => {
|
||||
TagAPI.index().then(data => {
|
||||
callback(buildOptions(data));
|
||||
}).catch(error => onError(error));
|
||||
};
|
||||
|
||||
/**
|
||||
* Callback triggered when the form is submitted: process with the user creation or update.
|
||||
*/
|
||||
@ -344,14 +350,14 @@ export const UserProfileForm: React.FC<UserProfileFormProps> = ({ action, size,
|
||||
</div>}
|
||||
{showTrainingsInput && <div className="trainings">
|
||||
<FormMultiSelect control={control}
|
||||
options={trainings}
|
||||
loadOptions={loadTrainings}
|
||||
formState={formState}
|
||||
label={t('app.shared.user_profile_form.trainings')}
|
||||
id="statistic_profile_attributes.training_ids" />
|
||||
</div>}
|
||||
{showTagsInput && <div className="tags">
|
||||
<FormMultiSelect control={control}
|
||||
options={tags}
|
||||
loadOptions={loadTags}
|
||||
formState={formState}
|
||||
label={t('app.shared.user_profile_form.tags')}
|
||||
id="tag_ids" />
|
||||
|
@ -530,6 +530,13 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
|
||||
$scope.cancel = function () { $uibModalInstance.dismiss('cancel'); };
|
||||
|
||||
$scope.rateValue = function (value) {
|
||||
if (value.rate === 'null' || value.value === 'undefined' || value.rate === 'NaN') {
|
||||
return '';
|
||||
}
|
||||
return value.rate;
|
||||
};
|
||||
|
||||
const initialize = function () {
|
||||
rateHistory.setting.history.forEach(function (rate) {
|
||||
$scope.history.push({ date: rate.created_at, rate: rate.value, user: rate.user });
|
||||
@ -544,7 +551,8 @@ Application.Controllers.controller('InvoicesController', ['$scope', '$state', 'I
|
||||
});
|
||||
return editMultiVATModalInstance.result.then(function (result) {
|
||||
['Machine', 'Space', 'Training', 'Event', 'Subscription'].forEach(rateType => {
|
||||
Setting.update({ name: `invoice_VAT-rate_${rateType}` }, { value: result.multiVAT[`rate${rateType}`] + '' }, function (data) {
|
||||
const value = _.isFinite(result.multiVAT[`rate${rateType}`]) ? result.multiVAT[`rate${rateType}`] + '' : '';
|
||||
Setting.update({ name: `invoice_VAT-rate_${rateType}` }, { value }, function (data) {
|
||||
return growl.success(_t('app.admin.invoices.VAT_rate_successfully_saved'));
|
||||
}
|
||||
, function (error) {
|
||||
|
@ -432,7 +432,7 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
size: 'sm',
|
||||
resolve: {
|
||||
settingsPromise: ['Setting', function (Setting) {
|
||||
return Setting.query({ names: "['confirmation_required']" }).$promise;
|
||||
return Setting.query({ names: "['confirmation_required', 'public_registrations']" }).$promise;
|
||||
}]
|
||||
},
|
||||
controller: ['$scope', '$uibModalInstance', '_t', 'settingsPromise', function ($scope, $uibModalInstance, _t, settingsPromise) {
|
||||
@ -441,6 +441,9 @@ Application.Controllers.controller('ApplicationController', ['$rootScope', '$sco
|
||||
// email confirmation required before user sign-in?
|
||||
$scope.confirmationRequired = settingsPromise.confirmation_required;
|
||||
|
||||
// is public registrations allowed?
|
||||
$scope.publicRegistrations = (settingsPromise.public_registrations !== 'false');
|
||||
|
||||
$scope.login = function () {
|
||||
Auth.login(user).then(function (user) {
|
||||
// Authentication succeeded ...
|
||||
|
@ -166,9 +166,9 @@ Application.Controllers.controller('CalendarController', ['$scope', '$state', '$
|
||||
$scope.calendarConfig = CalendarConfig({
|
||||
slotEventOverlap: true,
|
||||
header: {
|
||||
left: 'month agendaWeek agendaDay',
|
||||
left: 'month agendaWeek agendaDay today prev,next',
|
||||
center: 'title',
|
||||
right: 'today prev,next'
|
||||
right: ''
|
||||
},
|
||||
minTime: moment.duration(moment(bookingWindowStart.setting.value).format('HH:mm:ss')),
|
||||
maxTime: moment.duration(moment(bookingWindowEnd.setting.value).format('HH:mm:ss')),
|
||||
|
@ -1,7 +1,12 @@
|
||||
Application.Directives.directive('members', ['Member',
|
||||
function (Member) {
|
||||
Application.Directives.directive('members', ['Member', 'Setting',
|
||||
function (Member, Setting) {
|
||||
return ({
|
||||
restrict: 'E',
|
||||
resolve: {
|
||||
settingsPromise: ['Setting', function (Setting) {
|
||||
return Setting.query({ names: "['public_registrations']" }).$promise;
|
||||
}]
|
||||
},
|
||||
templateUrl: '/home/members.html',
|
||||
link ($scope, element, attributes) {
|
||||
// The last registered members who confirmed their addresses
|
||||
@ -12,6 +17,10 @@ Application.Directives.directive('members', ['Member',
|
||||
Member.lastSubscribed({ limit: 4 }, function (data) {
|
||||
$scope.lastMembers = data;
|
||||
});
|
||||
Setting.query({ names: "['public_registrations']" }, function (data) {
|
||||
// is public registrations allowed?
|
||||
$scope.publicRegistrations = (data.public_registrations !== 'false');
|
||||
});
|
||||
};
|
||||
|
||||
// !!! MUST BE CALLED AT THE END of the directive
|
||||
|
@ -47,7 +47,7 @@ export interface OpenIdConnectProvider {
|
||||
issuer: string,
|
||||
discovery: boolean,
|
||||
client_auth_method?: 'basic' | 'jwks',
|
||||
scope?: string,
|
||||
scope?: Array<string>,
|
||||
prompt?: 'none' | 'login' | 'consent' | 'select_account',
|
||||
send_scope_to_token_endpoint?: string,
|
||||
client__identifier: string,
|
||||
|
@ -53,12 +53,14 @@ export interface User {
|
||||
address: string
|
||||
}
|
||||
},
|
||||
user_profile_custom_fields_attributes: {
|
||||
id: number,
|
||||
value: string,
|
||||
invoicing_profile_id: number,
|
||||
profile_custom_field_id: number
|
||||
}
|
||||
user_profile_custom_fields_attributes: Array<
|
||||
{
|
||||
id?: number,
|
||||
value: string,
|
||||
invoicing_profile_id: number,
|
||||
profile_custom_field_id: number
|
||||
}
|
||||
>
|
||||
},
|
||||
statistic_profile_attributes: {
|
||||
id: number,
|
||||
@ -84,7 +86,8 @@ export interface User {
|
||||
training_credits: Array<number>,
|
||||
machine_credits: Array<{ machine_id: number, hours_used: number }>,
|
||||
last_sign_in_at: TDateISO
|
||||
validated_at: TDateISO
|
||||
validated_at: TDateISO,
|
||||
tag_ids: Array<number>
|
||||
}
|
||||
|
||||
type OrderingKey = 'last_name' | 'first_name' | 'email' | 'phone' | 'group' | 'plan' | 'id'
|
||||
|
@ -13,9 +13,9 @@ Application.Services.factory('CalendarConfig', [() =>
|
||||
timezone: Fablab.timezone,
|
||||
locale: Fablab.fullcalendar_locale,
|
||||
header: {
|
||||
left: 'month agendaWeek',
|
||||
left: 'month agendaWeek today prev,next',
|
||||
center: 'title',
|
||||
right: 'today prev,next'
|
||||
right: ''
|
||||
},
|
||||
firstDay: Fablab.weekStartingDay,
|
||||
scrollTime: DEFAULT_CALENDAR_POSITION,
|
||||
|
@ -55,10 +55,14 @@
|
||||
box-shadow: none;
|
||||
text-shadow: none;
|
||||
margin: 0;
|
||||
margin-right: 0.5rem;
|
||||
height: 40px;
|
||||
line-height: 18px;
|
||||
padding: 10px;
|
||||
}
|
||||
.fc-button-group .fc-button {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.fc-toolbar h2 {
|
||||
font-size: 15px;
|
||||
|
@ -77,7 +77,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m-t-lg" ng-if="availability" ng-hide="availability.available_type == 'event'">
|
||||
<div class="widget panel b-a" ng-if="availability" ng-hide="availability.available_type == 'event'">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'app.admin.calendar.ongoing_reservations' }}</h3>
|
||||
</div>
|
||||
@ -96,7 +96,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m-t-lg" ng-if="availability.machine_ids.length > 0">
|
||||
<div class="widget panel b-a" ng-if="availability.machine_ids.length > 0">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'app.admin.calendar.machines' }}</h3>
|
||||
</div>
|
||||
@ -110,7 +110,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m-t-lg" ng-if="availability.plan_ids.length > 0">
|
||||
<div class="widget panel b-a" ng-if="availability.plan_ids.length > 0">
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'app.admin.calendar.plans' }}</h3>
|
||||
</div>
|
||||
@ -129,7 +129,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="widget panel b-a m-t-lg" ng-if="availability" >
|
||||
<div class="widget panel b-a" ng-if="availability" >
|
||||
<div class="panel-heading b-b small">
|
||||
<h3 translate>{{ 'app.admin.calendar.actions' }}</h3>
|
||||
</div>
|
||||
|
@ -17,7 +17,7 @@
|
||||
<td>
|
||||
<span class="no-user-label" ng-show="value.enabled === false" translate>{{'app.admin.invoices.VAT_disabled'}}</span>
|
||||
<span class="no-user-label" ng-show="value.enabled === true" translate>{{'app.admin.invoices.VAT_enabled'}}</span>
|
||||
<span ng-show="value.rate">{{value.rate}}</span>
|
||||
<span ng-show="value.rate">{{rateValue(value)}}</span>
|
||||
</td>
|
||||
<td>{{value.date | amDateFormat:'L LT'}}</td>
|
||||
<td>{{value.user.name}}<span class="no-user-label" ng-hide="value.user" translate>{{ 'app.admin.invoices.deleted_user' }}</span></td>
|
||||
|
@ -17,7 +17,7 @@
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<div class="m-t-sm m-b-sm text-center" ng-if="!isAuthenticated()">
|
||||
<div class="m-t-sm m-b-sm text-center" ng-if="!isAuthenticated()" ng-show="publicRegistrations">
|
||||
<button ng-click="signup($event)" class="btn btn-warning-full width-70 font-sbold rounded text-sm" translate>{{ 'app.public.home.create_an_account' }}</button>
|
||||
</div>
|
||||
|
||||
|
@ -60,7 +60,7 @@
|
||||
<button class="btn btn-valid btn-warning btn-block p-l btn-lg text-u-c r-b" ng-click="login()" ng-disabled="loginForm.$invalid" translate translate-default="OK">{{ 'app.shared.buttons.confirm' }}</button>
|
||||
</div>
|
||||
</div>
|
||||
<p class="text-center font-sbold">
|
||||
<p class="text-center font-sbold" ng-show="publicRegistrations">
|
||||
<span translate translate-default="Not registered?">{{ 'app.public.common.not_registered_to_the_fablab' }}</span>
|
||||
<br/>
|
||||
<a ng-click="openSignup($event)" class="text-u-l" translate translate-default="Create an account">{{ 'app.public.common.create_an_account' }}</a></br>
|
||||
|
@ -165,8 +165,8 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div ng-repeat="(i, profileCustomField) in profileCustomFields">
|
||||
<div class="form-group required-row" ng-show="user.organization" ng-class="{'has-error': signupForm.user_profile_custom_fields{{i}}.$dirty && signupForm.user_profile_custom_fields{{i}}.$invalid}">
|
||||
<div ng-if="user.organization" ng-repeat="(i, profileCustomField) in profileCustomFields">
|
||||
<div class="form-group required-row" ng-class="{'has-error': signupForm.user_profile_custom_fields{{i}}.$dirty && signupForm.user_profile_custom_fields{{i}}.$invalid}">
|
||||
<div class="col-sm-12">
|
||||
<div class="input-group">
|
||||
<span class="input-group-addon"><i class="fa fa-building-o"></i></span>
|
||||
|
@ -17,6 +17,10 @@ class OpenIdConnectProvider < ApplicationRecord
|
||||
validates :prompt, inclusion: { in: %w[none login consent select_account], allow_nil: true }
|
||||
validates :client_auth_method, inclusion: { in: %w[basic jwks] }
|
||||
|
||||
def scope
|
||||
self[:scope].join(' ')
|
||||
end
|
||||
|
||||
def config
|
||||
OpenIdConnectProvider.columns.map(&:name).filter { |n| !n.start_with?('client__') && n != 'profile_url' }.map do |n|
|
||||
[n, send(n)]
|
||||
|
@ -22,7 +22,7 @@ class AuthProviderService
|
||||
provider.providable.response_type = 'code'
|
||||
provider.providable.uid_field = provider.auth_provider_mappings
|
||||
.find { |m| m.local_model == 'user' && m.local_field == 'uid' }
|
||||
.api_field
|
||||
&.api_field
|
||||
|
||||
URI.parse(provider.providable.issuer).tap do |uri|
|
||||
provider.providable.client__scheme = uri.scheme
|
||||
|
@ -50,7 +50,7 @@ class Members::ListService
|
||||
'SELECT max("created_at") ' \
|
||||
'FROM "subscriptions" ' \
|
||||
'WHERE "statistic_profile_id" = "statistic_profiles"."id")')
|
||||
.where("users.is_active = 'true' AND (roles.name = 'member' OR roles.name = 'manager')")
|
||||
.where("users.is_active = 'true'")
|
||||
.limit(50)
|
||||
query.downcase.split(' ').each do |word|
|
||||
members = members.where('lower(f_unaccent(profiles.first_name)) ~ :search OR ' \
|
||||
|
@ -72,7 +72,7 @@ class VatHistoryService
|
||||
.history_values.where('created_at >= ?', first_vat_rate_by_type.created_at)
|
||||
.order(created_at: 'ASC')
|
||||
vat_rate_by_type.each do |rate|
|
||||
if rate.value.blank?
|
||||
if rate.value.blank? || rate.value == 'null' || rate.value == 'undefined' || rate.value == 'NaN'
|
||||
# if, at some point in the history, a blank rate was set, the general VAT rate is used instead
|
||||
vat_rate = Setting.find_by(name: 'invoice_VAT-rate')
|
||||
.history_values.where('created_at < ?', rate.created_at)
|
||||
|
@ -77,7 +77,9 @@ Rails.application.configure do
|
||||
authentication: Rails.application.secrets.smtp_authentication,
|
||||
enable_starttls_auto: Rails.application.secrets.smtp_enable_starttls_auto,
|
||||
openssl_verify_mode: Rails.application.secrets.smtp_openssl_verify_mode,
|
||||
tls: Rails.application.secrets.smtp_tls
|
||||
tls: Rails.application.secrets.smtp_tls,
|
||||
ca_file: Rails.application.secrets.smtp_ca_file,
|
||||
ca_path: Rails.application.secrets.smtp_ca_path
|
||||
}
|
||||
# use :smtp for switch prod
|
||||
config.action_mailer.delivery_method = Rails.application.secrets.delivery_method.to_sym
|
||||
|
@ -527,3 +527,5 @@ en:
|
||||
payzen_card_update_modal:
|
||||
update_card: "Update the card"
|
||||
validate_button: "Validate the new card"
|
||||
form_multi_select:
|
||||
create_label: "Add {VALUE}"
|
@ -154,7 +154,7 @@ fr:
|
||||
member_select:
|
||||
select_a_member: "Sélectionnez un membre"
|
||||
start_typing: "Commencez à écrire..."
|
||||
member_not_validated: "Attention :</br> Le membre n'a pas validé."
|
||||
member_not_validated: "Attention :</br> Le compte du membre n'est pas validé."
|
||||
#payment modal
|
||||
payment:
|
||||
online_payment: "Paiement en ligne"
|
||||
|
@ -137,6 +137,8 @@ production:
|
||||
smtp_enable_starttls_auto: <%= ENV["SMTP_ENABLE_STARTTLS_AUTO"] %>
|
||||
smtp_openssl_verify_mode: <%= ENV["SMTP_OPENSSL_VERIFY_MODE"] %>
|
||||
smtp_tls: <%= ENV["SMTP_TLS"] %>
|
||||
smtp_ca_file: <%= ENV.fetch("SMTP_CA_FILE", nil) %>
|
||||
smtp_ca_path: <%= ENV.fetch("SMTP_CA_PATH", nil) %>
|
||||
week_starting_day: <%= ENV["WEEK_STARTING_DAY"] %>
|
||||
d3_date_format: <%= ENV.fetch("D3_DATE_FORMAT", '%y-%m-%d').dump %>
|
||||
uib_date_format: <%= ENV["UIB_DATE_FORMAT"] %>
|
||||
|
9
db/migrate/20220531160223_change_oidc_scope_to_array.rb
Normal file
9
db/migrate/20220531160223_change_oidc_scope_to_array.rb
Normal file
@ -0,0 +1,9 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# Previously, the OpenID Connect scope was a string, scopes were separated by commas.
|
||||
# To be more fron-end friendly, we now use an array.
|
||||
class ChangeOidcScopeToArray < ActiveRecord::Migration[5.2]
|
||||
def change
|
||||
change_column :open_id_connect_providers, :scope, "varchar[] USING (string_to_array(scope, ','))"
|
||||
end
|
||||
end
|
24
db/schema.rb
24
db/schema.rb
@ -10,7 +10,7 @@
|
||||
#
|
||||
# It's strongly recommended that you check this file into your version control system.
|
||||
|
||||
ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
ActiveRecord::Schema.define(version: 2022_05_31_160223) do
|
||||
|
||||
# These are extensions that must be enabled in order to support this database
|
||||
enable_extension "fuzzystrmatch"
|
||||
@ -19,8 +19,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
enable_extension "unaccent"
|
||||
|
||||
create_table "abuses", id: :serial, force: :cascade do |t|
|
||||
t.string "signaled_type"
|
||||
t.integer "signaled_id"
|
||||
t.string "signaled_type"
|
||||
t.string "first_name"
|
||||
t.string "last_name"
|
||||
t.string "email"
|
||||
@ -49,8 +49,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
t.string "locality"
|
||||
t.string "country"
|
||||
t.string "postal_code"
|
||||
t.string "placeable_type"
|
||||
t.integer "placeable_id"
|
||||
t.string "placeable_type"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
end
|
||||
@ -64,8 +64,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
end
|
||||
|
||||
create_table "assets", id: :serial, force: :cascade do |t|
|
||||
t.string "viewable_type"
|
||||
t.integer "viewable_id"
|
||||
t.string "viewable_type"
|
||||
t.string "attachment"
|
||||
t.string "type"
|
||||
t.datetime "created_at"
|
||||
@ -146,8 +146,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
end
|
||||
|
||||
create_table "credits", id: :serial, force: :cascade do |t|
|
||||
t.string "creditable_type"
|
||||
t.integer "creditable_id"
|
||||
t.string "creditable_type"
|
||||
t.integer "plan_id"
|
||||
t.integer "hours"
|
||||
t.datetime "created_at"
|
||||
@ -369,15 +369,15 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
|
||||
create_table "notifications", id: :serial, force: :cascade do |t|
|
||||
t.integer "receiver_id"
|
||||
t.string "attached_object_type"
|
||||
t.integer "attached_object_id"
|
||||
t.string "attached_object_type"
|
||||
t.integer "notification_type_id"
|
||||
t.boolean "is_read", default: false
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "receiver_type"
|
||||
t.boolean "is_send", default: false
|
||||
t.jsonb "meta_data", default: "{}"
|
||||
t.jsonb "meta_data", default: {}
|
||||
t.index ["notification_type_id"], name: "index_notifications_on_notification_type_id"
|
||||
t.index ["receiver_id"], name: "index_notifications_on_receiver_id"
|
||||
end
|
||||
@ -415,7 +415,7 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
t.string "issuer"
|
||||
t.boolean "discovery"
|
||||
t.string "client_auth_method"
|
||||
t.string "scope"
|
||||
t.string "scope", array: true
|
||||
t.string "response_type"
|
||||
t.string "response_mode"
|
||||
t.string "display"
|
||||
@ -570,8 +570,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
create_table "prices", id: :serial, force: :cascade do |t|
|
||||
t.integer "group_id"
|
||||
t.integer "plan_id"
|
||||
t.string "priceable_type"
|
||||
t.integer "priceable_id"
|
||||
t.string "priceable_type"
|
||||
t.integer "amount"
|
||||
t.datetime "created_at", null: false
|
||||
t.datetime "updated_at", null: false
|
||||
@ -729,8 +729,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
t.text "message"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.string "reservable_type"
|
||||
t.integer "reservable_id"
|
||||
t.string "reservable_type"
|
||||
t.integer "nb_reserve_places"
|
||||
t.integer "statistic_profile_id"
|
||||
t.index ["reservable_type", "reservable_id"], name: "index_reservations_on_reservable_type_and_reservable_id"
|
||||
@ -739,8 +739,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
|
||||
create_table "roles", id: :serial, force: :cascade do |t|
|
||||
t.string "name"
|
||||
t.string "resource_type"
|
||||
t.integer "resource_id"
|
||||
t.string "resource_type"
|
||||
t.datetime "created_at"
|
||||
t.datetime "updated_at"
|
||||
t.index ["name", "resource_type", "resource_id"], name: "index_roles_on_name_and_resource_type_and_resource_id"
|
||||
@ -1021,8 +1021,8 @@ ActiveRecord::Schema.define(version: 2022_05_17_140916) do
|
||||
t.boolean "is_allow_newsletter"
|
||||
t.inet "current_sign_in_ip"
|
||||
t.inet "last_sign_in_ip"
|
||||
t.datetime "validated_at"
|
||||
t.string "mapped_from_sso"
|
||||
t.datetime "validated_at"
|
||||
t.index ["auth_token"], name: "index_users_on_auth_token"
|
||||
t.index ["confirmation_token"], name: "index_users_on_confirmation_token", unique: true
|
||||
t.index ["email"], name: "index_users_on_email", unique: true
|
||||
|
@ -64,6 +64,26 @@ See http://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-config
|
||||
|
||||
When DELIVERY_METHOD is set to **smtp**, configure the SMTP server parameters.
|
||||
See https://guides.rubyonrails.org/action_mailer_basics.html#action-mailer-configuration for more details.
|
||||
<a name="SMTP_CA_FILE"></a>
|
||||
|
||||
SMTP_CA_FILE
|
||||
|
||||
The path to a file containing a PEM-format CA certificate.
|
||||
See [OpenSSL::SSL::SSLContext](https://ruby-doc.org/stdlib-2.6/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html).
|
||||
This is an undocumented setting of [mail gem](https://github.com/mikel/mail).
|
||||
In production with Docker, you may need to mount your certificate into the running container, by adding an entry in your [docker-compose.yml](https://github.com/sleede/fab-manager/blob/master/setup/docker-compose.yml), under `services > fabmanager > volumes`, like the following: `- ${PWD}/certs/ca-cert-file.pem:/etc/ssl/my-certs/ca-cert-file.pem`
|
||||
<a name="SMTP_CA_PATH"></a>
|
||||
|
||||
SMTP_CA_PATH
|
||||
|
||||
The path to a directory containing CA certificates in PEM format.
|
||||
See [OpenSSL::SSL::SSLContext](https://ruby-doc.org/stdlib-2.6/libdoc/openssl/rdoc/OpenSSL/SSL/SSLContext.html).
|
||||
This is an undocumented setting of [mail gem](https://github.com/mikel/mail).
|
||||
In production with Docker, you may need to mount your certificate into the running container, by adding an entry in your [docker-compose.yml](https://github.com/sleede/fab-manager/blob/master/setup/docker-compose.yml), under `services > fabmanager > volumes`, like the following: `- ${PWD}/certs:/etc/ssl/my-certs`
|
||||
<a name="SMTP_TLS"></a>
|
||||
|
||||
SMTP_TLS
|
||||
Enables the SMTP connection to use SMTP/TLS (SMTPS: SMTP over direct TLS connection).
|
||||
<a name="DEFAULT_HOST"></a><a name="DEFAULT_PROTOCOL"></a>
|
||||
|
||||
DEFAULT_HOST, DEFAULT_PROTOCOL
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "fab-manager",
|
||||
"version": "5.4.1",
|
||||
"version": "5.4.2",
|
||||
"description": "Fab-manager is the FabLab management solution. It provides a comprehensive, web-based, open-source tool to simplify your administrative tasks and your marker's projects.",
|
||||
"keywords": [
|
||||
"fablab",
|
||||
@ -137,7 +137,7 @@
|
||||
"react-hook-form": "^7.30.0",
|
||||
"react-i18next": "^11.15.6",
|
||||
"react-modal": "^3.11.2",
|
||||
"react-select": "^5.2.2",
|
||||
"react-select": "^5.3.2",
|
||||
"react-switch": "^6.0.0",
|
||||
"react2angular": "^4.0.6",
|
||||
"resolve-url-loader": "^4.0.0",
|
||||
|
@ -4,11 +4,11 @@ docker-compose()
|
||||
{
|
||||
if ! docker compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
if ! \docker-compose version 1>/dev/null 2>/dev/null
|
||||
if ! command docker-compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1
|
||||
else
|
||||
\docker-compose "$@"
|
||||
command docker-compose "$@"
|
||||
fi
|
||||
else
|
||||
docker compose "$@"
|
||||
|
@ -12,11 +12,11 @@ docker-compose()
|
||||
{
|
||||
if ! docker compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
if ! \docker-compose version 1>/dev/null 2>/dev/null
|
||||
if ! command docker-compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1
|
||||
else
|
||||
\docker-compose "$@"
|
||||
command docker-compose "$@"
|
||||
fi
|
||||
else
|
||||
docker compose "$@"
|
||||
|
@ -63,11 +63,11 @@ docker-compose()
|
||||
{
|
||||
if ! docker compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
if ! \docker-compose version 1>/dev/null 2>/dev/null
|
||||
if ! command docker-compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1
|
||||
else
|
||||
\docker-compose "$@"
|
||||
command docker-compose "$@"
|
||||
fi
|
||||
else
|
||||
docker compose "$@"
|
||||
|
@ -47,11 +47,11 @@ docker-compose()
|
||||
{
|
||||
if ! docker compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
if ! \docker-compose version 1>/dev/null 2>/dev/null
|
||||
if ! command docker-compose version 1>/dev/null 2>/dev/null
|
||||
then
|
||||
echo -e "\e[91m[ ❌ ] docker-compose was not found, exiting...\e[39m" && exit 1
|
||||
else
|
||||
\docker-compose "$@"
|
||||
command docker-compose "$@"
|
||||
fi
|
||||
else
|
||||
docker compose "$@"
|
||||
|
139
yarn.lock
139
yarn.lock
@ -370,6 +370,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.16.7.tgz#aa3a8ab4c3cceff8e65eb9e73d87dc4ff320b2f5"
|
||||
integrity sha512-Qg3Nk7ZxpgMrsox6HreY1ZNKdBq7K72tDSliA6dCl5f007jR4ne8iD5UzuNnCJH2xBf2BEEVGr+/OL6Gdp7RxA==
|
||||
|
||||
"@babel/helper-plugin-utils@^7.17.12":
|
||||
version "7.17.12"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.17.12.tgz#86c2347da5acbf5583ba0a10aed4c9bf9da9cf96"
|
||||
integrity sha512-JDkf04mqtN3y4iAbO1hv9U2ARpPyPL1zqyWs/2WG1pgSq9llHFjStX5jdxb84himgJm+8Ng+x0oiWF/nw/XQKA==
|
||||
|
||||
"@babel/helper-remap-async-to-generator@^7.16.8":
|
||||
version "7.16.8"
|
||||
resolved "https://registry.yarnpkg.com/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.16.8.tgz#29ffaade68a367e2ed09c90901986918d25e57e3"
|
||||
@ -719,6 +724,13 @@
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.8.0"
|
||||
|
||||
"@babel/plugin-syntax-jsx@^7.12.13":
|
||||
version "7.17.12"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.17.12.tgz#834035b45061983a491f60096f61a2e7c5674a47"
|
||||
integrity sha512-spyY3E3AURfxh/RHtjx5j6hs8am5NbUBGfcZ2vB3uShSpZdQyXSf5rR5Mk76vbtlAZOelyVQ71Fg0x9SG4fsog==
|
||||
dependencies:
|
||||
"@babel/helper-plugin-utils" "^7.17.12"
|
||||
|
||||
"@babel/plugin-syntax-jsx@^7.16.7":
|
||||
version "7.16.7"
|
||||
resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-jsx/-/plugin-syntax-jsx-7.16.7.tgz#50b6571d13f764266a113d77c82b4a6508bbe665"
|
||||
@ -1370,6 +1382,13 @@
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/runtime@^7.7.2":
|
||||
version "7.18.3"
|
||||
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.18.3.tgz#c7b654b57f6f63cf7f8b418ac9ca04408c4579f4"
|
||||
integrity sha512-38Y8f7YUhce/K7RMwTp7m0uCumpv9hZkitCbBClqQIow1qSbCvGkcegKOXpEWCQLfWmevgRiWokZ1GkpfhbZug==
|
||||
dependencies:
|
||||
regenerator-runtime "^0.13.4"
|
||||
|
||||
"@babel/template@^7.14.5":
|
||||
version "7.14.5"
|
||||
resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.14.5.tgz#a9bc9d8b33354ff6e55a9c60d1109200a68974f4"
|
||||
@ -1469,6 +1488,24 @@
|
||||
resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.6.tgz#d5e0706cf8c6acd8c6032f8d54070af261bbbb2f"
|
||||
integrity sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==
|
||||
|
||||
"@emotion/babel-plugin@^11.7.1":
|
||||
version "11.9.2"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/babel-plugin/-/babel-plugin-11.9.2.tgz#723b6d394c89fb2ef782229d92ba95a740576e95"
|
||||
integrity sha512-Pr/7HGH6H6yKgnVFNEj2MVlreu3ADqftqjqwUvDy/OJzKFgxKeTQ+eeUf20FOTuHVkDON2iNa25rAXVYtWJCjw==
|
||||
dependencies:
|
||||
"@babel/helper-module-imports" "^7.12.13"
|
||||
"@babel/plugin-syntax-jsx" "^7.12.13"
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@emotion/hash" "^0.8.0"
|
||||
"@emotion/memoize" "^0.7.5"
|
||||
"@emotion/serialize" "^1.0.2"
|
||||
babel-plugin-macros "^2.6.1"
|
||||
convert-source-map "^1.5.0"
|
||||
escape-string-regexp "^4.0.0"
|
||||
find-root "^1.1.0"
|
||||
source-map "^0.5.7"
|
||||
stylis "4.0.13"
|
||||
|
||||
"@emotion/cache@^11.4.0":
|
||||
version "11.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.4.0.tgz#293fc9d9a7a38b9aad8e9337e5014366c3b09ac0"
|
||||
@ -1480,26 +1517,37 @@
|
||||
"@emotion/weak-memoize" "^0.2.5"
|
||||
stylis "^4.0.3"
|
||||
|
||||
"@emotion/cache@^11.7.1":
|
||||
version "11.7.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/cache/-/cache-11.7.1.tgz#08d080e396a42e0037848214e8aa7bf879065539"
|
||||
integrity sha512-r65Zy4Iljb8oyjtLeCuBH8Qjiy107dOYC6SJq7g7GV5UCQWMObY4SJDPGFjiiVpPrOJ2hmJOoBiYTC7hwx9E2A==
|
||||
dependencies:
|
||||
"@emotion/memoize" "^0.7.4"
|
||||
"@emotion/sheet" "^1.1.0"
|
||||
"@emotion/utils" "^1.0.0"
|
||||
"@emotion/weak-memoize" "^0.2.5"
|
||||
stylis "4.0.13"
|
||||
|
||||
"@emotion/hash@^0.8.0":
|
||||
version "0.8.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/hash/-/hash-0.8.0.tgz#bbbff68978fefdbe68ccb533bc8cbe1d1afb5413"
|
||||
integrity sha512-kBJtf7PH6aWwZ6fka3zQ0p6SBYzx4fl1LoZXE2RrnYST9Xljm7WfKJrU4g/Xr3Beg72MLrp1AWNUmuYJTL7Cow==
|
||||
|
||||
"@emotion/memoize@^0.7.4":
|
||||
"@emotion/memoize@^0.7.4", "@emotion/memoize@^0.7.5":
|
||||
version "0.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/memoize/-/memoize-0.7.5.tgz#2c40f81449a4e554e9fc6396910ed4843ec2be50"
|
||||
integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
|
||||
|
||||
"@emotion/react@^11.1.1":
|
||||
version "11.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.4.0.tgz#2465ad7b073a691409b88dfd96dc17097ddad9b7"
|
||||
integrity sha512-4XklWsl9BdtatLoJpSjusXhpKv9YVteYKh9hPKP1Sxl+mswEFoUe0WtmtWjxEjkA51DQ2QRMCNOvKcSlCQ7ivg==
|
||||
"@emotion/react@^11.8.1":
|
||||
version "11.9.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/react/-/react-11.9.0.tgz#b6d42b1db3bd7511e7a7c4151dc8bc82e14593b8"
|
||||
integrity sha512-lBVSF5d0ceKtfKCDQJveNAtkC7ayxpVlgOohLgXqRwqWr9bOf4TZAFFyIcNngnV6xK6X4x2ZeXq7vliHkoVkxQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.13.10"
|
||||
"@emotion/cache" "^11.4.0"
|
||||
"@emotion/serialize" "^1.0.2"
|
||||
"@emotion/sheet" "^1.0.1"
|
||||
"@emotion/utils" "^1.0.0"
|
||||
"@emotion/babel-plugin" "^11.7.1"
|
||||
"@emotion/cache" "^11.7.1"
|
||||
"@emotion/serialize" "^1.0.3"
|
||||
"@emotion/utils" "^1.1.0"
|
||||
"@emotion/weak-memoize" "^0.2.5"
|
||||
hoist-non-react-statics "^3.3.1"
|
||||
|
||||
@ -1514,11 +1562,27 @@
|
||||
"@emotion/utils" "^1.0.0"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@emotion/sheet@^1.0.0", "@emotion/sheet@^1.0.1":
|
||||
"@emotion/serialize@^1.0.3":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.3.tgz#99e2060c26c6292469fb30db41f4690e1c8fea63"
|
||||
integrity sha512-2mSSvgLfyV3q+iVh3YWgNlUc2a9ZlDU7DjuP5MjK3AXRR0dYigCrP99aeFtaB2L/hjfEZdSThn5dsZ0ufqbvsA==
|
||||
dependencies:
|
||||
"@emotion/hash" "^0.8.0"
|
||||
"@emotion/memoize" "^0.7.4"
|
||||
"@emotion/unitless" "^0.7.5"
|
||||
"@emotion/utils" "^1.0.0"
|
||||
csstype "^3.0.2"
|
||||
|
||||
"@emotion/sheet@^1.0.0":
|
||||
version "1.0.1"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.0.1.tgz#245f54abb02dfd82326e28689f34c27aa9b2a698"
|
||||
integrity sha512-GbIvVMe4U+Zc+929N1V7nW6YYJtidj31lidSmdYcWozwoBIObXBnaJkKNDjZrLm9Nc0BR+ZyHNaRZxqNZbof5g==
|
||||
|
||||
"@emotion/sheet@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/sheet/-/sheet-1.1.0.tgz#56d99c41f0a1cda2726a05aa6a20afd4c63e58d2"
|
||||
integrity sha512-u0AX4aSo25sMAygCuQTzS+HsImZFuS8llY8O7b9MDRzbJM0kVJlAz6KNDqcG7pOuQZJmj/8X/rAW+66kMnMW+g==
|
||||
|
||||
"@emotion/unitless@^0.7.5":
|
||||
version "0.7.5"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/unitless/-/unitless-0.7.5.tgz#77211291c1900a700b8a78cfafda3160d76949ed"
|
||||
@ -1529,6 +1593,11 @@
|
||||
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.0.0.tgz#abe06a83160b10570816c913990245813a2fd6af"
|
||||
integrity sha512-mQC2b3XLDs6QCW+pDQDiyO/EdGZYOygE8s5N5rrzjSI4M3IejPE/JPndCBwRT9z982aqQNi6beWs1UeayrQxxA==
|
||||
|
||||
"@emotion/utils@^1.1.0":
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/utils/-/utils-1.1.0.tgz#86b0b297f3f1a0f2bdb08eeac9a2f49afd40d0cf"
|
||||
integrity sha512-iRLa/Y4Rs5H/f2nimczYmS5kFJEbpiVvgN3XVfZ022IYhuNA1IRSHEizcof88LtCTXtl9S2Cxt32KgaXEu72JQ==
|
||||
|
||||
"@emotion/weak-memoize@^0.2.5":
|
||||
version "0.2.5"
|
||||
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
|
||||
@ -2965,6 +3034,15 @@ babel-plugin-dynamic-import-node@^2.3.3:
|
||||
dependencies:
|
||||
object.assign "^4.1.0"
|
||||
|
||||
babel-plugin-macros@^2.6.1:
|
||||
version "2.8.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-2.8.0.tgz#0f958a7cc6556b1e65344465d99111a1e5e10138"
|
||||
integrity sha512-SEP5kJpfGYqYKpBrj5XU3ahw5p5GOHJ0U5ssOSQ/WBVdwkD2Dzlce95exQTs3jOVWPPKLBN2rlEWkCK7dSmLvg==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.7.2"
|
||||
cosmiconfig "^6.0.0"
|
||||
resolve "^1.12.0"
|
||||
|
||||
babel-plugin-macros@^3.1.0:
|
||||
version "3.1.0"
|
||||
resolved "https://registry.yarnpkg.com/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz#9ef6dc74deb934b4db344dc973ee851d148c50c1"
|
||||
@ -3379,7 +3457,7 @@ content-type@~1.0.4:
|
||||
resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.4.tgz#e138cc75e040c727b1966fe5e5f8c9aee256fe3b"
|
||||
integrity sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==
|
||||
|
||||
convert-source-map@^1.7.0:
|
||||
convert-source-map@^1.5.0, convert-source-map@^1.7.0:
|
||||
version "1.8.0"
|
||||
resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.8.0.tgz#f3373c32d21b4d780dd8004514684fb791ca4369"
|
||||
integrity sha512-+OQdjP49zViI/6i7nIJpA8rAl4sV/JdPfU9nZs3VqOwGIgizICvuN2ru6fMd+4llL0tar18UYJXfZ/TWtmhUjA==
|
||||
@ -3427,6 +3505,17 @@ core-util-is@~1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
|
||||
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
|
||||
|
||||
cosmiconfig@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
|
||||
integrity sha512-xb3ZL6+L8b9JLLCx3ZdoZy4+2ECphCMo2PwqgP1tlfVq6M6YReyzBJtvWWtbDSpNr9hn96pkCiZqUcFEc+54Qg==
|
||||
dependencies:
|
||||
"@types/parse-json" "^4.0.0"
|
||||
import-fresh "^3.1.0"
|
||||
parse-json "^5.0.0"
|
||||
path-type "^4.0.0"
|
||||
yaml "^1.7.2"
|
||||
|
||||
cosmiconfig@^7.0.0, cosmiconfig@^7.0.1:
|
||||
version "7.0.1"
|
||||
resolved "https://registry.yarnpkg.com/cosmiconfig/-/cosmiconfig-7.0.1.tgz#714d756522cace867867ccb4474c5d01bbae5d6d"
|
||||
@ -4348,6 +4437,11 @@ find-cache-dir@^3.3.1:
|
||||
make-dir "^3.0.2"
|
||||
pkg-dir "^4.1.0"
|
||||
|
||||
find-root@^1.1.0:
|
||||
version "1.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-root/-/find-root-1.1.0.tgz#abcfc8ba76f708c42a97b3d685b7e9450bfb9ce4"
|
||||
integrity sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==
|
||||
|
||||
find-up@^2.1.0:
|
||||
version "2.1.0"
|
||||
resolved "https://registry.yarnpkg.com/find-up/-/find-up-2.1.0.tgz#45d1b7e506c717ddd482775a2b77920a3c0c57a7"
|
||||
@ -4794,7 +4888,7 @@ immutable@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/immutable/-/immutable-4.0.0.tgz#b86f78de6adef3608395efb269a91462797e2c23"
|
||||
integrity sha512-zIE9hX70qew5qTUjSS7wi1iwj/l7+m54KWU247nhM3v806UdGj1yDndXj+IOYxxtW9zyLI+xqFNZjTuDaLUqFw==
|
||||
|
||||
import-fresh@^3.0.0, import-fresh@^3.2.1:
|
||||
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
|
||||
version "3.3.0"
|
||||
resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
|
||||
integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
|
||||
@ -6486,14 +6580,14 @@ react-refresh@^0.11.0:
|
||||
resolved "https://registry.yarnpkg.com/react-refresh/-/react-refresh-0.11.0.tgz#77198b944733f0f1f1a90e791de4541f9f074046"
|
||||
integrity sha512-F27qZr8uUqwhWZboondsPx8tnC3Ct3SxZA3V5WyEvujRyyNv0VYPhoBg1gZ8/MV5tubQp76Trw8lTv9hzRBa+A==
|
||||
|
||||
react-select@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.2.2.tgz#3d5edf0a60f1276fd5f29f9f90a305f0a25a5189"
|
||||
integrity sha512-miGS2rT1XbFNjduMZT+V73xbJEeMzVkJOz727F6MeAr2hKE0uUSA8Ff7vD44H32x2PD3SRB6OXTY/L+fTV3z9w==
|
||||
react-select@^5.3.2:
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/react-select/-/react-select-5.3.2.tgz#ecee0d5c59ed4acb7f567f7de3c75a488d93dacb"
|
||||
integrity sha512-W6Irh7U6Ha7p5uQQ2ZnemoCQ8mcfgOtHfw3wuMzG6FAu0P+CYicgofSLOq97BhjMx8jS+h+wwWdCBeVVZ9VqlQ==
|
||||
dependencies:
|
||||
"@babel/runtime" "^7.12.0"
|
||||
"@emotion/cache" "^11.4.0"
|
||||
"@emotion/react" "^11.1.1"
|
||||
"@emotion/react" "^11.8.1"
|
||||
"@types/react-transition-group" "^4.4.0"
|
||||
memoize-one "^5.0.0"
|
||||
prop-types "^15.6.0"
|
||||
@ -6738,7 +6832,7 @@ resolve@^1.10.1, resolve@^1.14.2:
|
||||
is-core-module "^2.2.0"
|
||||
path-parse "^1.0.6"
|
||||
|
||||
resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0:
|
||||
resolve@^1.12.0, resolve@^1.19.0, resolve@^1.20.0, resolve@^1.9.0:
|
||||
version "1.22.0"
|
||||
resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198"
|
||||
integrity sha512-Hhtrw0nLeSrFQ7phPp4OOcVjLPIeMnRlr5mcnVuMe7M/7eBn98A3hmFRLoFo3DLZkivSYwhRUJTyPyWAk56WLw==
|
||||
@ -7019,7 +7113,7 @@ source-map@0.6.1, source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.0, sourc
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
|
||||
integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
|
||||
|
||||
source-map@^0.5.0:
|
||||
source-map@^0.5.0, source-map@^0.5.7:
|
||||
version "0.5.7"
|
||||
resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.5.7.tgz#8a039d2d1021d22d1ea14c80d8ea468ba2ef3fcc"
|
||||
integrity sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=
|
||||
@ -7153,6 +7247,11 @@ stylehacks@^5.1.0:
|
||||
browserslist "^4.16.6"
|
||||
postcss-selector-parser "^6.0.4"
|
||||
|
||||
stylis@4.0.13:
|
||||
version "4.0.13"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.13.tgz#f5db332e376d13cc84ecfe5dace9a2a51d954c91"
|
||||
integrity sha512-xGPXiFVl4YED9Jh7Euv2V220mriG9u4B2TA6Ybjc1catrstKD2PpIdU3U0RKpkVBC2EhmL/F0sPCr9vrFTNRag==
|
||||
|
||||
stylis@^4.0.3:
|
||||
version "4.0.10"
|
||||
resolved "https://registry.yarnpkg.com/stylis/-/stylis-4.0.10.tgz#446512d1097197ab3f02fb3c258358c3f7a14240"
|
||||
@ -7710,7 +7809,7 @@ yallist@^4.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
|
||||
integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
|
||||
|
||||
yaml@^1.10.0, yaml@^1.10.2:
|
||||
yaml@^1.10.0, yaml@^1.10.2, yaml@^1.7.2:
|
||||
version "1.10.2"
|
||||
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
|
||||
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
|
||||
|
Loading…
x
Reference in New Issue
Block a user