Merge pull request #11568 from nixocio/ui_rs5

Bump react scripts to 5.0
This commit is contained in:
Kersom
2022-02-09 07:49:43 -05:00
committed by GitHub
97 changed files with 15536 additions and 25938 deletions

0
awx/ui/.babel.rc Normal file
View File

View File

@@ -7,4 +7,5 @@ build
node_modules node_modules
dist dist
images images
instrumented instrumented
*test*.js

View File

@@ -1,14 +1,19 @@
{ {
"parser": "babel-eslint", "parser": "@babel/eslint-parser",
"ignorePatterns": ["./node_modules/"],
"parserOptions": { "parserOptions": {
"requireConfigFile": false,
"ecmaVersion": 6, "ecmaVersion": 6,
"sourceType": "module", "sourceType": "module",
"ecmaFeatures": { "ecmaFeatures": {
"jsx": true, "jsx": true,
"modules": true "modules": true
} },
"babelOptions": {
"presets": ["@babel/preset-react"]
}
}, },
"plugins": ["react-hooks", "jsx-a11y", "i18next"], "plugins": ["react-hooks", "jsx-a11y", "i18next", "@babel"],
"extends": [ "extends": [
"airbnb", "airbnb",
"prettier", "prettier",
@@ -17,7 +22,7 @@
], ],
"settings": { "settings": {
"react": { "react": {
"version": "16.5.2" "version": "detect"
}, },
"import/resolver": { "import/resolver": {
"node": { "node": {
@@ -141,6 +146,9 @@
"jsx-a11y/label-has-associated-control": "off", "jsx-a11y/label-has-associated-control": "off",
"react-hooks/rules-of-hooks": "error", "react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn", "react-hooks/exhaustive-deps": "warn",
"react/jsx-filename-extension": "off" "react/jsx-filename-extension": "off",
"no-restricted-exports": "off",
"react/function-component-definition": "off",
"prefer-regex-literals": "off"
} }
} }

View File

@@ -1,4 +1,4 @@
FROM node:14 FROM node:14.18.3
ARG NPMRC_FILE=.npmrc ARG NPMRC_FILE=.npmrc
ENV NPMRC_FILE=${NPMRC_FILE} ENV NPMRC_FILE=${NPMRC_FILE}
ARG TARGET='https://awx:8043' ARG TARGET='https://awx:8043'
@@ -6,7 +6,7 @@ ENV TARGET=${TARGET}
ENV CI=true ENV CI=true
WORKDIR /ui WORKDIR /ui
ADD .eslintignore .eslintignore ADD .eslintignore .eslintignore
ADD .eslintrc .eslintrc ADD .eslintrc.json .eslintrc.json
ADD .linguirc .linguirc ADD .linguirc .linguirc
ADD jsconfig.json jsconfig.json ADD jsconfig.json jsconfig.json
ADD public public ADD public public

View File

@@ -1,7 +1,7 @@
# AWX-UI # AWX-UI
## Requirements ## Requirements
- node 14.x LTS, npm 7.x, make, git - node 14.18.3, npm 7.24.2, make, git
## Development ## Development
The API development server will need to be running. See [CONTRIBUTING.md](../../CONTRIBUTING.md). The API development server will need to be running. See [CONTRIBUTING.md](../../CONTRIBUTING.md).

39260
awx/ui/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,6 @@
"ace-builds": "^1.4.12", "ace-builds": "^1.4.12",
"ansi-to-html": "0.7.2", "ansi-to-html": "0.7.2",
"axios": "0.22.0", "axios": "0.22.0",
"babel-plugin-macros": "^3.0.1",
"codemirror": "^5.47.0", "codemirror": "^5.47.0",
"d3": "7.1.1", "d3": "7.1.1",
"dagre": "^0.8.4", "dagre": "^0.8.4",
@@ -34,31 +33,36 @@
"styled-components": "5.3.0" "styled-components": "5.3.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.16.10",
"@babel/eslint-parser": "^7.16.5",
"@babel/eslint-plugin": "^7.16.5",
"@babel/plugin-syntax-jsx": "7.16.7",
"@babel/polyfill": "^7.8.7", "@babel/polyfill": "^7.8.7",
"@babel/preset-react": "7.16.7",
"@cypress/instrument-cra": "^1.4.0", "@cypress/instrument-cra": "^1.4.0",
"@lingui/cli": "^3.7.1", "@lingui/cli": "^3.7.1",
"@lingui/loader": "^3.8.3", "@lingui/loader": "^3.8.3",
"@lingui/macro": "^3.7.1", "@lingui/macro": "^3.7.1",
"@nteract/mockument": "^1.0.4", "@nteract/mockument": "^1.0.4",
"@wojtekmaj/enzyme-adapter-react-17": "0.6.5", "@wojtekmaj/enzyme-adapter-react-17": "0.6.5",
"babel-core": "^7.0.0-bridge.0", "babel-plugin-macros": "3.1.0",
"enzyme": "^3.10.0", "enzyme": "^3.10.0",
"enzyme-adapter-react-16": "^1.14.0", "enzyme-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.3.5", "enzyme-to-json": "^3.3.5",
"eslint": "7.30.0", "eslint": "^8.7.0",
"eslint-config-airbnb": "18.2.1", "eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "8.3.0", "eslint-config-prettier": "8.3.0",
"eslint-import-resolver-webpack": "0.11.1", "eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-i18next": "^5.0.0", "eslint-plugin-i18next": "5.1.2",
"eslint-plugin-import": "^2.14.0", "eslint-plugin-import": "2.25.4",
"eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-react": "^7.11.1", "eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.2.0", "eslint-plugin-react-hooks": "4.3.0",
"http-proxy-middleware": "^1.0.3", "http-proxy-middleware": "^1.0.3",
"jest-websocket-mock": "^2.0.2", "jest-websocket-mock": "^2.0.2",
"mock-socket": "^9.0.3", "mock-socket": "^9.0.3",
"prettier": "2.3.2", "prettier": "2.3.2",
"react-scripts": "^4.0.3" "react-scripts": "5.0.0"
}, },
"scripts": { "scripts": {
"prelint": "lingui compile", "prelint": "lingui compile",
@@ -66,7 +70,7 @@
"prestart-instrumented": "lingui compile", "prestart-instrumented": "lingui compile",
"pretest": "lingui compile", "pretest": "lingui compile",
"pretest-watch": "lingui compile", "pretest-watch": "lingui compile",
"start": "ESLINT_NO_DEV_ERRORS=true PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts start", "start": "GENERATE_SOURCEMAP=false ESLINT_NO_DEV_ERRORS=true PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts start",
"start-instrumented": "ESLINT_NO_DEV_ERRORS=true DEBUG=instrument-cra PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts -r @cypress/instrument-cra start", "start-instrumented": "ESLINT_NO_DEV_ERRORS=true DEBUG=instrument-cra PORT=3001 HTTPS=true DANGEROUSLY_DISABLE_HOST_CHECK=true react-scripts -r @cypress/instrument-cra start",
"build": "INLINE_RUNTIME_CHUNK=false react-scripts build", "build": "INLINE_RUNTIME_CHUNK=false react-scripts build",
"test": "TZ='UTC' react-scripts test --watchAll=false", "test": "TZ='UTC' react-scripts test --watchAll=false",

View File

@@ -1,3 +1,4 @@
/* eslint-disable default-param-last */
import axios from 'axios'; import axios from 'axios';
import { encodeQueryString } from 'util/qs'; import { encodeQueryString } from 'util/qs';
import debounce from 'util/debounce'; import debounce from 'util/debounce';

View File

@@ -1,7 +1,11 @@
.pf-c-select__toggle:before { .pf-c-select .pf-c-select__toggle:before {
border-top: var(--pf-c-select__toggle--before--BorderTopWidth) solid var(--pf-c-select__toggle--before--BorderTopColor); border-top: var(--pf-c-select__toggle--before--BorderTopWidth) solid
border-right: var(--pf-c-select__toggle--before--BorderRightWidth) solid var(--pf-c-select__toggle--before--BorderRightColor); var(--pf-c-select__toggle--before--BorderTopColor);
border-bottom: var(--pf-c-select__toggle--before--BorderBottomWidth) solid var(--pf-c-select__toggle--before--BorderBottomColor); border-right: var(--pf-c-select__toggle--before--BorderRightWidth) solid
border-left: var(--pf-c-select__toggle--before--BorderLeftWidth) solid var(--pf-c-select__toggle--before--BorderLeftColor); var(--pf-c-select__toggle--before--BorderRightColor);
border-bottom: var(--pf-c-select__toggle--before--BorderBottomWidth) solid
var(--pf-c-select__toggle--before--BorderBottomColor);
border-left: var(--pf-c-select__toggle--before--BorderLeftWidth) solid
var(--pf-c-select__toggle--before--BorderLeftColor);
} }
/* https://github.com/patternfly/patternfly-react/issues/5650 */ /* https://github.com/patternfly/patternfly-react/issues/5650 */

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState, useRef, useEffect } from 'react'; import React, { useState, useRef, useEffect } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';

View File

@@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -91,76 +91,74 @@ function AssociateModal({
}; };
return ( return (
<> <Modal
<Modal ouiaId={ouiaId}
ouiaId={ouiaId} variant="large"
variant="large" title={title}
title={title} aria-label={t`Association modal`}
aria-label={t`Association modal`} isOpen={isModalOpen}
isOpen={isModalOpen} onClose={handleClose}
onClose={handleClose} actions={[
actions={[ <Button
<Button ouiaId="associate-modal-save"
ouiaId="associate-modal-save" aria-label={t`Save`}
aria-label={t`Save`} key="select"
key="select" variant="primary"
variant="primary" onClick={handleSave}
onClick={handleSave} isDisabled={selected.length === 0}
isDisabled={selected.length === 0} >
> {t`Save`}
{t`Save`} </Button>,
</Button>, <Button
<Button ouiaId="associate-modal-cancel"
ouiaId="associate-modal-cancel" aria-label={t`Cancel`}
aria-label={t`Cancel`} key="cancel"
key="cancel" variant="link"
variant="link" onClick={handleClose}
onClick={handleClose} >
> {t`Cancel`}
{t`Cancel`} </Button>,
</Button>, ]}
>
<OptionsList
displayKey={displayKey}
contentError={contentError}
columns={columns}
deselectItem={handleSelect}
header={header}
isLoading={isLoading}
multiple
optionCount={itemCount}
options={items}
qsConfig={QS_CONFIG(displayKey)}
readOnly={false}
selectItem={handleSelect}
value={selected}
searchColumns={[
{
name: t`Name`,
key: `${displayKey}__icontains`,
isDefault: true,
},
{
name: t`Created By (Username)`,
key: 'created_by__username__icontains',
},
{
name: t`Modified By (Username)`,
key: 'modified_by__username__icontains',
},
]} ]}
> sortColumns={[
<OptionsList {
displayKey={displayKey} name: t`Name`,
contentError={contentError} key: `${displayKey}`,
columns={columns} },
deselectItem={handleSelect} ]}
header={header} searchableKeys={searchableKeys}
isLoading={isLoading} relatedSearchableKeys={relatedSearchableKeys}
multiple />
optionCount={itemCount} </Modal>
options={items}
qsConfig={QS_CONFIG(displayKey)}
readOnly={false}
selectItem={handleSelect}
value={selected}
searchColumns={[
{
name: t`Name`,
key: `${displayKey}__icontains`,
isDefault: true,
},
{
name: t`Created By (Username)`,
key: 'created_by__username__icontains',
},
{
name: t`Modified By (Username)`,
key: 'modified_by__username__icontains',
},
]}
sortColumns={[
{
name: t`Name`,
key: `${displayKey}`,
},
]}
searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys}
/>
</Modal>
</>
); );
} }

View File

@@ -1,4 +1,5 @@
import React, { Fragment } from 'react'; /* eslint-disable react/jsx-no-useless-fragment */
import React from 'react';
import { Link, Redirect } from 'react-router-dom'; import { Link, Redirect } from 'react-router-dom';
import { bool, instanceOf } from 'prop-types'; import { bool, instanceOf } from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -1,4 +1,4 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useMemo, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import styled from 'styled-components'; import styled from 'styled-components';
import { useLocation } from 'react-router-dom'; import { useLocation } from 'react-router-dom';
@@ -77,6 +77,14 @@ function DataListToolbar({
setIsKebabOpen(false); setIsKebabOpen(false);
} }
}, [isKebabModalOpen]); }, [isKebabModalOpen]);
const kebabProviderValue = useMemo(
() => ({
isKebabified: true,
onKebabModalChange: setIsKebabModalOpen,
}),
[setIsKebabModalOpen]
);
return ( return (
<Toolbar <Toolbar
id={`${qsConfig.namespace}-list-toolbar`} id={`${qsConfig.namespace}-list-toolbar`}
@@ -145,25 +153,18 @@ function DataListToolbar({
</ToolbarToggleGroup> </ToolbarToggleGroup>
{showExpandCollapse && ( {showExpandCollapse && (
<ToolbarGroup> <ToolbarGroup>
<> <ToolbarItem>
<ToolbarItem> <ExpandCollapse
<ExpandCollapse isCompact={isCompact}
isCompact={isCompact} onCompact={onCompact}
onCompact={onCompact} onExpand={onExpand}
onExpand={onExpand} />
/> </ToolbarItem>
</ToolbarItem>
</>
</ToolbarGroup> </ToolbarGroup>
)} )}
{isAdvancedSearchShown && additionalControls.length > 0 && ( {isAdvancedSearchShown && additionalControls.length > 0 && (
<ToolbarItem> <ToolbarItem>
<KebabifiedProvider <KebabifiedProvider value={kebabProviderValue}>
value={{
isKebabified: true,
onKebabModalChange: setIsKebabModalOpen,
}}
>
<Dropdown <Dropdown
toggle={ toggle={
<KebabToggle <KebabToggle

View File

@@ -2,7 +2,7 @@ import React from 'react';
import { TextList, TextListVariants } from '@patternfly/react-core'; import { TextList, TextListVariants } from '@patternfly/react-core';
import styled from 'styled-components'; import styled from 'styled-components';
const DetailList = ({ children, stacked, compact, ...props }) => ( const DetailList = ({ children, stacked, ...props }) => (
<TextList component={TextListVariants.dl} {...props}> <TextList component={TextListVariants.dl} {...props}>
{children} {children}
</TextList> </TextList>

View File

@@ -22,10 +22,10 @@ function DisassociateButton({
const [isOpen, setIsOpen] = useState(false); const [isOpen, setIsOpen] = useState(false);
const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext); const { isKebabified, onKebabModalChange } = useContext(KebabifiedContext);
function handleDisassociate() { const handleDisassociate = () => {
onDisassociate(); onDisassociate();
setIsOpen(false); setIsOpen(false);
} };
useEffect(() => { useEffect(() => {
if (isKebabified) { if (isKebabified) {

View File

@@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useField } from 'formik'; import { useField } from 'formik';

View File

@@ -35,9 +35,9 @@ function CredentialsStep({
name: 'credentials', name: 'credentials',
validate: (val) => validate: (val) =>
credentialsValidator( credentialsValidator(
defaultCredentials,
allowCredentialsWithPasswords, allowCredentialsWithPasswords,
val val,
defaultCredentials
), ),
}); });
const [selectedType, setSelectedType] = useState(null); const [selectedType, setSelectedType] = useState(null);
@@ -102,9 +102,9 @@ function CredentialsStep({
useEffect(() => { useEffect(() => {
helpers.setError( helpers.setError(
credentialsValidator( credentialsValidator(
defaultCredentials,
allowCredentialsWithPasswords, allowCredentialsWithPasswords,
field.value field.value,
defaultCredentials
) )
); );
/* eslint-disable-next-line react-hooks/exhaustive-deps */ /* eslint-disable-next-line react-hooks/exhaustive-deps */

View File

@@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import styled from 'styled-components'; import styled from 'styled-components';
import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons'; import { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
import { Tooltip } from '@patternfly/react-core'; import { Tooltip } from '@patternfly/react-core';

View File

@@ -19,18 +19,16 @@ function StepName({ hasErrors, children, id }) {
return <div id={id}>{children}</div>; return <div id={id}>{children}</div>;
} }
return ( return (
<> <AlertText id={id}>
<AlertText id={id}> {children}
{children} <Tooltip
<Tooltip position="right"
position="right" content={t`This step contains errors`}
content={t`This step contains errors`} trigger="click mouseenter focus"
trigger="click mouseenter focus" >
> <ExclamationCircleIcon css="color: var(--pf-global--danger-color--100)" />
<ExclamationCircleIcon css="color: var(--pf-global--danger-color--100)" /> </Tooltip>
</Tooltip> </AlertText>
</AlertText>
</>
); );
} }

View File

@@ -7,9 +7,9 @@ const credentialPromptsForPassword = (credential) =>
credential?.inputs?.vault_password === 'ASK'; credential?.inputs?.vault_password === 'ASK';
export default function credentialsValidator( export default function credentialsValidator(
defaultCredentials = [],
allowCredentialsWithPasswords, allowCredentialsWithPasswords,
selectedCredentials selectedCredentials,
defaultCredentials = []
) { ) {
if (defaultCredentials.length > 0 && selectedCredentials) { if (defaultCredentials.length > 0 && selectedCredentials) {
const missingCredentialTypes = []; const missingCredentialTypes = [];

View File

@@ -35,9 +35,9 @@ export default function useCredentialsStep(
validate: () => { validate: () => {
helpers.setError( helpers.setError(
credentialsValidator( credentialsValidator(
resourceDefaultCredentials,
allowCredentialsWithPasswords, allowCredentialsWithPasswords,
field.value field.value,
resourceDefaultCredentials
) )
); );
}, },

View File

@@ -7,7 +7,7 @@ import useOtherPromptsStep from './steps/useOtherPromptsStep';
import useSurveyStep from './steps/useSurveyStep'; import useSurveyStep from './steps/useSurveyStep';
import usePreviewStep from './steps/usePreviewStep'; import usePreviewStep from './steps/usePreviewStep';
function showCredentialPasswordsStep(credentials = [], launchConfig) { function showCredentialPasswordsStep(launchConfig, credentials = []) {
if ( if (
!launchConfig?.ask_credential_on_launch && !launchConfig?.ask_credential_on_launch &&
launchConfig?.passwords_needed_to_start launchConfig?.passwords_needed_to_start
@@ -53,7 +53,7 @@ export default function useLaunchSteps(launchConfig, surveyConfig, resource) {
), ),
useCredentialPasswordsStep( useCredentialPasswordsStep(
launchConfig, launchConfig,
showCredentialPasswordsStep(formikValues.credentials, launchConfig), showCredentialPasswordsStep(launchConfig, formikValues.credentials),
visited visited
), ),
useOtherPromptsStep(launchConfig, resource), useOtherPromptsStep(launchConfig, resource),

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';

View File

@@ -150,50 +150,48 @@ function ExecutionEnvironmentLookup({
}, [fetchExecutionEnvironments]); }, [fetchExecutionEnvironments]);
const renderLookup = () => ( const renderLookup = () => (
<> <Lookup
<Lookup id={id}
id={id} header={t`Execution Environment`}
header={t`Execution Environment`} value={value}
value={value} onBlur={onBlur}
onBlur={onBlur} onChange={onChange}
onChange={onChange} onDebounce={checkExecutionEnvironmentName}
onDebounce={checkExecutionEnvironmentName} fieldName={fieldName}
fieldName={fieldName} validate={validate}
validate={validate} qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG} isLoading={isLoading || isProjectLoading}
isLoading={isLoading || isProjectLoading} isDisabled={isDisabled}
isDisabled={isDisabled} renderOptionsList={({ state, dispatch, canDelete }) => (
renderOptionsList={({ state, dispatch, canDelete }) => ( <OptionsList
<OptionsList value={state.selectedItems}
value={state.selectedItems} options={executionEnvironments}
options={executionEnvironments} optionCount={count}
optionCount={count} searchColumns={[
searchColumns={[ {
{ name: t`Name`,
name: t`Name`, key: 'name__icontains',
key: 'name__icontains', isDefault: true,
isDefault: true, },
}, ]}
]} sortColumns={[
sortColumns={[ {
{ name: t`Name`,
name: t`Name`, key: 'name',
key: 'name', },
}, ]}
]} searchableKeys={searchableKeys}
searchableKeys={searchableKeys} relatedSearchableKeys={relatedSearchableKeys}
relatedSearchableKeys={relatedSearchableKeys} multiple={state.multiple}
multiple={state.multiple} header={t`Execution Environment`}
header={t`Execution Environment`} name="executionEnvironments"
name="executionEnvironments" qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG} readOnly={!canDelete}
readOnly={!canDelete} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })} />
/> )}
)} />
/>
</>
); );
const renderLabel = () => { const renderLabel = () => {

View File

@@ -123,71 +123,69 @@ function InventoryLookup({
}, [fetchInventories]); }, [fetchInventories]);
return isPromptableField ? ( return isPromptableField ? (
<> <FieldWithPrompt
<FieldWithPrompt fieldId={fieldId}
fieldId={fieldId} isRequired={required}
isRequired={required} label={t`Inventory`}
label={t`Inventory`} promptId={promptId}
promptId={promptId} promptName={promptName}
promptName={promptName} isDisabled={!canEdit || isDisabled}
isDisabled={!canEdit || isDisabled} tooltip={t`Select the inventory containing the hosts
tooltip={t`Select the inventory containing the hosts
you want this job to manage.`} you want this job to manage.`}
> >
<Lookup <Lookup
id="inventory-lookup" id="inventory-lookup"
header={t`Inventory`} header={t`Inventory`}
value={value} value={value}
onChange={onChange} onChange={onChange}
onBlur={onBlur} onBlur={onBlur}
required={required} required={required}
onDebounce={checkInventoryName} onDebounce={checkInventoryName}
fieldName={fieldName} fieldName={fieldName}
validate={validate} validate={validate}
isLoading={isLoading} isLoading={isLoading}
isDisabled={!canEdit || isDisabled} isDisabled={!canEdit || isDisabled}
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
renderOptionsList={({ state, dispatch, canDelete }) => ( renderOptionsList={({ state, dispatch, canDelete }) => (
<OptionsList <OptionsList
value={state.selectedItems} value={state.selectedItems}
options={inventories} options={inventories}
optionCount={count} optionCount={count}
searchColumns={[ searchColumns={[
{ {
name: t`Name`, name: t`Name`,
key: 'name__icontains', key: 'name__icontains',
isDefault: true, isDefault: true,
}, },
{ {
name: t`Created By (Username)`, name: t`Created By (Username)`,
key: 'created_by__username__icontains', key: 'created_by__username__icontains',
}, },
{ {
name: t`Modified By (Username)`, name: t`Modified By (Username)`,
key: 'modified_by__username__icontains', key: 'modified_by__username__icontains',
}, },
]} ]}
sortColumns={[ sortColumns={[
{ {
name: t`Name`, name: t`Name`,
key: 'name', key: 'name',
}, },
]} ]}
searchableKeys={searchableKeys} searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys} relatedSearchableKeys={relatedSearchableKeys}
multiple={state.multiple} multiple={state.multiple}
header={t`Inventory`} header={t`Inventory`}
name="inventory" name="inventory"
qsConfig={QS_CONFIG} qsConfig={QS_CONFIG}
readOnly={!canDelete} readOnly={!canDelete}
selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })} selectItem={(item) => dispatch({ type: 'SELECT_ITEM', item })}
deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })} deselectItem={(item) => dispatch({ type: 'DESELECT_ITEM', item })}
/> />
)} )}
/> />
<LookupErrorMessage error={error} /> <LookupErrorMessage error={error} />
</FieldWithPrompt> </FieldWithPrompt>
</>
) : ( ) : (
<> <>
<Lookup <Lookup

View File

@@ -1,4 +1,5 @@
import React, { Fragment } from 'react'; /* eslint-disable react/jsx-no-useless-fragment */
import React from 'react';
import { func, string } from 'prop-types'; import { func, string } from 'prop-types';
import { Button } from '@patternfly/react-core'; import { Button } from '@patternfly/react-core';

View File

@@ -21,12 +21,12 @@ function RoutedTabs({ tabsArray }) {
return 0; return 0;
}; };
function handleTabSelect(event, eventKey) { const handleTabSelect = (event, eventKey) => {
const match = tabsArray.find((tab) => tab.id === eventKey); const match = tabsArray.find((tab) => tab.id === eventKey);
if (match) { if (match) {
history.push(match.link); history.push(match.link);
} }
} };
return ( return (
<Tabs <Tabs

View File

@@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -164,37 +164,35 @@ function Search({
/> />
)) || )) ||
(options && ( (options && (
<> <Select
<Select variant={SelectVariant.checkbox}
variant={SelectVariant.checkbox} aria-label={name}
aria-label={name} typeAheadAriaLabel={name}
typeAheadAriaLabel={name} onToggle={setIsFilterDropdownOpen}
onToggle={setIsFilterDropdownOpen} onSelect={(event, selection) =>
onSelect={(event, selection) => handleFilterDropdownSelect(key, event, selection)
handleFilterDropdownSelect(key, event, selection) }
} selections={chipsByKey[key].chips.map((chip) => {
selections={chipsByKey[key].chips.map((chip) => { const [, ...value] = chip.key.split(':');
const [, ...value] = chip.key.split(':'); return value.join(':');
return value.join(':'); })}
})} isOpen={isFilterDropdownOpen}
isOpen={isFilterDropdownOpen} placeholderText={t`Filter By ${name}`}
placeholderText={t`Filter By ${name}`} ouiaId={`filter-by-${key}`}
ouiaId={`filter-by-${key}`} isDisabled={isDisabled}
isDisabled={isDisabled} maxHeight={maxSelectHeight}
maxHeight={maxSelectHeight} noResultsFoundText={t`No results found`}
noResultsFoundText={t`No results found`} >
> {options.map(([optionKey, optionLabel]) => (
{options.map(([optionKey, optionLabel]) => ( <SelectOption
<SelectOption key={optionKey}
key={optionKey} value={optionKey}
value={optionKey} inputId={`select-option-${optionKey}`}
inputId={`select-option-${optionKey}`} >
> {optionLabel}
{optionLabel} </SelectOption>
</SelectOption> ))}
))} </Select>
</Select>
</>
)) || )) ||
(isBoolean && ( (isBoolean && (
<Select <Select

View File

@@ -37,7 +37,7 @@ function DraggableSelectedList({ selected, onRemove, onRowDrag }) {
return result; return result;
} }
function dragItem(item, dest) { const dragItem = (item, dest) => {
if (!dest || item.index === dest.index) { if (!dest || item.index === dest.index) {
return false; return false;
} }
@@ -45,7 +45,7 @@ function DraggableSelectedList({ selected, onRemove, onRowDrag }) {
const newItems = reorder(selected, item.index, dest.index); const newItems = reorder(selected, item.index, dest.index);
onRowDrag(newItems); onRowDrag(newItems);
return true; return true;
} };
if (selected.length <= 0) { if (selected.length <= 0) {
return null; return null;

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState } from 'react'; import React, { useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';

View File

@@ -1,4 +1,4 @@
import React, { Fragment } from 'react'; import React from 'react';
import { arrayOf } from 'prop-types'; import { arrayOf } from 'prop-types';
import { Link as _Link } from 'react-router-dom'; import { Link as _Link } from 'react-router-dom';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import 'styled-components/macro'; import 'styled-components/macro';
import React from 'react'; import React from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -133,14 +133,12 @@ function WorkflowNodeHelp({ node }) {
return ( return (
<> <>
{!unifiedJobTemplate && (!job || job.type !== 'workflow_approval') && ( {!unifiedJobTemplate && (!job || job.type !== 'workflow_approval') && (
<> <ResourceDeleted job={job}>
<ResourceDeleted job={job}> <StyledExclamationTriangleIcon />
<StyledExclamationTriangleIcon /> <Trans>
<Trans> The resource associated with this node has been deleted.
The resource associated with this node has been deleted. </Trans>
</Trans> </ResourceDeleted>
</ResourceDeleted>
</>
)} )}
{job && ( {job && (
<GridDL> <GridDL>

View File

@@ -4,6 +4,7 @@ import React, {
useState, useState,
useRef, useRef,
useCallback, useCallback,
useMemo,
} from 'react'; } from 'react';
import { useHistory, Redirect } from 'react-router-dom'; import { useHistory, Redirect } from 'react-router-dom';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
@@ -163,23 +164,35 @@ function SessionProvider({ children }) {
clearInterval(sessionIntervalId.current); clearInterval(sessionIntervalId.current);
}, []); }, []);
const sessionValue = useMemo(
() => ({
isUserBeingLoggedOut,
loginRedirectOverride,
authRedirectTo,
handleSessionContinue,
isSessionExpired,
logout,
sessionCountdown,
setAuthRedirectTo,
}),
[
isUserBeingLoggedOut,
loginRedirectOverride,
authRedirectTo,
handleSessionContinue,
isSessionExpired,
logout,
sessionCountdown,
setAuthRedirectTo,
]
);
if (isLoading) { if (isLoading) {
return null; return null;
} }
return ( return (
<SessionContext.Provider <SessionContext.Provider value={sessionValue}>
value={{
isUserBeingLoggedOut,
loginRedirectOverride,
authRedirectTo,
handleSessionContinue,
isSessionExpired,
logout,
sessionCountdown,
setAuthRedirectTo,
}}
>
{children} {children}
</SessionContext.Provider> </SessionContext.Provider>
); );

View File

@@ -73,21 +73,19 @@ function ApplicationAdd({ onSuccessfulAdd }) {
return <ContentError error={error} />; return <ContentError error={error} />;
} }
return ( return (
<> <PageSection>
<PageSection> <Card>
<Card> <CardBody>
<CardBody> <ApplicationForm
<ApplicationForm onSubmit={handleSubmit}
onSubmit={handleSubmit} onCancel={handleCancel}
onCancel={handleCancel} authorizationOptions={authorizationOptions}
authorizationOptions={authorizationOptions} clientTypeOptions={clientTypeOptions}
clientTypeOptions={clientTypeOptions} submitError={submitError}
submitError={submitError} />
/> </CardBody>
</CardBody> </Card>
</Card> </PageSection>
</PageSection>
</>
); );
} }
export default ApplicationAdd; export default ApplicationAdd;

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState } from 'react'; import React, { useState } from 'react';
import { useField, useFormikContext } from 'formik'; import { useField, useFormikContext } from 'formik';
import { shape, string } from 'prop-types'; import { shape, string } from 'prop-types';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { useLocation, useHistory } from 'react-router-dom'; import { useLocation, useHistory } from 'react-router-dom';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { useField, useFormikContext } from 'formik'; import { useField, useFormikContext } from 'formik';

View File

@@ -74,59 +74,57 @@ function ExecutionEnvironmentTemplateList({ executionEnvironment }) {
}, [fetchTemplates]); }, [fetchTemplates]);
return ( return (
<> <Card>
<Card> <PaginatedTable
<PaginatedTable contentError={contentError}
contentError={contentError} hasContentLoading={isLoading}
hasContentLoading={isLoading} items={templates}
items={templates} itemCount={templatesCount}
itemCount={templatesCount} pluralizedItemName={t`Templates`}
pluralizedItemName={t`Templates`} qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG} toolbarSearchableKeys={searchableKeys}
toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarSearchColumns={[
toolbarSearchColumns={[ {
{ name: t`Name`,
name: t`Name`, key: 'name__icontains',
key: 'name__icontains', isDefault: true,
isDefault: true, },
}, {
{ name: t`Type`,
name: t`Type`, key: 'or__type',
key: 'or__type', options: [
options: [ [`job_template`, t`Job Template`],
[`job_template`, t`Job Template`], [`workflow_job_template`, t`Workflow Template`],
[`workflow_job_template`, t`Workflow Template`], ],
], },
}, {
{ name: t`Created By (Username)`,
name: t`Created By (Username)`, key: 'created_by__username__icontains',
key: 'created_by__username__icontains', },
}, {
{ name: t`Modified By (Username)`,
name: t`Modified By (Username)`, key: 'modified_by__username__icontains',
key: 'modified_by__username__icontains', },
}, ]}
]} renderToolbar={(props) => (
renderToolbar={(props) => ( <DatalistToolbar {...props} qsConfig={QS_CONFIG} />
<DatalistToolbar {...props} qsConfig={QS_CONFIG} /> )}
)} headerRow={
headerRow={ <HeaderRow qsConfig={QS_CONFIG} isSelectable={false}>
<HeaderRow qsConfig={QS_CONFIG} isSelectable={false}> <HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell> <HeaderCell>{t`Type`}</HeaderCell>
<HeaderCell>{t`Type`}</HeaderCell> </HeaderRow>
</HeaderRow> }
} renderRow={(template) => (
renderRow={(template) => ( <ExecutionEnvironmentTemplateListItem
<ExecutionEnvironmentTemplateListItem key={template.id}
key={template.id} template={template}
template={template} detailUrl={`/templates/${template.type}/${template.id}/details`}
detailUrl={`/templates/${template.type}/${template.id}/details`} />
/> )}
)} />
/> </Card>
</Card>
</>
); );
} }

View File

@@ -91,7 +91,7 @@ function ContainerGroup({ setBreadcrumb }) {
{contentError.response?.status === 404 && ( {contentError.response?.status === 404 && (
<span> <span>
{t`Container group not found.`} {t`Container group not found.`}
{''}
<Link to="/instance_groups">{t`View all instance groups`}</Link> <Link to="/instance_groups">{t`View all instance groups`}</Link>
</span> </span>
)} )}

View File

@@ -102,7 +102,7 @@ function InstanceGroup({ setBreadcrumb }) {
{contentError.response?.status === 404 && ( {contentError.response?.status === 404 && (
<span> <span>
{t`Instance group not found.`} {t`Instance group not found.`}
{''}
<Link to="/instance_groups">{t`View all instance groups`}</Link> <Link to="/instance_groups">{t`View all instance groups`}</Link>
</span> </span>
)} )}

View File

@@ -28,9 +28,9 @@ const QS_CONFIG = getQSConfig('instance-group', {
}); });
function modifyInstanceGroups( function modifyInstanceGroups(
items = [],
defaultControlPlane, defaultControlPlane,
defaultExecution defaultExecution,
items = []
) { ) {
return items.map((item) => { return items.map((item) => {
const clonedItem = { const clonedItem = {
@@ -128,9 +128,9 @@ function InstanceGroupList({
useSelected(instanceGroups); useSelected(instanceGroups);
const modifiedSelected = modifyInstanceGroups( const modifiedSelected = modifyInstanceGroups(
selected,
defaultControlPlane, defaultControlPlane,
defaultExecution defaultExecution,
selected
); );
const { const {
@@ -158,13 +158,10 @@ function InstanceGroupList({
const canAdd = actions && actions.POST; const canAdd = actions && actions.POST;
function cannotDelete(item) { const cannotDelete = (item) =>
return ( !item.summary_fields.user_capabilities.delete ||
!item.summary_fields.user_capabilities.delete || item.name === defaultExecution ||
item.name === defaultExecution || item.name === defaultControlPlane;
item.name === defaultControlPlane
);
}
const pluralizedItemName = t`Instance Groups`; const pluralizedItemName = t`Instance Groups`;

View File

@@ -107,104 +107,102 @@ function InventoryGroupsList() {
actions && Object.prototype.hasOwnProperty.call(actions, 'POST'); actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
return ( return (
<> <PaginatedTable
<PaginatedTable contentError={contentError}
contentError={contentError} hasContentLoading={isLoading || isAdHocLaunchLoading}
hasContentLoading={isLoading || isAdHocLaunchLoading} items={groups}
items={groups} itemCount={groupCount}
itemCount={groupCount} qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG} clearSelected={clearSelected}
clearSelected={clearSelected} toolbarSearchColumns={[
toolbarSearchColumns={[ {
{ name: t`Name`,
name: t`Name`, key: 'name__icontains',
key: 'name__icontains', isDefault: true,
isDefault: true, },
}, {
{ name: t`Group type`,
name: t`Group type`, key: 'parents__isnull',
key: 'parents__isnull', options: [['true', t`Show only root groups`]],
options: [['true', t`Show only root groups`]], },
}, {
{ name: t`Created By (Username)`,
name: t`Created By (Username)`, key: 'created_by__username__icontains',
key: 'created_by__username__icontains', },
}, {
{ name: t`Modified By (Username)`,
name: t`Modified By (Username)`, key: 'modified_by__username__icontains',
key: 'modified_by__username__icontains', },
}, ]}
]} toolbarSearchableKeys={searchableKeys}
toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} headerRow={
headerRow={ <HeaderRow qsConfig={QS_CONFIG}>
<HeaderRow qsConfig={QS_CONFIG}> <HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell> <HeaderCell>{t`Actions`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell> </HeaderRow>
</HeaderRow> }
} renderRow={(item, index) => (
renderRow={(item, index) => ( <InventoryGroupItem
<InventoryGroupItem key={item.id}
key={item.id} group={item}
group={item} inventoryId={inventoryId}
inventoryId={inventoryId} isSelected={selected.some((row) => row.id === item.id)}
isSelected={selected.some((row) => row.id === item.id)} onSelect={() => handleSelect(item)}
onSelect={() => handleSelect(item)} rowIndex={index}
rowIndex={index} />
)}
renderToolbar={(props) => (
<DataListToolbar
{...props}
isAllSelected={isAllSelected}
onSelectAll={selectAll}
qsConfig={QS_CONFIG}
additionalControls={[
...(canAdd
? [
<ToolbarAddButton
key="add"
linkTo={`/inventories/inventory/${inventoryId}/groups/add`}
/>,
]
: []),
...(!isAdHocDisabled
? [
<AdHocCommands
adHocItems={selected}
hasListItems={groupCount > 0}
onLaunchLoading={setIsAdHocLaunchLoading}
moduleOptions={moduleOptions}
/>,
]
: []),
<Tooltip content={renderTooltip()} position="top" key="delete">
<div>
<InventoryGroupsDeleteModal
groups={selected}
isDisabled={
selected.length === 0 || selected.some(cannotDelete)
}
onAfterDelete={() => {
fetchData();
clearSelected();
}}
/>
</div>
</Tooltip>,
]}
/>
)}
emptyStateControls={
canAdd && (
<ToolbarAddButton
key="add"
linkTo={`/inventories/inventory/${inventoryId}/groups/add`}
/> />
)} )
renderToolbar={(props) => ( }
<DataListToolbar />
{...props}
isAllSelected={isAllSelected}
onSelectAll={selectAll}
qsConfig={QS_CONFIG}
additionalControls={[
...(canAdd
? [
<ToolbarAddButton
key="add"
linkTo={`/inventories/inventory/${inventoryId}/groups/add`}
/>,
]
: []),
...(!isAdHocDisabled
? [
<AdHocCommands
adHocItems={selected}
hasListItems={groupCount > 0}
onLaunchLoading={setIsAdHocLaunchLoading}
moduleOptions={moduleOptions}
/>,
]
: []),
<Tooltip content={renderTooltip()} position="top" key="delete">
<div>
<InventoryGroupsDeleteModal
groups={selected}
isDisabled={
selected.length === 0 || selected.some(cannotDelete)
}
onAfterDelete={() => {
fetchData();
clearSelected();
}}
/>
</div>
</Tooltip>,
]}
/>
)}
emptyStateControls={
canAdd && (
<ToolbarAddButton
key="add"
linkTo={`/inventories/inventory/${inventoryId}/groups/add`}
/>
)
}
/>
</>
); );
} }
export default InventoryGroupsList; export default InventoryGroupsList;

View File

@@ -5,22 +5,20 @@ import InventoryRelatedGroupAdd from '../InventoryRelatedGroupAdd';
function InventoryRelatedGroups() { function InventoryRelatedGroups() {
return ( return (
<> <Switch>
<Switch> <Route
<Route key="addRelatedGroups"
key="addRelatedGroups" path="/inventories/inventory/:id/groups/:groupId/nested_groups/add"
path="/inventories/inventory/:id/groups/:groupId/nested_groups/add" >
> <InventoryRelatedGroupAdd />
<InventoryRelatedGroupAdd /> </Route>
</Route> <Route
<Route key="relatedGroups"
key="relatedGroups" path="/inventories/inventory/:id/groups/:groupId/nested_groups"
path="/inventories/inventory/:id/groups/:groupId/nested_groups" >
> <InventoryRelatedGroupList />
<InventoryRelatedGroupList /> </Route>
</Route> </Switch>
</Switch>
</>
); );
} }
export default InventoryRelatedGroups; export default InventoryRelatedGroups;

View File

@@ -57,87 +57,85 @@ function InventorySourceListItem({
} }
return ( return (
<> <Tr id={`source-row-${source.id}`} ouiaId={`source-row-${source.id}`}>
<Tr id={`source-row-${source.id}`} ouiaId={`source-row-${source.id}`}> <Td
<Td data-cy={`check-action-${source.id}`}
data-cy={`check-action-${source.id}`} select={{
select={{ rowIndex,
rowIndex, isSelected,
isSelected, onSelect,
onSelect, }}
}} />
/> <TdBreakWord dataLabel={t`Name`}>
<TdBreakWord dataLabel={t`Name`}> <Link to={`${detailUrl}/details`}>
<Link to={`${detailUrl}/details`}> <b>{source.name}</b>
<b>{source.name}</b> </Link>
</Link> {missingExecutionEnvironment && (
{missingExecutionEnvironment && ( <span>
<span>
<Tooltip
className="missing-execution-environment"
content={t`Custom virtual environment ${source.custom_virtualenv} must be replaced by an execution environment.`}
position="right"
>
<ExclamationTriangleIcon />
</Tooltip>
</span>
)}
</TdBreakWord>
<Td dataLabel={t`Status`}>
{job && (
<Tooltip <Tooltip
position="top" className="missing-execution-environment"
content={generateLastJobTooltip(job)} content={t`Custom virtual environment ${source.custom_virtualenv} must be replaced by an execution environment.`}
key={job.id} position="right"
> >
<Link to={`/jobs/inventory/${job.id}`}> <ExclamationTriangleIcon />
<StatusLabel status={job.status} />
</Link>
</Tooltip> </Tooltip>
)} </span>
</Td> )}
<Td dataLabel={t`Type`}>{label}</Td> </TdBreakWord>
<ActionsTd dataLabel={t`Actions`}> <Td dataLabel={t`Status`}>
{['running', 'pending', 'waiting'].includes(job?.status) ? ( {job && (
<ActionItem visible={source.summary_fields.user_capabilities.start}> <Tooltip
{source.summary_fields?.current_job?.id && ( position="top"
<JobCancelButton content={generateLastJobTooltip(job)}
job={{ key={job.id}
type: 'inventory_update',
id: source?.summary_fields?.current_job?.id,
}}
errorTitle={t`Inventory Source Sync Error`}
errorMessage={t`Failed to cancel Inventory Source Sync`}
title={t`Cancel Inventory Source Sync`}
showIconButton
/>
)}
</ActionItem>
) : (
<ActionItem
visible={source.summary_fields.user_capabilities.start}
tooltip={t`Sync`}
>
<InventorySourceSyncButton source={source} />
</ActionItem>
)}
<ActionItem
visible={source.summary_fields.user_capabilities.edit}
tooltip={t`Edit`}
> >
<Button <Link to={`/jobs/inventory/${job.id}`}>
ouiaId={`${source.id}-edit-button`} <StatusLabel status={job.status} />
aria-label={t`Edit Source`} </Link>
variant="plain" </Tooltip>
component={Link} )}
to={`${detailUrl}/edit`} </Td>
> <Td dataLabel={t`Type`}>{label}</Td>
<PencilAltIcon /> <ActionsTd dataLabel={t`Actions`}>
</Button> {['running', 'pending', 'waiting'].includes(job?.status) ? (
<ActionItem visible={source.summary_fields.user_capabilities.start}>
{source.summary_fields?.current_job?.id && (
<JobCancelButton
job={{
type: 'inventory_update',
id: source?.summary_fields?.current_job?.id,
}}
errorTitle={t`Inventory Source Sync Error`}
errorMessage={t`Failed to cancel Inventory Source Sync`}
title={t`Cancel Inventory Source Sync`}
showIconButton
/>
)}
</ActionItem> </ActionItem>
</ActionsTd> ) : (
</Tr> <ActionItem
</> visible={source.summary_fields.user_capabilities.start}
tooltip={t`Sync`}
>
<InventorySourceSyncButton source={source} />
</ActionItem>
)}
<ActionItem
visible={source.summary_fields.user_capabilities.edit}
tooltip={t`Edit`}
>
<Button
ouiaId={`${source.id}-edit-button`}
aria-label={t`Edit Source`}
variant="plain"
component={Link}
to={`${detailUrl}/edit`}
>
<PencilAltIcon />
</Button>
</ActionItem>
</ActionsTd>
</Tr>
); );
} }
export default InventorySourceListItem; export default InventorySourceListItem;

View File

@@ -55,68 +55,66 @@ function SmartInventoryHostList({ inventory }) {
}, [fetchHosts]); }, [fetchHosts]);
return ( return (
<> <PaginatedTable
<PaginatedTable contentError={contentError}
contentError={contentError} hasContentLoading={isLoading || isAdHocLaunchLoading}
hasContentLoading={isLoading || isAdHocLaunchLoading} items={hosts}
items={hosts} itemCount={count}
itemCount={count} pluralizedItemName={t`Hosts`}
pluralizedItemName={t`Hosts`} qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG} clearSelected={clearSelected}
clearSelected={clearSelected} toolbarSearchColumns={[
toolbarSearchColumns={[ {
{ name: t`Name`,
name: t`Name`, key: 'name__icontains',
key: 'name__icontains', isDefault: true,
isDefault: true, },
}, {
{ name: t`Created by (username)`,
name: t`Created by (username)`, key: 'created_by__username',
key: 'created_by__username', },
}, {
{ name: t`Modified by (username)`,
name: t`Modified by (username)`, key: 'modified_by__username',
key: 'modified_by__username', },
}, ]}
]} renderToolbar={(props) => (
renderToolbar={(props) => ( <DataListToolbar
<DataListToolbar {...props}
{...props} isAllSelected={isAllSelected}
isAllSelected={isAllSelected} onSelectAll={selectAll}
onSelectAll={selectAll} qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG} additionalControls={
additionalControls={ inventory?.summary_fields?.user_capabilities?.adhoc
inventory?.summary_fields?.user_capabilities?.adhoc ? [
? [ <AdHocCommands
<AdHocCommands adHocItems={selected}
adHocItems={selected} hasListItems={count > 0}
hasListItems={count > 0} onLaunchLoading={setIsAdHocLaunchLoading}
onLaunchLoading={setIsAdHocLaunchLoading} />,
/>, ]
] : []
: [] }
} />
/> )}
)} headerRow={
headerRow={ <HeaderRow qsConfig={QS_CONFIG}>
<HeaderRow qsConfig={QS_CONFIG}> <HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell> <HeaderCell>{t`Recent jobs`}</HeaderCell>
<HeaderCell>{t`Recent jobs`}</HeaderCell> <HeaderCell>{t`Inventory`}</HeaderCell>
<HeaderCell>{t`Inventory`}</HeaderCell> </HeaderRow>
</HeaderRow> }
} renderRow={(host, index) => (
renderRow={(host, index) => ( <SmartInventoryHostListItem
<SmartInventoryHostListItem key={host.id}
key={host.id} host={host}
host={host} detailUrl={`/inventories/smart_inventory/${inventory.id}/hosts/${host.id}/details`}
detailUrl={`/inventories/smart_inventory/${inventory.id}/hosts/${host.id}/details`} isSelected={selected.some((row) => row.id === host.id)}
isSelected={selected.some((row) => row.id === host.id)} onSelect={() => handleSelect(host)}
onSelect={() => handleSelect(host)} rowIndex={index}
rowIndex={index} />
/> )}
)} />
/>
</>
); );
} }

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useCallback, useEffect, useRef, useState } from 'react'; import React, { useCallback, useEffect, useRef, useState } from 'react';
import { useHistory, useLocation } from 'react-router-dom'; import { useHistory, useLocation } from 'react-router-dom';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -42,58 +42,56 @@ const PageControls = ({
isFlatMode, isFlatMode,
isTemplateJob, isTemplateJob,
}) => ( }) => (
<> <ControllsWrapper>
<ControllsWrapper> <ExpandCollapseWrapper>
<ExpandCollapseWrapper> {!isFlatMode && isTemplateJob && (
{!isFlatMode && isTemplateJob && (
<Button
aria-label={
isAllCollapsed ? t`Expand job events` : t`Collapse all job events`
}
variant="plain"
type="button"
onClick={toggleExpandCollapseAll}
>
{isAllCollapsed ? <AngleRightIcon /> : <AngleDownIcon />}
</Button>
)}
</ExpandCollapseWrapper>
<ScrollWrapper>
<Button <Button
ouiaId="job-output-scroll-previous-button" aria-label={
aria-label={t`Scroll previous`} isAllCollapsed ? t`Expand job events` : t`Collapse all job events`
onClick={onScrollPrevious} }
variant="plain" variant="plain"
type="button"
onClick={toggleExpandCollapseAll}
> >
<AngleUpIcon /> {isAllCollapsed ? <AngleRightIcon /> : <AngleDownIcon />}
</Button> </Button>
<Button )}
ouiaId="job-output-scroll-next-button" </ExpandCollapseWrapper>
aria-label={t`Scroll next`} <ScrollWrapper>
onClick={onScrollNext} <Button
variant="plain" ouiaId="job-output-scroll-previous-button"
> aria-label={t`Scroll previous`}
<AngleDownIcon /> onClick={onScrollPrevious}
</Button> variant="plain"
<Button >
ouiaId="job-output-scroll-first-button" <AngleUpIcon />
aria-label={t`Scroll first`} </Button>
onClick={onScrollFirst} <Button
variant="plain" ouiaId="job-output-scroll-next-button"
> aria-label={t`Scroll next`}
<AngleDoubleUpIcon /> onClick={onScrollNext}
</Button> variant="plain"
<Button >
ouiaId="job-output-scroll-last-button" <AngleDownIcon />
aria-label={t`Scroll last`} </Button>
onClick={onScrollLast} <Button
variant="plain" ouiaId="job-output-scroll-first-button"
> aria-label={t`Scroll first`}
<AngleDoubleDownIcon /> onClick={onScrollFirst}
</Button> variant="plain"
</ScrollWrapper> >
</ControllsWrapper> <AngleDoubleUpIcon />
</> </Button>
<Button
ouiaId="job-output-scroll-last-button"
aria-label={t`Scroll last`}
onClick={onScrollLast}
variant="plain"
>
<AngleDoubleDownIcon />
</Button>
</ScrollWrapper>
</ControllsWrapper>
); );
export default PageControls; export default PageControls;

View File

@@ -68,10 +68,12 @@ const OutputToolbar = ({ job, onDelete, isDeleteDisabled, jobStatus }) => {
const taskCount = job?.playbook_counts?.task_count; const taskCount = job?.playbook_counts?.task_count;
const darkCount = job?.host_status_counts?.dark; const darkCount = job?.host_status_counts?.dark;
const failureCount = job?.host_status_counts?.failures; const failureCount = job?.host_status_counts?.failures;
const totalHostCount = Object.keys(job?.host_status_counts || {}).reduce( const totalHostCount = job?.host_status_counts
(sum, key) => sum + job?.host_status_counts[key], ? Object.keys(job.host_status_counts || {}).reduce(
0 (sum, key) => sum + job.host_status_counts[key],
); 0
)
: 0;
const { me } = useConfig(); const { me } = useConfig();
return ( return (

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/destructuring-assignment */
import { useState, useEffect, useReducer } from 'react'; import { useState, useEffect, useReducer } from 'react';
const initialState = { const initialState = {

View File

@@ -9,6 +9,12 @@ import Job from './Job';
import JobTypeRedirect from './JobTypeRedirect'; import JobTypeRedirect from './JobTypeRedirect';
import { JOB_TYPE_URL_SEGMENTS } from '../../constants'; import { JOB_TYPE_URL_SEGMENTS } from '../../constants';
function TypeRedirect({ view }) {
const { id } = useParams();
const { path } = useRouteMatch();
return <JobTypeRedirect id={id} path={path} view={view} />;
}
function Jobs() { function Jobs() {
const match = useRouteMatch(); const match = useRouteMatch();
const [breadcrumbConfig, setBreadcrumbConfig] = useState({ const [breadcrumbConfig, setBreadcrumbConfig] = useState({
@@ -29,12 +35,6 @@ function Jobs() {
}); });
}, []); }, []);
function TypeRedirect({ view }) {
const { id } = useParams();
const { path } = useRouteMatch();
return <JobTypeRedirect id={id} path={path} view={view} />;
}
return ( return (
<> <>
<ScreenHeader streamType="job" breadcrumbConfig={breadcrumbConfig} /> <ScreenHeader streamType="job" breadcrumbConfig={breadcrumbConfig} />

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useCallback, useEffect } from 'react'; import React, { useCallback, useEffect } from 'react';
import { Redirect, withRouter } from 'react-router-dom'; import { Redirect, withRouter } from 'react-router-dom';
@@ -135,9 +136,9 @@ function AWXLogin({ alt, isAuthenticated }) {
/> />
); );
function setSessionRedirect() { const setSessionRedirect = () => {
window.sessionStorage.setItem(SESSION_REDIRECT_URL, authRedirectTo); window.sessionStorage.setItem(SESSION_REDIRECT_URL, authRedirectTo);
} };
return ( return (
<Login header={Header} footer={Footer}> <Login header={Header} footer={Footer}>

View File

@@ -130,7 +130,7 @@ function ManagementJob({ setBreadcrumb }) {
{error?.response?.status === 404 && ( {error?.response?.status === 404 && (
<span> <span>
{t`Management job not found.`} {t`Management job not found.`}
{''}
<Link to={basePath}>{t`View all management jobs`}</Link> <Link to={basePath}>{t`View all management jobs`}</Link>
</span> </span>
)} )}

View File

@@ -75,16 +75,14 @@ function ManagementJobListItem({
{isSuperUser ? ( {isSuperUser ? (
<> <>
{isPrompted ? ( {isPrompted ? (
<> <LaunchManagementPrompt
<LaunchManagementPrompt isOpen={isManagementPromptOpen}
isOpen={isManagementPromptOpen} isLoading={isManagementPromptLoading}
isLoading={isManagementPromptLoading} onClick={handleManagementPromptClick}
onClick={handleManagementPromptClick} onClose={handleManagementPromptClose}
onClose={handleManagementPromptClose} onConfirm={handleManagementPromptConfirm}
onConfirm={handleManagementPromptConfirm} defaultDays={30}
defaultDays={30} />
/>
</>
) : ( ) : (
<Tooltip content={t`Launch management job`} position="top"> <Tooltip content={t`Launch management job`} position="top">
<Button <Button

View File

@@ -120,7 +120,7 @@ function NotificationTemplateForm({
const messages = template.messages || { workflow_approval: {} }; const messages = template.messages || { workflow_approval: {} };
const defs = defaultMessages[template.notification_type || 'email']; const defs = defaultMessages[template.notification_type || 'email'];
const mergeDefaultMessages = (templ = {}, def) => ({ const mergeDefaultMessages = (def, templ = {}) => ({
message: templ?.message || def.message || '', message: templ?.message || def.message || '',
body: templ?.body || def.body || '', body: templ?.body || def.body || '',
}); });
@@ -140,32 +140,32 @@ function NotificationTemplateForm({
}, },
organization: template.summary_fields?.organization, organization: template.summary_fields?.organization,
messages: { messages: {
started: { ...mergeDefaultMessages(messages.started, defs.started) }, started: { ...mergeDefaultMessages(defs.started, messages.started) },
success: { ...mergeDefaultMessages(messages.success, defs.success) }, success: { ...mergeDefaultMessages(defs.success, messages.success) },
error: { ...mergeDefaultMessages(messages.error, defs.error) }, error: { ...mergeDefaultMessages(defs.error, messages.error) },
workflow_approval: { workflow_approval: {
approved: { approved: {
...mergeDefaultMessages( ...mergeDefaultMessages(
messages.workflow_approval?.approved, defs.workflow_approval.approved,
defs.workflow_approval.approved messages.workflow_approval?.approved
), ),
}, },
denied: { denied: {
...mergeDefaultMessages( ...mergeDefaultMessages(
messages.workflow_approval?.denied, defs.workflow_approval.denied,
defs.workflow_approval.denied messages.workflow_approval?.denied
), ),
}, },
running: { running: {
...mergeDefaultMessages( ...mergeDefaultMessages(
messages.workflow_approval?.running, defs.workflow_approval.running,
defs.workflow_approval.running messages.workflow_approval?.running
), ),
}, },
timed_out: { timed_out: {
...mergeDefaultMessages( ...mergeDefaultMessages(
messages.workflow_approval?.timed_out, defs.workflow_approval.timed_out,
defs.workflow_approval.timed_out messages.workflow_approval?.timed_out
), ),
}, },
}, },

View File

@@ -69,57 +69,55 @@ function OrganizationExecEnvList({ organization }) {
}, [fetchExecutionEnvironments]); }, [fetchExecutionEnvironments]);
return ( return (
<> <Card>
<Card> <PaginatedTable
<PaginatedTable contentError={contentError}
contentError={contentError} hasContentLoading={isLoading}
hasContentLoading={isLoading} items={executionEnvironments}
items={executionEnvironments} itemCount={executionEnvironmentsCount}
itemCount={executionEnvironmentsCount} pluralizedItemName={t`Execution Environments`}
pluralizedItemName={t`Execution Environments`} qsConfig={QS_CONFIG}
qsConfig={QS_CONFIG} toolbarSearchableKeys={searchableKeys}
toolbarSearchableKeys={searchableKeys} toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys} toolbarSearchColumns={[
toolbarSearchColumns={[ {
{ name: t`Name`,
name: t`Name`, key: 'name__icontains',
key: 'name__icontains', isDefault: true,
isDefault: true, },
}, {
{ name: t`Image`,
name: t`Image`, key: 'image__icontains',
key: 'image__icontains', isDefault: false,
isDefault: false, },
}, {
{ name: t`Created By (Username)`,
name: t`Created By (Username)`, key: 'created_by__username__icontains',
key: 'created_by__username__icontains', },
}, {
{ name: t`Modified By (Username)`,
name: t`Modified By (Username)`, key: 'modified_by__username__icontains',
key: 'modified_by__username__icontains', },
}, ]}
]} renderToolbar={(props) => (
renderToolbar={(props) => ( <DatalistToolbar {...props} qsConfig={QS_CONFIG} />
<DatalistToolbar {...props} qsConfig={QS_CONFIG} /> )}
)} headerRow={
headerRow={ <HeaderRow qsConfig={QS_CONFIG} isSelectable={false}>
<HeaderRow qsConfig={QS_CONFIG} isSelectable={false}> <HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell> <HeaderCell sortKey="image">{t`Image`}</HeaderCell>
<HeaderCell sortKey="image">{t`Image`}</HeaderCell> </HeaderRow>
</HeaderRow> }
} renderRow={(executionEnvironment, index) => (
renderRow={(executionEnvironment, index) => ( <OrganizationExecEnvListItem
<OrganizationExecEnvListItem key={executionEnvironment.id}
key={executionEnvironment.id} executionEnvironment={executionEnvironment}
executionEnvironment={executionEnvironment} detailUrl={`/execution_environments/${executionEnvironment.id}`}
detailUrl={`/execution_environments/${executionEnvironment.id}`} rowIndex={index}
rowIndex={index} />
/> )}
)} />
/> </Card>
</Card>
</>
); );
} }

View File

@@ -54,11 +54,11 @@ function SubscriptionModal({
const { selected, setSelected } = useSelected(subscriptions); const { selected, setSelected } = useSelected(subscriptions);
function handleConfirm() { const handleConfirm = () => {
const [subscription] = selected; const [subscription] = selected;
onConfirm(subscription); onConfirm(subscription);
onClose(); onClose();
} };
useEffect(() => { useEffect(() => {
fetchSubscriptions(); fetchSubscriptions();
@@ -109,29 +109,27 @@ function SubscriptionModal({
> >
{isLoading && <ContentLoading />} {isLoading && <ContentLoading />}
{!isLoading && error && ( {!isLoading && error && (
<> <EmptyState variant="full">
<EmptyState variant="full"> <EmptyStateIcon icon={ExclamationTriangleIcon} />
<EmptyStateIcon icon={ExclamationTriangleIcon} /> <Title size="lg" headingLevel="h3">
<Title size="lg" headingLevel="h3"> <Trans>No subscriptions found</Trans>
<Trans>No subscriptions found</Trans> </Title>
</Title> <EmptyStateBody>
<EmptyStateBody> <Trans>
<Trans> We were unable to locate licenses associated with this account.
We were unable to locate licenses associated with this account. </Trans>{' '}
</Trans>{' '} <Button
<Button aria-label={t`Close subscription modal`}
aria-label={t`Close subscription modal`} onClick={onClose}
onClick={onClose} variant="link"
variant="link" isInline
isInline ouiaId="subscription-modal-close"
ouiaId="subscription-modal-close" >
> <Trans>Return to subscription management.</Trans>
<Trans>Return to subscription management.</Trans> </Button>
</Button> </EmptyStateBody>
</EmptyStateBody> <ErrorDetail error={error} />
<ErrorDetail error={error} /> </EmptyState>
</EmptyState>
</>
)} )}
{!isLoading && !error && subscriptions?.length === 0 && ( {!isLoading && !error && subscriptions?.length === 0 && (
<ContentEmpty <ContentEmpty

View File

@@ -119,36 +119,34 @@ function SubscriptionStep() {
labelIcon={ labelIcon={
<Popover <Popover
content={ content={
<> <Trans>
<Trans> A subscription manifest is an export of a Red Hat
A subscription manifest is an export of a Red Hat Subscription. To generate a subscription manifest, go to{' '}
Subscription. To generate a subscription manifest, go to{' '} <Button
<Button component="a"
component="a" href="https://access.redhat.com/management/subscription_allocations"
href="https://access.redhat.com/management/subscription_allocations" variant="link"
variant="link" target="_blank"
target="_blank" isInline
isInline ouiaId="subscription-allocations-link"
ouiaId="subscription-allocations-link" >
> access.redhat.com
access.redhat.com </Button>
</Button> . For more information, see the{' '}
. For more information, see the{' '} <Button
<Button component="a"
component="a" href={`${getDocsBaseUrl(
href={`${getDocsBaseUrl( config
config )}/html/userguide/import_license.html`}
)}/html/userguide/import_license.html`} variant="link"
variant="link" target="_blank"
target="_blank" ouiaId="import-license-link"
ouiaId="import-license-link" isInline
isInline >
> User Guide
User Guide </Button>
</Button> .
. </Trans>
</Trans>
</>
} }
/> />
} }

View File

@@ -33,10 +33,10 @@ function RevertButton({
isMatch = true; isMatch = true;
} }
function handleConfirm() { const handleConfirm = () => {
helpers.setValue(isRevertable ? defaultValue : initialValue); helpers.setValue(isRevertable ? defaultValue : initialValue);
onRevertCallback(); onRevertCallback();
} };
const revertTooltipContent = isRevertable const revertTooltipContent = isRevertable
? t`Revert to factory default.` ? t`Revert to factory default.`

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -9,7 +9,7 @@ function JobTemplateAdd() {
const [formSubmitError, setFormSubmitError] = useState(null); const [formSubmitError, setFormSubmitError] = useState(null);
const history = useHistory(); const history = useHistory();
async function handleSubmit(values) { const handleSubmit = async (values) => {
const { const {
labels, labels,
instanceGroups, instanceGroups,
@@ -35,7 +35,7 @@ function JobTemplateAdd() {
execution_environment: values.execution_environment?.id, execution_environment: values.execution_environment?.id,
}); });
await Promise.all([ await Promise.all([
submitLabels(id, labels, values.project.summary_fields.organization.id), submitLabels(id, values.project.summary_fields.organization.id, labels),
submitInstanceGroups(id, instanceGroups), submitInstanceGroups(id, instanceGroups),
submitCredentials(id, credentials), submitCredentials(id, credentials),
]); ]);
@@ -43,9 +43,9 @@ function JobTemplateAdd() {
} catch (error) { } catch (error) {
setFormSubmitError(error); setFormSubmitError(error);
} }
} };
async function submitLabels(templateId, labels = [], orgId) { async function submitLabels(templateId, orgId, labels = []) {
if (!orgId) { if (!orgId) {
// eslint-disable-next-line no-useless-catch // eslint-disable-next-line no-useless-catch
try { try {
@@ -80,9 +80,9 @@ function JobTemplateAdd() {
return Promise.all(associateCredentials); return Promise.all(associateCredentials);
} }
function handleCancel() { const handleCancel = () => {
history.push(`/templates`); history.push(`/templates`);
} };
return ( return (
<PageSection> <PageSection>

View File

@@ -60,7 +60,7 @@ function JobTemplateEdit({ template, reloadTemplate }) {
try { try {
await JobTemplatesAPI.update(template.id, remainingValues); await JobTemplatesAPI.update(template.id, remainingValues);
await Promise.all([ await Promise.all([
submitLabels(labels, template?.organization), submitLabels(template?.organization, labels),
submitCredentials(credentials), submitCredentials(credentials),
JobTemplatesAPI.orderInstanceGroups( JobTemplatesAPI.orderInstanceGroups(
template.id, template.id,
@@ -77,7 +77,7 @@ function JobTemplateEdit({ template, reloadTemplate }) {
} }
}; };
const submitLabels = async (labels = [], orgId) => { const submitLabels = async (orgId, labels = []) => {
const { added, removed } = getAddedAndRemoved( const { added, removed } = getAddedAndRemoved(
template.summary_fields.labels.results, template.summary_fields.labels.results,
labels labels

View File

@@ -36,14 +36,14 @@ function WorkflowJobTemplateAdd() {
const { const {
data: { id }, data: { id },
} = await WorkflowJobTemplatesAPI.create(templatePayload); } = await WorkflowJobTemplatesAPI.create(templatePayload);
await Promise.all(await submitLabels(id, labels, organizationId)); await Promise.all(await submitLabels(id, organizationId, labels));
history.push(`/templates/workflow_job_template/${id}/visualizer`); history.push(`/templates/workflow_job_template/${id}/visualizer`);
} catch (err) { } catch (err) {
setFormSubmitError(err); setFormSubmitError(err);
} }
}; };
const submitLabels = async (templateId, labels = [], organizationId) => { const submitLabels = async (templateId, organizationId, labels = []) => {
if (!organizationId) { if (!organizationId) {
// eslint-disable-next-line no-useless-catch // eslint-disable-next-line no-useless-catch
try { try {

View File

@@ -34,7 +34,7 @@ function WorkflowJobTemplateEdit({ template }) {
organization?.id || inventory?.summary_fields?.organization.id || null; organization?.id || inventory?.summary_fields?.organization.id || null;
try { try {
await Promise.all( await Promise.all(
await submitLabels(labels, formOrgId, template.organization) await submitLabels(formOrgId, template.organization, labels)
); );
await WorkflowJobTemplatesAPI.update(template.id, templatePayload); await WorkflowJobTemplatesAPI.update(template.id, templatePayload);
history.push(`/templates/workflow_job_template/${template.id}/details`); history.push(`/templates/workflow_job_template/${template.id}/details`);
@@ -43,7 +43,7 @@ function WorkflowJobTemplateEdit({ template }) {
} }
}; };
const submitLabels = async (labels = [], formOrgId, templateOrgId) => { const submitLabels = async (formOrgId, templateOrgId, labels = []) => {
const { added, removed } = getAddedAndRemoved( const { added, removed } = getAddedAndRemoved(
template.summary_fields.labels.results, template.summary_fields.labels.results,
labels labels

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import 'styled-components/macro'; import 'styled-components/macro';
import React, { useContext, useState, useEffect, useCallback } from 'react'; import React, { useContext, useState, useEffect, useCallback } from 'react';
import { useHistory } from 'react-router-dom'; import { useHistory } from 'react-router-dom';
@@ -118,11 +119,16 @@ function NodeModalForm({
contentError || credentialError contentError || credentialError
); );
const nextButtonText = (activeStep) => function nextButtonText(activeStep) {
activeStep.id === promptSteps[promptSteps?.length - 1]?.id || let verifyPromptSteps = false;
activeStep.name === 'Preview' if (promptSteps.length) {
verifyPromptSteps =
activeStep.id === promptSteps[promptSteps.length - 1]?.id;
}
return verifyPromptSteps || activeStep.name === 'Preview'
? t`Save` ? t`Save`
: t`Next`; : t`Next`;
}
const CustomFooter = ( const CustomFooter = (
<WizardFooter> <WizardFooter>

View File

@@ -72,9 +72,14 @@ function NodeViewModal({ readOnly }) {
fullUnifiedJobTemplate?.related?.webhook_receiver && fullUnifiedJobTemplate?.related?.webhook_receiver &&
!fullUnifiedJobTemplate.webhook_key !fullUnifiedJobTemplate.webhook_key
) { ) {
const { let webhook_key = null;
data: { webhook_key }, if (nodeAPI) {
} = await nodeAPI?.readWebhookKey(fullUnifiedJobTemplate.id); const { data } = await nodeAPI.readWebhookKey(
fullUnifiedJobTemplate.id
);
webhook_key = data.webhook_key;
}
related.webhook_key = webhook_key; related.webhook_key = webhook_key;
} }

View File

@@ -617,51 +617,49 @@ function JobTemplateForm({
</FormFullWidthLayout> </FormFullWidthLayout>
{(allowCallbacks || enableWebhooks) && ( {(allowCallbacks || enableWebhooks) && (
<> <SubFormLayout>
<SubFormLayout> {allowCallbacks && (
{allowCallbacks && ( <>
<> <Title size="md" headingLevel="h4">
<Title size="md" headingLevel="h4"> {t`Provisioning Callback details`}
{t`Provisioning Callback details`} </Title>
</Title> <FormColumnLayout>
<FormColumnLayout> {callbackUrl && (
{callbackUrl && ( <FormGroup
<FormGroup label={t`Provisioning Callback URL`}
label={t`Provisioning Callback URL`} fieldId="template-callback-url"
fieldId="template-callback-url" >
> <TextInput
<TextInput id="template-callback-url"
id="template-callback-url" isDisabled
isDisabled value={callbackUrl}
value={callbackUrl} />
/> </FormGroup>
</FormGroup> )}
)} <FormField
<FormField id="template-host-config-key"
id="template-host-config-key" name="host_config_key"
name="host_config_key" label={t`Host Config Key`}
label={t`Host Config Key`} validate={allowCallbacks ? required(null) : null}
validate={allowCallbacks ? required(null) : null} isRequired={allowCallbacks}
isRequired={allowCallbacks} />
/> </FormColumnLayout>
</FormColumnLayout> </>
</> )}
)}
{allowCallbacks && enableWebhooks && <br />} {allowCallbacks && enableWebhooks && <br />}
{enableWebhooks && ( {enableWebhooks && (
<> <>
<Title size="md" headingLevel="h4"> <Title size="md" headingLevel="h4">
{t`Webhook details`} {t`Webhook details`}
</Title> </Title>
<FormColumnLayout> <FormColumnLayout>
<WebhookSubForm templateType={template.type} /> <WebhookSubForm templateType={template.type} />
</FormColumnLayout> </FormColumnLayout>
</> </>
)} )}
</SubFormLayout> </SubFormLayout>
</>
)} )}
</FormColumnLayout> </FormColumnLayout>
</FormFullWidthLayout> </FormFullWidthLayout>

View File

@@ -50,12 +50,8 @@ function UserListItem({ user, isSelected, onSelect, detailUrl, rowIndex }) {
</span> </span>
)} )}
</TdBreakWord> </TdBreakWord>
<Td dataLabel={t`First Name`}> {user.first_name && <Td dataLabel={t`First Name`}>{user.first_name}</Td>}
{user.first_name && <>{user.first_name}</>} {user.last_name && <Td dataLabel={t`Last Name`}>{user.last_name}</Td>}
</Td>
<Td dataLabel={t`Last Name`}>
{user.last_name && <>{user.last_name}</>}
</Td>
<Td dataLabel={t`Role`}>{user_type}</Td> <Td dataLabel={t`Role`}>{user_type}</Td>
<ActionsTd dataLabel={t`Actions`}> <ActionsTd dataLabel={t`Actions`}>
<ActionItem <ActionItem

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useContext } from 'react'; import React, { useContext } from 'react';
import { t } from '@lingui/macro'; import { t } from '@lingui/macro';

View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,22 @@
MIT License
Copyright (c) 2014-present Sebastian McKenzie and other contributors
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2020 Kent C. Dodds
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,7 +0,0 @@
Copyright © Jorge Bucaran <<https://jorgebucaran.com>>
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,22 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 David Clark
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 JD Ballard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,20 +0,0 @@
Copyright (c) 2013 Raynos.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,22 +0,0 @@
Copyright (c) 2013 Thiago de Arruda
Permission is hereby granted, free of charge, to any person
obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without
restriction, including without limitation the rights to use,
copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following
conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 JD Ballard
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,20 +0,0 @@
The MIT License (MIT)
Copyright (c) 2014 Dave Justice
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,25 +0,0 @@
Copyright 2017 Kat Marchán
Copyright npm, Inc.
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
---
This library is a fork of 'better-json-errors' by Kat Marchán, extended and
distributed under the terms of the MIT license above.

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Brian Donovan
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
The MIT License (MIT)
Copyright (c) 2015 Javier Blanco
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -0,0 +1,15 @@
ISC License
Copyright (c) 2021 Alexey Raspopov, Kostiantyn Denysov, Anton Verinov
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,21 +0,0 @@
MIT License
Copyright (c) 2012 James Halliday
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -0,0 +1,28 @@
Copyright (c) 2009-2011, Mozilla Foundation and contributors
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the names of the Mozilla Foundation nor the names of project
contributors may be used to endorse or promote products derived from this
software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

View File

@@ -1,9 +0,0 @@
MIT License
Copyright (c) Sindre Sorhus <sindresorhus@gmail.com> (https://sindresorhus.com)
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -1,13 +0,0 @@
Copyright 2018 Eemeli Aro <eemeli@gmail.com>
Permission to use, copy, modify, and/or distribute this software for any purpose
with or without fee is hereby granted, provided that the above copyright notice
and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
THIS SOFTWARE.