Bump react scripts to 5.0

Bump react scripts to 5.0

See: https://github.com/ansible/awx/issues/11543

Bump eslint

Bump eslint and related plugins

Add @babe/core

Add @babe/core remove babel/core.

Rename .eslintrc to .eslintrc.json

Rename .eslintrc to .eslintrc.json

Add extra plugin

Move babe-plugin-macro as dev dependencies

Move babe-plugin-macro as dev dependencies

Add preset-react

Add preset-react

Fixing lint errors

Fixing lint errors

Run eslint --fix

Run eslint --fix

Turn no-restricted-exports off

Turn no-restricted-exports off

Revert "Run eslint --fix"

This reverts commit e760885b6c199f2ca18091088cb79bfa77c1d3ed.

Run --fix

Run --fix

Fix lint errors

Also bump specificity of Select CSS border component to avoid bug of
missing borders.

Also update API tests related to lincenses.
This commit is contained in:
nixocio 2022-01-19 18:02:13 -05:00
parent 54cbf13219
commit 9703fb06fc
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
dist
images
instrumented
instrumented
*test*.js

View File

@ -1,14 +1,19 @@
{
"parser": "babel-eslint",
"parser": "@babel/eslint-parser",
"ignorePatterns": ["./node_modules/"],
"parserOptions": {
"requireConfigFile": false,
"ecmaVersion": 6,
"sourceType": "module",
"ecmaFeatures": {
"jsx": true,
"modules": true
}
},
"babelOptions": {
"presets": ["@babel/preset-react"]
}
},
"plugins": ["react-hooks", "jsx-a11y", "i18next"],
"plugins": ["react-hooks", "jsx-a11y", "i18next", "@babel"],
"extends": [
"airbnb",
"prettier",
@ -17,7 +22,7 @@
],
"settings": {
"react": {
"version": "16.5.2"
"version": "detect"
},
"import/resolver": {
"node": {
@ -141,6 +146,9 @@
"jsx-a11y/label-has-associated-control": "off",
"react-hooks/rules-of-hooks": "error",
"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
ENV NPMRC_FILE=${NPMRC_FILE}
ARG TARGET='https://awx:8043'
@ -6,7 +6,7 @@ ENV TARGET=${TARGET}
ENV CI=true
WORKDIR /ui
ADD .eslintignore .eslintignore
ADD .eslintrc .eslintrc
ADD .eslintrc.json .eslintrc.json
ADD .linguirc .linguirc
ADD jsconfig.json jsconfig.json
ADD public public

View File

@ -1,7 +1,7 @@
# AWX-UI
## Requirements
- node 14.x LTS, npm 7.x, make, git
- node 14.18.3, npm 7.24.2, make, git
## Development
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",
"ansi-to-html": "0.7.2",
"axios": "0.22.0",
"babel-plugin-macros": "^3.0.1",
"codemirror": "^5.47.0",
"d3": "7.1.1",
"dagre": "^0.8.4",
@ -34,31 +33,36 @@
"styled-components": "5.3.0"
},
"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/preset-react": "7.16.7",
"@cypress/instrument-cra": "^1.4.0",
"@lingui/cli": "^3.7.1",
"@lingui/loader": "^3.8.3",
"@lingui/macro": "^3.7.1",
"@nteract/mockument": "^1.0.4",
"@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-adapter-react-16": "^1.14.0",
"enzyme-to-json": "^3.3.5",
"eslint": "7.30.0",
"eslint-config-airbnb": "18.2.1",
"eslint": "^8.7.0",
"eslint-config-airbnb": "19.0.4",
"eslint-config-prettier": "8.3.0",
"eslint-import-resolver-webpack": "0.11.1",
"eslint-plugin-i18next": "^5.0.0",
"eslint-plugin-import": "^2.14.0",
"eslint-plugin-jsx-a11y": "^6.4.1",
"eslint-plugin-react": "^7.11.1",
"eslint-plugin-react-hooks": "4.2.0",
"eslint-import-resolver-webpack": "0.13.2",
"eslint-plugin-i18next": "5.1.2",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-jsx-a11y": "6.5.1",
"eslint-plugin-react": "7.28.0",
"eslint-plugin-react-hooks": "4.3.0",
"http-proxy-middleware": "^1.0.3",
"jest-websocket-mock": "^2.0.2",
"mock-socket": "^9.0.3",
"prettier": "2.3.2",
"react-scripts": "^4.0.3"
"react-scripts": "5.0.0"
},
"scripts": {
"prelint": "lingui compile",
@ -66,7 +70,7 @@
"prestart-instrumented": "lingui compile",
"pretest": "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",
"build": "INLINE_RUNTIME_CHUNK=false react-scripts build",
"test": "TZ='UTC' react-scripts test --watchAll=false",

View File

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

View File

@ -1,7 +1,11 @@
.pf-c-select__toggle:before {
border-top: var(--pf-c-select__toggle--before--BorderTopWidth) solid var(--pf-c-select__toggle--before--BorderTopColor);
border-right: var(--pf-c-select__toggle--before--BorderRightWidth) solid 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);
.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-right: var(--pf-c-select__toggle--before--BorderRightWidth) solid
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 */

View File

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState, useRef, useEffect } from 'react';
import { t } from '@lingui/macro';
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 { t } from '@lingui/macro';

View File

@ -91,76 +91,74 @@ function AssociateModal({
};
return (
<>
<Modal
ouiaId={ouiaId}
variant="large"
title={title}
aria-label={t`Association modal`}
isOpen={isModalOpen}
onClose={handleClose}
actions={[
<Button
ouiaId="associate-modal-save"
aria-label={t`Save`}
key="select"
variant="primary"
onClick={handleSave}
isDisabled={selected.length === 0}
>
{t`Save`}
</Button>,
<Button
ouiaId="associate-modal-cancel"
aria-label={t`Cancel`}
key="cancel"
variant="link"
onClick={handleClose}
>
{t`Cancel`}
</Button>,
<Modal
ouiaId={ouiaId}
variant="large"
title={title}
aria-label={t`Association modal`}
isOpen={isModalOpen}
onClose={handleClose}
actions={[
<Button
ouiaId="associate-modal-save"
aria-label={t`Save`}
key="select"
variant="primary"
onClick={handleSave}
isDisabled={selected.length === 0}
>
{t`Save`}
</Button>,
<Button
ouiaId="associate-modal-cancel"
aria-label={t`Cancel`}
key="cancel"
variant="link"
onClick={handleClose}
>
{t`Cancel`}
</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',
},
]}
>
<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={[
{
name: t`Name`,
key: `${displayKey}`,
},
]}
searchableKeys={searchableKeys}
relatedSearchableKeys={relatedSearchableKeys}
/>
</Modal>
</>
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 { bool, instanceOf } from 'prop-types';
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 styled from 'styled-components';
import { useLocation } from 'react-router-dom';
@ -77,6 +77,14 @@ function DataListToolbar({
setIsKebabOpen(false);
}
}, [isKebabModalOpen]);
const kebabProviderValue = useMemo(
() => ({
isKebabified: true,
onKebabModalChange: setIsKebabModalOpen,
}),
[setIsKebabModalOpen]
);
return (
<Toolbar
id={`${qsConfig.namespace}-list-toolbar`}
@ -145,25 +153,18 @@ function DataListToolbar({
</ToolbarToggleGroup>
{showExpandCollapse && (
<ToolbarGroup>
<>
<ToolbarItem>
<ExpandCollapse
isCompact={isCompact}
onCompact={onCompact}
onExpand={onExpand}
/>
</ToolbarItem>
</>
<ToolbarItem>
<ExpandCollapse
isCompact={isCompact}
onCompact={onCompact}
onExpand={onExpand}
/>
</ToolbarItem>
</ToolbarGroup>
)}
{isAdvancedSearchShown && additionalControls.length > 0 && (
<ToolbarItem>
<KebabifiedProvider
value={{
isKebabified: true,
onKebabModalChange: setIsKebabModalOpen,
}}
>
<KebabifiedProvider value={kebabProviderValue}>
<Dropdown
toggle={
<KebabToggle

View File

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

View File

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

View File

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

View File

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

View File

@ -35,9 +35,9 @@ function CredentialsStep({
name: 'credentials',
validate: (val) =>
credentialsValidator(
defaultCredentials,
allowCredentialsWithPasswords,
val
val,
defaultCredentials
),
});
const [selectedType, setSelectedType] = useState(null);
@ -102,9 +102,9 @@ function CredentialsStep({
useEffect(() => {
helpers.setError(
credentialsValidator(
defaultCredentials,
allowCredentialsWithPasswords,
field.value
field.value,
defaultCredentials
)
);
/* 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 { ExclamationCircleIcon as PFExclamationCircleIcon } from '@patternfly/react-icons';
import { Tooltip } from '@patternfly/react-core';

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState } from 'react';
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 { 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 React from 'react';
import { t } from '@lingui/macro';

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useState } from 'react';
import { useField, useFormikContext } from 'formik';
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 PropTypes from 'prop-types';
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 { useField, useFormikContext } from 'formik';

View File

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

View File

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

View File

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

View File

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

View File

@ -107,104 +107,102 @@ function InventoryGroupsList() {
actions && Object.prototype.hasOwnProperty.call(actions, 'POST');
return (
<>
<PaginatedTable
contentError={contentError}
hasContentLoading={isLoading || isAdHocLaunchLoading}
items={groups}
itemCount={groupCount}
qsConfig={QS_CONFIG}
clearSelected={clearSelected}
toolbarSearchColumns={[
{
name: t`Name`,
key: 'name__icontains',
isDefault: true,
},
{
name: t`Group type`,
key: 'parents__isnull',
options: [['true', t`Show only root groups`]],
},
{
name: t`Created By (Username)`,
key: 'created_by__username__icontains',
},
{
name: t`Modified By (Username)`,
key: 'modified_by__username__icontains',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
renderRow={(item, index) => (
<InventoryGroupItem
key={item.id}
group={item}
inventoryId={inventoryId}
isSelected={selected.some((row) => row.id === item.id)}
onSelect={() => handleSelect(item)}
rowIndex={index}
<PaginatedTable
contentError={contentError}
hasContentLoading={isLoading || isAdHocLaunchLoading}
items={groups}
itemCount={groupCount}
qsConfig={QS_CONFIG}
clearSelected={clearSelected}
toolbarSearchColumns={[
{
name: t`Name`,
key: 'name__icontains',
isDefault: true,
},
{
name: t`Group type`,
key: 'parents__isnull',
options: [['true', t`Show only root groups`]],
},
{
name: t`Created By (Username)`,
key: 'created_by__username__icontains',
},
{
name: t`Modified By (Username)`,
key: 'modified_by__username__icontains',
},
]}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
headerRow={
<HeaderRow qsConfig={QS_CONFIG}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell>{t`Actions`}</HeaderCell>
</HeaderRow>
}
renderRow={(item, index) => (
<InventoryGroupItem
key={item.id}
group={item}
inventoryId={inventoryId}
isSelected={selected.some((row) => row.id === item.id)}
onSelect={() => handleSelect(item)}
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;

View File

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

View File

@ -57,87 +57,85 @@ function InventorySourceListItem({
}
return (
<>
<Tr id={`source-row-${source.id}`} ouiaId={`source-row-${source.id}`}>
<Td
data-cy={`check-action-${source.id}`}
select={{
rowIndex,
isSelected,
onSelect,
}}
/>
<TdBreakWord dataLabel={t`Name`}>
<Link to={`${detailUrl}/details`}>
<b>{source.name}</b>
</Link>
{missingExecutionEnvironment && (
<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 && (
<Tr id={`source-row-${source.id}`} ouiaId={`source-row-${source.id}`}>
<Td
data-cy={`check-action-${source.id}`}
select={{
rowIndex,
isSelected,
onSelect,
}}
/>
<TdBreakWord dataLabel={t`Name`}>
<Link to={`${detailUrl}/details`}>
<b>{source.name}</b>
</Link>
{missingExecutionEnvironment && (
<span>
<Tooltip
position="top"
content={generateLastJobTooltip(job)}
key={job.id}
className="missing-execution-environment"
content={t`Custom virtual environment ${source.custom_virtualenv} must be replaced by an execution environment.`}
position="right"
>
<Link to={`/jobs/inventory/${job.id}`}>
<StatusLabel status={job.status} />
</Link>
<ExclamationTriangleIcon />
</Tooltip>
)}
</Td>
<Td dataLabel={t`Type`}>{label}</Td>
<ActionsTd dataLabel={t`Actions`}>
{['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
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`}
</span>
)}
</TdBreakWord>
<Td dataLabel={t`Status`}>
{job && (
<Tooltip
position="top"
content={generateLastJobTooltip(job)}
key={job.id}
>
<Button
ouiaId={`${source.id}-edit-button`}
aria-label={t`Edit Source`}
variant="plain"
component={Link}
to={`${detailUrl}/edit`}
>
<PencilAltIcon />
</Button>
<Link to={`/jobs/inventory/${job.id}`}>
<StatusLabel status={job.status} />
</Link>
</Tooltip>
)}
</Td>
<Td dataLabel={t`Type`}>{label}</Td>
<ActionsTd dataLabel={t`Actions`}>
{['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>
</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;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -9,6 +9,12 @@ import Job from './Job';
import JobTypeRedirect from './JobTypeRedirect';
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() {
const match = useRouteMatch();
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 (
<>
<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 { 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);
}
};
return (
<Login header={Header} footer={Footer}>

View File

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

View File

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

View File

@ -120,7 +120,7 @@ function NotificationTemplateForm({
const messages = template.messages || { workflow_approval: {} };
const defs = defaultMessages[template.notification_type || 'email'];
const mergeDefaultMessages = (templ = {}, def) => ({
const mergeDefaultMessages = (def, templ = {}) => ({
message: templ?.message || def.message || '',
body: templ?.body || def.body || '',
});
@ -140,32 +140,32 @@ function NotificationTemplateForm({
},
organization: template.summary_fields?.organization,
messages: {
started: { ...mergeDefaultMessages(messages.started, defs.started) },
success: { ...mergeDefaultMessages(messages.success, defs.success) },
error: { ...mergeDefaultMessages(messages.error, defs.error) },
started: { ...mergeDefaultMessages(defs.started, messages.started) },
success: { ...mergeDefaultMessages(defs.success, messages.success) },
error: { ...mergeDefaultMessages(defs.error, messages.error) },
workflow_approval: {
approved: {
...mergeDefaultMessages(
messages.workflow_approval?.approved,
defs.workflow_approval.approved
defs.workflow_approval.approved,
messages.workflow_approval?.approved
),
},
denied: {
...mergeDefaultMessages(
messages.workflow_approval?.denied,
defs.workflow_approval.denied
defs.workflow_approval.denied,
messages.workflow_approval?.denied
),
},
running: {
...mergeDefaultMessages(
messages.workflow_approval?.running,
defs.workflow_approval.running
defs.workflow_approval.running,
messages.workflow_approval?.running
),
},
timed_out: {
...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]);
return (
<>
<Card>
<PaginatedTable
contentError={contentError}
hasContentLoading={isLoading}
items={executionEnvironments}
itemCount={executionEnvironmentsCount}
pluralizedItemName={t`Execution Environments`}
qsConfig={QS_CONFIG}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarSearchColumns={[
{
name: t`Name`,
key: 'name__icontains',
isDefault: true,
},
{
name: t`Image`,
key: 'image__icontains',
isDefault: false,
},
{
name: t`Created By (Username)`,
key: 'created_by__username__icontains',
},
{
name: t`Modified By (Username)`,
key: 'modified_by__username__icontains',
},
]}
renderToolbar={(props) => (
<DatalistToolbar {...props} qsConfig={QS_CONFIG} />
)}
headerRow={
<HeaderRow qsConfig={QS_CONFIG} isSelectable={false}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell sortKey="image">{t`Image`}</HeaderCell>
</HeaderRow>
}
renderRow={(executionEnvironment, index) => (
<OrganizationExecEnvListItem
key={executionEnvironment.id}
executionEnvironment={executionEnvironment}
detailUrl={`/execution_environments/${executionEnvironment.id}`}
rowIndex={index}
/>
)}
/>
</Card>
</>
<Card>
<PaginatedTable
contentError={contentError}
hasContentLoading={isLoading}
items={executionEnvironments}
itemCount={executionEnvironmentsCount}
pluralizedItemName={t`Execution Environments`}
qsConfig={QS_CONFIG}
toolbarSearchableKeys={searchableKeys}
toolbarRelatedSearchableKeys={relatedSearchableKeys}
toolbarSearchColumns={[
{
name: t`Name`,
key: 'name__icontains',
isDefault: true,
},
{
name: t`Image`,
key: 'image__icontains',
isDefault: false,
},
{
name: t`Created By (Username)`,
key: 'created_by__username__icontains',
},
{
name: t`Modified By (Username)`,
key: 'modified_by__username__icontains',
},
]}
renderToolbar={(props) => (
<DatalistToolbar {...props} qsConfig={QS_CONFIG} />
)}
headerRow={
<HeaderRow qsConfig={QS_CONFIG} isSelectable={false}>
<HeaderCell sortKey="name">{t`Name`}</HeaderCell>
<HeaderCell sortKey="image">{t`Image`}</HeaderCell>
</HeaderRow>
}
renderRow={(executionEnvironment, index) => (
<OrganizationExecEnvListItem
key={executionEnvironment.id}
executionEnvironment={executionEnvironment}
detailUrl={`/execution_environments/${executionEnvironment.id}`}
rowIndex={index}
/>
)}
/>
</Card>
);
}

View File

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

View File

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

View File

@ -33,10 +33,10 @@ function RevertButton({
isMatch = true;
}
function handleConfirm() {
const handleConfirm = () => {
helpers.setValue(isRevertable ? defaultValue : initialValue);
onRevertCallback();
}
};
const revertTooltipContent = isRevertable
? 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 { t } from '@lingui/macro';

View File

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

View File

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

View File

@ -36,14 +36,14 @@ function WorkflowJobTemplateAdd() {
const {
data: { id },
} = 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`);
} catch (err) {
setFormSubmitError(err);
}
};
const submitLabels = async (templateId, labels = [], organizationId) => {
const submitLabels = async (templateId, organizationId, labels = []) => {
if (!organizationId) {
// eslint-disable-next-line no-useless-catch
try {

View File

@ -34,7 +34,7 @@ function WorkflowJobTemplateEdit({ template }) {
organization?.id || inventory?.summary_fields?.organization.id || null;
try {
await Promise.all(
await submitLabels(labels, formOrgId, template.organization)
await submitLabels(formOrgId, template.organization, labels)
);
await WorkflowJobTemplatesAPI.update(template.id, templatePayload);
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(
template.summary_fields.labels.results,
labels

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,3 +1,4 @@
/* eslint-disable react/jsx-no-useless-fragment */
import React, { useContext } from 'react';
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.