Compare commits

...

3 Commits

Author SHA1 Message Date
github-actions[bot]
2bd10d6010 chore: update versions (6-next) (#2948) 2022-01-22 14:01:47 +01:00
Juan Picado
7ff4808be6 feat: improve language switch ui and package manager info (#2936)
* feat: improve language switch ui and package manager info

* feat: improve registry info dialog and language switch

* add description

* update text

* update npmignore

* chore: update test expect
2022-01-22 13:54:00 +01:00
renovate[bot]
7bb3c2bf0e fix(deps): update all non-major core dependencies (#2910)
Co-authored-by: Renovate Bot <bot@renovateapp.com>
2022-01-18 08:35:07 +01:00
53 changed files with 1153 additions and 760 deletions

View File

@@ -95,6 +95,7 @@
"thick-countries-move",
"three-moles-drop",
"three-pots-sit",
"tiny-seals-join",
"two-dolls-check",
"wild-jokes-beam"
]

View File

@@ -0,0 +1,5 @@
---
'@verdaccio/ui-theme': minor
---
feat: improve registry info dialog and language switch

View File

@@ -41,17 +41,17 @@
"@changesets/changelog-github": "0.4.2",
"@changesets/cli": "2.15.0",
"@changesets/get-dependents-graph": "1.2.4",
"@crowdin/cli": "3.7.6",
"@crowdin/cli": "3.7.7",
"@trivago/prettier-plugin-sort-imports": "3.1.1",
"@types/async": "3.2.12",
"@types/autocannon": "4.1.1",
"@types/express": "4.17.13",
"@types/http-errors": "1.8.1",
"@types/http-errors": "1.8.2",
"@types/jest": "27.4.0",
"@types/lodash": "4.14.178",
"@types/mime": "2.0.3",
"@types/minimatch": "3.0.5",
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@types/request": "2.48.8",
"@types/semver": "7.3.9",
"@types/supertest": "2.0.11",
@@ -73,7 +73,7 @@
"babel-plugin-emotion": "10.2.2",
"codecov": "3.8.3",
"concurrently": "6.5.1",
"core-js": "3.20.2",
"core-js": "3.20.3",
"cross-env": "7.0.3",
"debug": "4.3.3",
"detect-secrets": "1.0.6",
@@ -112,7 +112,7 @@
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
"lint": "eslint --max-warnings 47 \"**/*.{js,jsx,ts,tsx}\"",
"lint": "eslint --max-warnings 44 \"**/*.{js,jsx,ts,tsx}\"",
"test": "pnpm recursive test --filter ./packages",
"test:e2e:cli": "pnpm test --filter ...@verdaccio/e2e-cli",
"test:e2e:ui": "pnpm test --filter ...@verdaccio/e2e-ui",

View File

@@ -57,7 +57,7 @@
"semver": "7.3.5"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/server": "workspace:6.0.0-6-next.25",
"@verdaccio/types": "workspace:11.0.0-6-next.10",
"@verdaccio/helper": "1.0.0",

View File

@@ -38,7 +38,7 @@
"http-status-codes": "2.2.0",
"semver": "7.3.5",
"process-warning": "1.0.0",
"core-js": "3.20.2"
"core-js": "3.20.3"
},
"devDependencies": {
"@verdaccio/types": "workspace:11.0.0-6-next.10"

View File

@@ -43,7 +43,7 @@
"@verdaccio/utils": "workspace:6.0.0-6-next.10",
"@verdaccio/readme": "workspace:11.0.0-6-next.4",
"abortcontroller-polyfill": "1.7.3",
"core-js": "3.20.2",
"core-js": "3.20.3",
"debug": "4.3.3",
"fastify": "3.25.3",
"fastify-plugin": "3.0.0",
@@ -51,7 +51,7 @@
"semver": "7.3.5"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/types": "workspace:11.0.0-6-next.10",
"ts-node": "10.4.0"
},

View File

@@ -38,7 +38,7 @@
"build": "exit 0"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"tsd": "0.19.1"
},
"funding": {

View File

@@ -32,14 +32,14 @@
"dependencies": {
"@verdaccio/core": "workspace:6.0.0-6-next.4",
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
"core-js": "3.20.2",
"core-js": "3.20.3",
"debug": "4.3.3",
"handlebars": "4.7.7",
"undici": "4.12.2",
"undici-fetch": "1.0.0-rc.4"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/auth": "workspace:6.0.0-6-next.17",
"@verdaccio/config": "workspace:6.0.0-6-next.12",
"@verdaccio/types": "workspace:11.0.0-6-next.10"

View File

@@ -43,12 +43,12 @@
"@verdaccio/config": "workspace:6.0.0-6-next.12",
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
"@verdaccio/server": "workspace:6.0.0-6-next.25",
"core-js": "3.20.2",
"core-js": "3.20.3",
"debug": "4.3.3",
"lodash": "4.17.21"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/mock": "workspace:6.0.0-6-next.13",
"@verdaccio/types": "workspace:11.0.0-6-next.10",
"jest-mock-process": "1.4.1",

View File

@@ -38,7 +38,7 @@
"@verdaccio/file-locking": "workspace:11.0.0-6-next.4",
"apache-md5": "1.1.7",
"bcryptjs": "2.4.3",
"core-js": "3.20.2",
"core-js": "3.20.3",
"http-errors": "1.8.1",
"unix-crypt-td-js": "1.1.4"
},

View File

@@ -41,7 +41,7 @@
"@verdaccio/file-locking": "workspace:11.0.0-6-next.4",
"@verdaccio/streams": "workspace:11.0.0-6-next.5",
"async": "3.2.3",
"core-js": "3.20.2",
"core-js": "3.20.3",
"debug": "4.3.3",
"globby": "11.1.0",
"lockfile": "1.0.4",

View File

@@ -0,0 +1,4 @@
/*
!/static/**/*
!index.js
!README.md

View File

@@ -1,5 +1,11 @@
# @verdaccio/ui-theme
## 6.0.0-6-next.15
### Minor Changes
- 7ff4808b: feat: improve registry info dialog and language switch
## 6.0.0-6-next.14
### Minor Changes

View File

@@ -35,5 +35,7 @@ module.exports = Object.assign({}, config, {
'verdaccio-ui/utils/(.*)': '<rootDir>/src/utils/$1',
'verdaccio-ui/providers/(.*)': '<rootDir>/src/providers/$1',
'verdaccio-ui/design-tokens/(.*)': '<rootDir>/src/design-tokens/$1',
'react-markdown': '<rootDir>/src/__mocks__/react-markdown.tsx',
'remark-*': '<rootDir>/src/__mocks__/remark-plugin.ts',
},
});

View File

@@ -1,6 +1,6 @@
{
"name": "@verdaccio/ui-theme",
"version": "6.0.0-6-next.14",
"version": "6.0.0-6-next.15",
"description": "Verdaccio User Interface",
"author": {
"name": "Verdaccio Contributors",
@@ -29,12 +29,12 @@
"@emotion/styled": "11.6.0",
"@emotion/css": "11.7.1",
"@emotion/babel-plugin": "11.7.2",
"@mui/icons-material": "5.2.5",
"@mui/material": "5.2.8",
"@mui/styles": "5.2.3",
"@mui/icons-material": "5.3.0",
"@mui/material": "5.3.0",
"@mui/styles": "5.3.0",
"@rematch/core": "2.2.0",
"@rematch/loading": "2.1.2",
"@testing-library/dom": "8.11.1",
"@testing-library/dom": "8.11.2",
"@testing-library/jest-dom": "5.16.1",
"@testing-library/react": "12.1.2",
"@verdaccio/node-api": "workspace:6.0.0-6-next.26",
@@ -56,15 +56,17 @@
"js-yaml": "3.14.1",
"localstorage-memory": "1.0.3",
"lodash": "4.17.21",
"mini-css-extract-plugin": "2.4.6",
"mini-css-extract-plugin": "2.5.2",
"mutationobserver-shim": "0.3.7",
"node-mocks-http": "1.11.0",
"normalize.css": "8.0.1",
"react-markdown": "8.0.0",
"remark-gfm": "3.0.1",
"optimize-css-assets-webpack-plugin": "6.0.1",
"ora": "5.4.1",
"react": "17.0.2",
"react-dom": "17.0.2",
"react-hook-form": "7.23.0",
"react-hook-form": "7.24.0",
"react-hot-loader": "4.13.0",
"react-i18next": "11.15.3",
"react-router": "5.2.1",
@@ -79,7 +81,7 @@
"stylelint-config-recommended": "5.0.0",
"stylelint-config-styled-components": "0.1.1",
"stylelint-processor-styled-components": "1.10.0",
"stylelint-webpack-plugin": "3.1.0",
"stylelint-webpack-plugin": "3.1.1",
"supertest": "6.2.1",
"terser-webpack-plugin": "5.3.0",
"url-loader": "4.1.1",

View File

@@ -23,7 +23,6 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
const [showMobileNavBar, setShowMobileNavBar] = useState<boolean>(false);
const [showLoginModal, setShowLoginModal] = useState<boolean>(false);
const loginStore = useSelector((state: RootState) => state.login);
const configStore = useSelector((state: RootState) => state.configuration.config);
const { configOptions } = useConfig();
const dispatch = useDispatch<Dispatch>();
const handleLogout = () => {
@@ -45,12 +44,12 @@ const Header: React.FC<Props> = ({ withoutSearch }) => {
withoutSearch={withoutSearch}
/>
</InnerNavBar>
<HeaderInfoDialog
isOpen={isInfoDialogOpen}
onCloseDialog={() => setOpenInfoDialog(false)}
registryUrl={configOptions.base}
scope={configStore.scope}
/>
{
<HeaderInfoDialog
isOpen={isInfoDialogOpen}
onCloseDialog={() => setOpenInfoDialog(false)}
/>
}
</NavBar>
{showMobileNavBar && !withoutSearch && (
<MobileNavBar>

View File

@@ -1,19 +1,82 @@
/* eslint-disable verdaccio/jsx-spread */
import styled from '@emotion/styled';
import Box from '@mui/material/Box';
import Tab from '@mui/material/Tab';
import Tabs from '@mui/material/Tabs';
import React from 'react';
import { useTranslation } from 'react-i18next';
import ReactMarkdown from 'react-markdown';
import { useSelector } from 'react-redux';
import remarkGfm from 'remark-gfm';
import { Theme } from 'verdaccio-ui/design-tokens/theme';
import { RootState } from '../../store/store';
import LanguageSwitch from './LanguageSwitch';
import RegistryInfoContent from './RegistryInfoContent';
import RegistryInfoDialog from './RegistryInfoDialog';
interface Props {
isOpen: boolean;
onCloseDialog: () => void;
registryUrl: string;
scope: string;
}
const HeaderInfoDialog: React.FC<Props> = ({ onCloseDialog, isOpen, registryUrl, scope }) => (
<RegistryInfoDialog onClose={onCloseDialog} open={isOpen}>
<RegistryInfoContent registryUrl={registryUrl} scope={scope} />
</RegistryInfoDialog>
);
function a11yProps(index) {
return {
id: `simple-tab-${index}`,
'aria-controls': `simple-tabpanel-${index}`,
};
}
function TabPanel(props) {
const { children, value, index, ...other } = props;
return (
<div
aria-labelledby={`simple-tab-${index}`}
hidden={value !== index}
id={`simple-tabpanel-${index}`}
role="tabpanel"
{...other}
>
{value === index && <Box sx={{ paddingTop: 3 }}>{children}</Box>}
</div>
);
}
const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
padding: '10px 0',
backgroundColor: theme?.palette.background.default,
}));
const HeaderInfoDialog: React.FC<Props> = ({ onCloseDialog, isOpen }) => {
const [value, setValue] = React.useState(0);
const handleChange = (event, newValue) => {
setValue(newValue);
};
const configStore = useSelector((state: RootState) => state.configuration.config);
const { scope, base } = configStore;
const { t } = useTranslation();
return (
<RegistryInfoDialog onClose={onCloseDialog} open={isOpen}>
<Box sx={{ width: '100%' }}>
<Box sx={{ borderBottom: 1, borderColor: 'divider' }}>
<Tabs aria-label="basic tabs example" onChange={handleChange} value={value}>
<Tab label={t('packageManagers.title')} {...a11yProps(0)} />
<Tab label={t('language.title')} {...a11yProps(1)} />
</Tabs>
</Box>
<TabPanel index={0} value={value}>
<RegistryInfoContent registryUrl={base} scope={scope} />
</TabPanel>
<TabPanel index={1} value={value}>
<TextContent>{t('language.description')}</TextContent>
<LanguageSwitch />
<ReactMarkdown remarkPlugins={[remarkGfm]}>{t('language.contribute')}</ReactMarkdown>
</TabPanel>
</Box>
</RegistryInfoDialog>
);
};
export default HeaderInfoDialog;

View File

@@ -52,12 +52,7 @@ const HeaderMenu: React.FC<Props> = ({
<MenuItem>
<HeaderGreetings username={username} />
</MenuItem>
<MenuItem
button={true}
data-testid="logOutDialogIcon"
id="logOutDialogIcon"
onClick={onLogout}
>
<MenuItem data-testid="logOutDialogIcon" id="logOutDialogIcon" onClick={onLogout}>
{t('button.logout')}
</MenuItem>
</Menu>

View File

@@ -5,7 +5,6 @@ import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext';
import HeaderMenu from './HeaderMenu';
import HeaderToolTip from './HeaderToolTip';
import LanguageSwitch from './LanguageSwitch';
import { RightSide } from './styles';
interface Props {
@@ -79,7 +78,6 @@ const HeaderRight: React.FC<Props> = ({
tooltipIconType={'search'}
/>
)}
<LanguageSwitch />
<HeaderToolTip title={t('header.documentation')} tooltipIconType={'help'} />
<HeaderToolTip
onClick={onOpenRegistryInfoDialog}

View File

@@ -1,5 +1,4 @@
import React from 'react';
import Tooltip from 'verdaccio-ui/components/Tooltip';
import HeaderToolTipIcon, { TooltipIconType } from './HeaderToolTipIcon';
@@ -9,10 +8,8 @@ interface Props {
onClick?: () => void;
}
const HeaderToolTip: React.FC<Props> = ({ tooltipIconType, title, onClick }) => (
<Tooltip disableFocusListener={true} title={title}>
<HeaderToolTipIcon onClick={onClick} tooltipIconType={tooltipIconType} />
</Tooltip>
const HeaderToolTip: React.FC<Props> = ({ tooltipIconType, onClick }) => (
<HeaderToolTipIcon onClick={onClick} tooltipIconType={tooltipIconType} />
);
export default HeaderToolTip;

View File

@@ -50,7 +50,7 @@ const HeaderToolTipIcon = forwardRef<HeaderToolTipIconRef, Props>(function Heade
);
case 'search':
return (
<IconSearchButton color="inherit" onClick={onClick}>
<IconSearchButton color="inherit" onClick={onClick} ref={ref}>
<Search />
</IconSearchButton>
);

View File

@@ -1,12 +1,12 @@
/* eslint-disable react/jsx-pascal-case */
import styled from '@emotion/styled';
import LanguageIcon from '@mui/icons-material/Language';
import withStyles from '@mui/styles/withStyles';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import Grid from '@mui/material/Grid';
import Typography from '@mui/material/Typography';
import i18next from 'i18next';
import React, { useCallback, useContext, useMemo, useState } from 'react';
import React, { useCallback, useContext, useState } from 'react';
import { useTranslation } from 'react-i18next';
import { AutoComplete } from 'verdaccio-ui/components/AutoComplete/AutoCompleteV2';
import MenuItem from 'verdaccio-ui/components/MenuItem';
import ThemeContext from 'verdaccio-ui/design-tokens/ThemeContext';
import { Theme } from 'verdaccio-ui/design-tokens/theme';
@@ -20,26 +20,42 @@ const listConverted = listLanguages.reduce((prev, item) => {
return prev;
}, {});
export const CardSelected = styled(Card)<{ theme?: Theme }>(({ theme }) => {
return {
backgroundColor: theme?.palette?.grey['600'],
opacity: '0.9',
color: theme?.palette?.error.contrastText,
fontWeight: 'bold',
};
});
export const CardUnSelected = styled(Card)<{ theme?: Theme }>(({ theme }) => {
return {
cursor: 'pointer',
':hover': {
backgroundColor: theme?.palette.greyGainsboro,
},
};
});
const LanguageContent = ({ translation, icon }) => (
<>
<CardContent>
<Typography display="block" gutterBottom={true} variant="caption">
{translation}
</Typography>
</CardContent>
<CardContent>{icon}</CardContent>
</>
);
const LanguageSwitch = () => {
const themeContext = useContext(ThemeContext);
const themeContext = useContext(ThemeContext) as any;
const [languages] = useState<Language[]>(
Object.keys(i18next.options?.resources || {}) as Language[]
);
const { t } = useTranslation();
if (!themeContext) {
throw Error(t('theme-context-not-correct-used'));
}
const currentLanguage = themeContext.language;
const switchLanguage = useCallback(
({ language }: { language: Language }) => {
themeContext.setLanguage(language);
},
[themeContext]
);
const getCurrentLngDetails = useCallback(
(language: Language) => {
const lng = listConverted[language] || listConverted['en-US'];
@@ -50,75 +66,35 @@ const LanguageSwitch = () => {
},
[t]
);
const options = useMemo(
() =>
languages.map((language) => {
const { icon, translation } = getCurrentLngDetails(language);
return {
language,
icon,
translation,
};
}),
[languages, getCurrentLngDetails]
);
const option = useCallback(
({ icon, translation }: ReturnType<typeof getCurrentLngDetails>) => (
<StyledMenuItem component="div">
{icon}
{translation}
</StyledMenuItem>
),
[]
);
const optionLabel = useCallback(
({ translation }: ReturnType<typeof getCurrentLngDetails>) => translation,
[]
const handleChangeLanguage = useCallback(
(language: Language) => {
themeContext.setLanguage(language);
},
[themeContext]
);
return (
<Wrapper>
<AutoComplete
defaultValue={getCurrentLngDetails(currentLanguage).translation}
getOptionLabel={optionLabel}
hasClearIcon={true}
inputStartAdornment={<StyledLanguageIcon />}
onClick={switchLanguage}
options={options}
renderOption={option}
variant="outlined"
/>
</Wrapper>
<div>
<Grid columns={{ xs: 12, sm: 12, md: 12 }} container={true} spacing={{ xs: 1, md: 1 }}>
{languages.map((language, index) => {
const { icon, translation } = getCurrentLngDetails(language);
return (
<Grid item={true} key={index} sm={2} xs={6}>
{language === currentLanguage ? (
<CardSelected sx={{ maxWidth: 80 }}>
<LanguageContent icon={icon} translation={translation} />
</CardSelected>
) : (
<CardUnSelected onClick={() => handleChangeLanguage(language)}>
<LanguageContent icon={icon} translation={translation} />
</CardUnSelected>
)}
</Grid>
);
})}
</Grid>
</div>
);
};
export default LanguageSwitch;
const StyledLanguageIcon = styled(LanguageIcon)<{ theme?: Theme }>(({ theme }) => ({
color: theme?.palette.white,
}));
const Wrapper = styled('div')<{ theme?: Theme }>(({ theme }) => ({
width: 220,
display: 'none',
[`@media screen and (min-width: ${theme?.breakPoints.medium}px)`]: {
display: 'inline-block',
},
}));
const StyledMenuItem = withStyles((theme: Theme) => ({
root: {
display: 'grid',
cursor: 'pointer',
gridGap: theme?.spacing(0.5),
gridTemplateColumns: '20px 1fr',
alignItems: 'center',
'&:first-child': {
borderTopLeftRadius: 4,
borderTopRightRadius: 4,
},
},
}))(MenuItem);

View File

@@ -9,8 +9,10 @@ describe('<RegistryInfoContent /> component', () => {
});
test('should load the component with no data', () => {
render(<RegistryInfoContent registryUrl={''} scope={''} />);
expect(screen.getByText('No configurations available')).toBeInTheDocument();
render(<RegistryInfoContent registryUrl="http://localhost:4873" scope={''} />);
expect(
screen.getByText(/This is the configuration details for the registry./)
).toBeInTheDocument();
});
test('should load the appropiate tab content when the tab is clicked', () => {

View File

@@ -9,18 +9,23 @@ import Typography from '@mui/material/Typography';
import makeStyles from '@mui/styles/makeStyles';
import React, { FC } from 'react';
import { useTranslation } from 'react-i18next';
import ReactMarkdown from 'react-markdown';
import remarkGfm from 'remark-gfm';
import CopyToClipBoard from 'verdaccio-ui/components/CopyToClipBoard';
import { useConfig } from 'verdaccio-ui/providers/config';
import {
getCLIChangePassword,
getCLISBerryYamlRegistry,
getCLISetConfigRegistry,
getCLISetRegistry,
} from 'verdaccio-ui/utils/cli-utils';
import { NODE_MANAGER } from 'verdaccio-ui/utils/constants';
const renderNpmTab = (scope: string, registryUrl: string): JSX.Element => {
import { Description, TextContent } from './styles';
const renderNpmTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
return (
<Box display="flex" flexDirection="column" p={1}>
<Box display="flex" flexDirection="column">
<CopyToClipBoard
text={getCLISetConfigRegistry(`${NODE_MANAGER.npm} set`, scope, registryUrl)}
/>
@@ -30,9 +35,9 @@ const renderNpmTab = (scope: string, registryUrl: string): JSX.Element => {
);
};
const renderPnpmTab = (scope: string, registryUrl: string): JSX.Element => {
const renderPnpmTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
return (
<Box display="flex" flexDirection="column" p={1}>
<Box display="flex" flexDirection="column">
<CopyToClipBoard
text={getCLISetConfigRegistry(`${NODE_MANAGER.pnpm} set`, scope, registryUrl)}
/>
@@ -42,9 +47,9 @@ const renderPnpmTab = (scope: string, registryUrl: string): JSX.Element => {
);
};
const renderYarnTab = (scope: string, registryUrl: string): JSX.Element => {
const renderYarnTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
return (
<Box display="flex" flexDirection="column" p={1}>
<Box display="flex" flexDirection="column">
<CopyToClipBoard
text={getCLISetConfigRegistry(`${NODE_MANAGER.yarn} config set`, scope, registryUrl)}
/>
@@ -52,6 +57,14 @@ const renderYarnTab = (scope: string, registryUrl: string): JSX.Element => {
);
};
const renderYarnBerryTab = (scope: string | undefined, registryUrl: string): JSX.Element => {
return (
<Box display="flex" flexDirection="column">
<CopyToClipBoard text={getCLISBerryYamlRegistry(scope, registryUrl)} />
</Box>
);
};
const useStyles = makeStyles((theme) => ({
root: {
width: '100%',
@@ -63,7 +76,7 @@ const useStyles = makeStyles((theme) => ({
}));
export const AccordionContainer = styled('div')({
padding: 30,
padding: 0,
paddingLeft: 0,
paddingRight: 0,
});
@@ -79,7 +92,7 @@ export const LinkContainer = styled('div')({
export type Props = {
registryUrl: string;
scope: string;
scope: string | undefined;
};
const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
@@ -90,15 +103,13 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
const hasNpm = configOptions?.pkgManagers?.includes('npm');
const hasYarn = configOptions?.pkgManagers?.includes('yarn');
const hasPnpm = configOptions?.pkgManagers?.includes('pnpm');
const hasPkgManagers = hasNpm | hasPnpm | hasYarn;
if (!hasPkgManagers || !scope || !registryUrl) {
return <AccordionContainer>{t('header.registry-no-conf')}</AccordionContainer>;
}
return hasPkgManagers ? (
<AccordionContainer>
{hasNpm && (
<Accordion>
// TODO: we can improve this logic, expanding only one accordion based on which package manager is enabled
// feel free to contribute here
return (
<>
<TextContent>{t('packageManagers.description')}</TextContent>
<AccordionContainer>
<Accordion disabled={!hasNpm}>
<AccordionSummary
aria-controls="panel1a-content"
expandIcon={<ExpandMoreIcon />}
@@ -112,9 +123,7 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
</CommandContainer>
</AccordionDetails>
</Accordion>
)}
{hasYarn && (
<Accordion>
<Accordion disabled={!hasYarn}>
<AccordionSummary
aria-controls="panel2a-content"
expandIcon={<ExpandMoreIcon />}
@@ -123,14 +132,26 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
<Typography className={classes.heading}>{'yarn'}</Typography>
</AccordionSummary>
<AccordionDetails>
<Description>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{t('packageManagers.yarnclassicDetails')}
</ReactMarkdown>
</Description>
<CommandContainer data-testid={'tab-content'}>
{renderYarnTab(scope, registryUrl)}
</CommandContainer>
<Description>
<ReactMarkdown remarkPlugins={[remarkGfm]}>
{t('packageManagers.yarnBerryDetails')}
</ReactMarkdown>
</Description>
<CommandContainer data-testid={'tab-content'}>
{renderYarnBerryTab(scope, registryUrl)}
</CommandContainer>
</AccordionDetails>
</Accordion>
)}
{hasPnpm && (
<Accordion>
<Accordion disabled={!hasPnpm}>
<AccordionSummary
aria-controls="panel3a-content"
expandIcon={<ExpandMoreIcon />}
@@ -144,14 +165,15 @@ const RegistryInfoContent: FC<Props> = ({ scope, registryUrl }) => {
</CommandContainer>
</AccordionDetails>
</Accordion>
)}
<LinkContainer>
<Link href="https://verdaccio.org/docs/en/cli-registry" target="_blank">
<Typography>{t('header.registry-info-link')}</Typography>
</Link>
</LinkContainer>
</AccordionContainer>
) : null;
<LinkContainer>
<Link href="https://verdaccio.org/docs/en/cli-registry" target="_blank">
<Typography>{t('header.registry-info-link')}</Typography>
</Link>
</LinkContainer>
</AccordionContainer>
</>
);
};
export default RegistryInfoContent;

View File

@@ -0,0 +1,12 @@
import styled from '@emotion/styled';
import { Theme } from 'verdaccio-ui/design-tokens/theme';
export const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
paddingBottom: '10px',
backgroundColor: theme?.palette.background.default,
}));
export const Description = styled('div')<{ theme?: Theme }>(() => ({
fontSize: '1rem',
fontStyle: 'italic',
}));

View File

@@ -16,7 +16,7 @@ const RegistryInfoDialog: React.FC<Props> = ({ open = false, children, onClose }
onClose={onClose}
open={open}
>
<Title disableTypography={true}>{t('dialog.registry-info.title')}</Title>
<Title>{t('dialog.registry-info.title')}</Title>
<Content>{children}</Content>
<DialogActions>
<Button color="inherit" id="registryInfo--dialog-close" onClick={onClose}>

View File

@@ -13,3 +13,8 @@ export const Content = styled(DialogContent)<{ theme?: Theme }>(({ theme }) => (
padding: '0 24px',
backgroundColor: theme?.palette.background.default,
}));
export const TextContent = styled('div')<{ theme?: Theme }>(({ theme }) => ({
padding: '10px 24px',
backgroundColor: theme?.palette.background.default,
}));

View File

@@ -0,0 +1,8 @@
// eslint-disable
import React from 'react';
// @ts-ignore
export default function ({ children }) {
return <>{children}</>;
}
// eslint-enable

View File

@@ -0,0 +1 @@
export default function () {}

View File

@@ -1,300 +0,0 @@
import styled from '@emotion/styled';
import CloseIcon from '@mui/icons-material/Close';
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
import withStyles from '@mui/styles/withStyles';
import React, {
ChangeEvent,
KeyboardEvent,
memo,
useCallback,
useEffect,
useRef,
useState,
} from 'react';
import { useTranslation } from 'react-i18next';
import { Theme } from 'verdaccio-ui/design-tokens/theme';
import { useOnClickOutside } from '../../hooks/useOnClickOutside';
import IconButton from '../IconButton';
import MenuItem from '../MenuItem';
import Paper from '../Paper';
import TextField from '../TextField';
import { createFilterOptions } from './useAutoComplete';
const defaultFilterOptions = createFilterOptions();
type TextFieldProps = React.ComponentProps<typeof TextField>;
interface Props<Option extends {}> extends Pick<TextFieldProps, 'variant'> {
options: Option[];
getOptionLabel: (option: Option) => string;
renderOption?: (option: Option) => React.ReactNode;
placeholder?: string;
label?: React.ReactNode;
defaultValue?: string;
inputStartAdornment?: React.ReactNode;
hasClearIcon?: boolean;
className?: string;
onClick?: (option: any) => void;
}
const AutoComplete = <Option extends {}>({
placeholder,
defaultValue = '',
label,
variant,
inputStartAdornment,
options: suggestions,
getOptionLabel,
renderOption: renderOptionProp,
className,
onClick,
hasClearIcon,
}: Props<Option>) => {
const { t } = useTranslation();
const [searchTerm, setSearchTerm] = useState(defaultValue);
const [options, setOptions] = useState(suggestions);
const [activeOption, setActiveOption] = useState(0);
const [showOptions, setShowOptions] = useState(false);
// refs
const wrapperRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLInputElement>(null);
const contentRef = useRef<HTMLDivElement>(null);
const clickOutside = useCallback(() => {
setShowOptions(false);
if (!searchTerm.trim()) {
setSearchTerm(defaultValue);
}
}, [searchTerm, defaultValue]);
const filterOptions = useCallback(() => {
const filteredOptions = defaultFilterOptions(suggestions, {
inputValue: searchTerm,
getOptionLabel,
});
setOptions(filteredOptions);
}, [suggestions, searchTerm, getOptionLabel]);
const scrollIntoOption = useCallback(() => {
const option = contentRef?.current?.children[activeOption];
if (option) {
option.scrollIntoView({
behavior: 'smooth',
block: 'nearest',
inline: 'start',
});
}
}, [activeOption]);
useOnClickOutside(wrapperRef, useCallback(clickOutside, [wrapperRef, searchTerm]));
useEffect(filterOptions, [searchTerm]);
useEffect(scrollIntoOption, [activeOption]);
useEffect(() => {
setSearchTerm(defaultValue);
}, [defaultValue]);
const handleChange = useCallback((event: ChangeEvent<HTMLInputElement>) => {
setSearchTerm(event.target.value);
}, []);
const handleToggleShowOptions = useCallback(() => {
setShowOptions(!showOptions);
}, [showOptions]);
const handleClear = useCallback(() => {
setSearchTerm('');
setOptions([]);
setShowOptions(true);
if (inputRef.current) {
inputRef.current.focus();
}
}, []);
const handleClickOption = useCallback(
(option: Option) => () => {
if (onClick) {
onClick(option);
}
setSearchTerm(getOptionLabel(option));
setShowOptions(false);
if (inputRef.current) {
inputRef.current.blur();
}
},
[getOptionLabel, onClick]
);
const handleFocus = useCallback(() => {
setShowOptions(true);
}, []);
const onKeyDown = useCallback(
(event: KeyboardEvent) => {
// User pressed the enter key
if (event.keyCode === 13) {
setActiveOption(0);
setShowOptions(false);
handleClickOption(options[activeOption])();
return;
}
// User pressed the up arrow
if (event.keyCode === 38) {
if (activeOption === 0) {
return;
}
setActiveOption(activeOption - 1);
return;
}
// User pressed the down arrow
if (event.keyCode === 40) {
if (activeOption + 1 >= options.length) {
return;
}
setActiveOption(activeOption + 1);
return;
}
},
[activeOption, handleClickOption, options]
);
const renderOptions = useCallback(() => {
if (renderOptionProp) {
return options.map((option, index) => (
<Option
isSelected={index === activeOption}
key={index}
onClick={handleClickOption(option)}
tabIndex={0}
>
{renderOptionProp(option)}
</Option>
));
}
return options.map((option, index) => (
<MenuItem
component="div"
key={index}
onClick={handleClickOption(option)}
selected={index === activeOption}
tabIndex={0}
>
{getOptionLabel(option)}
</MenuItem>
));
}, [options, activeOption, getOptionLabel, renderOptionProp, handleClickOption]);
return (
<Wrapper className={className} ref={wrapperRef}>
<StyledTextField
autoComplete="off"
InputProps={{
startAdornment: inputStartAdornment,
endAdornment: (
<EndAdornment>
{hasClearIcon && searchTerm.length > 0 && (
<IconButton
color="inherit"
onClick={handleClear}
size="small"
title={t('autoComplete.clear')}
>
<CloseIcon fontSize="small" />
</IconButton>
)}
<ExpandButton
color="inherit"
onClick={handleToggleShowOptions}
showOptions={showOptions}
size="small"
title={showOptions ? t('autoComplete.collapse') : t('autoComplete.expand')}
>
<ExpandMoreIcon fontSize="small" />
</ExpandButton>
</EndAdornment>
),
}}
inputRef={inputRef}
label={label}
onChange={handleChange}
onFocus={handleFocus}
onKeyDown={onKeyDown}
placeholder={placeholder}
value={searchTerm}
variant={variant}
/>
{showOptions && (
<StyledPaper ref={contentRef} square={true}>
{renderOptions()}
</StyledPaper>
)}
</Wrapper>
);
};
const MemoizedAutoComplete = memo(AutoComplete) as typeof AutoComplete;
export { MemoizedAutoComplete as AutoComplete };
const Wrapper = styled('div')`
position: relative;
height: 40px;
`;
const EndAdornment = styled('div')({
display: 'flex',
});
const StyledTextField = styled(TextField)<{ theme?: Theme }>(({ theme }) => ({
height: 40,
color: theme?.palette.white,
['& .MuiInputBase-root']: {
height: 40,
color: theme?.palette.white,
},
['& .MuiInputBase-inputAdornedStart']: {
paddingLeft: theme?.spacing(2),
},
['& .MuiInputBase-input']: {
paddingTop: theme?.spacing(1),
paddingBottom: theme?.spacing(1),
},
['& .MuiOutlinedInput-notchedOutline']: {
borderColor: 'transparent',
},
['& :hover .MuiOutlinedInput-notchedOutline']: {
borderColor: theme?.palette.white,
},
['& :active .MuiOutlinedInput-notchedOutline']: {
borderColor: theme?.palette.white,
},
}));
const ExpandButton = styled(IconButton, {
shouldForwardProp: (prop) => prop !== 'showOptions',
})<{ showOptions: boolean }>(({ showOptions }) => ({
transform: showOptions ? 'rotate(180deg)' : 'none',
}));
const Option = styled('div')<{ isSelected: boolean }>(({ isSelected }) => ({
background: isSelected ? 'rgba(0, 0, 0, 0.08)' : 'none',
}));
const StyledPaper = withStyles((theme: Theme) => ({
root: {
marginTop: theme?.spacing(0.5),
position: 'absolute',
borderRadius: 3,
maxHeight: 165,
overflowY: 'scroll',
zIndex: 10000,
overflowX: 'hidden',
},
}))(Paper);

View File

@@ -1,61 +0,0 @@
// https://stackoverflow.com/questions/990904/remove-accents-diacritics-in-a-string-in-javascript
// Give up on IE 11 support for this feature
function stripDiacritics(value: string) {
return typeof value.normalize !== 'undefined'
? value.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
: value;
}
// Based on https://github.com/netochaves/material-ui/tree/test/useAutocomplete/packages/material-ui-lab/src/useAutocomplete
type CreateFilterOptionsConfig = {
ignoreAccents?: boolean;
ignoreCase?: boolean;
limit?: number;
matchFrom?: 'any' | 'start';
trim?: boolean;
};
export interface FilterOptionsState<Option> {
inputValue: string;
getOptionLabel: (option: Option) => string;
}
function createFilterOptions(config?: CreateFilterOptionsConfig) {
const {
ignoreAccents = true,
ignoreCase = true,
trim = false,
limit,
matchFrom = 'any',
} = config || {};
return <Option extends {}>(
options: Option[],
{ inputValue, getOptionLabel }: FilterOptionsState<Option>
): Option[] => {
let input = trim ? inputValue.trim() : inputValue;
if (ignoreCase) {
input = input.toLowerCase();
}
if (ignoreAccents) {
input = stripDiacritics(input);
}
const filteredOptions = options.filter((option) => {
let candidate = getOptionLabel(option);
if (ignoreCase) {
candidate = candidate.toLowerCase();
}
if (ignoreAccents) {
candidate = stripDiacritics(candidate);
}
return matchFrom === 'start' ? candidate.indexOf(input) === 0 : candidate.indexOf(input) > -1;
});
return typeof limit === 'number' ? filteredOptions.slice(0, limit) : filteredOptions;
};
}
export { createFilterOptions };

View File

@@ -3,8 +3,8 @@ import React, { forwardRef } from 'react';
type CardContentRef = HTMLElementTagNameMap[keyof HTMLElementTagNameMap];
const CardContent = forwardRef<CardContentRef, CardContentProps>(function CardContent(props, ref) {
return <MaterialUICardContent {...props} innerRef={ref} />;
const CardContent = forwardRef<CardContentRef, CardContentProps>(function CardContent(props) {
return <MaterialUICardContent {...props} />;
});
export default CardContent;

View File

@@ -3,8 +3,8 @@ import React, { forwardRef } from 'react';
type ChipRef = HTMLElementTagNameMap[keyof HTMLElementTagNameMap];
const Chip = forwardRef<ChipRef, ChipProps>(function Chip(props, ref) {
return <MaterialUIChip {...props} innerRef={ref} />;
const Chip = forwardRef<ChipRef, ChipProps>(function Chip(props) {
return <MaterialUIChip {...props} />;
});
export default Chip;

View File

@@ -37,8 +37,8 @@ const Wrapper = styled('div')({
const Content = styled('span')({
display: 'inline-block',
overflow: 'hidden',
whiteSpace: 'nowrap',
textOverflow: 'ellipsis',
height: '21px',
height: 'auto',
whiteSpace: 'break-spaces',
fontSize: '1rem',
});

View File

@@ -11,12 +11,12 @@ type Props = Omit<TooltipProps, 'title'> & {
title: TFunctionResult;
};
const Tooltip = forwardRef<TooltipRef, Props>(function ToolTip({ title, children, ...props }, ref) {
const Tooltip = forwardRef<TooltipRef, Props>(function ToolTip({ title, children, ...props }) {
if (!title) {
return children;
}
return (
<MaterialUITooltip {...props} title={title} innerRef={ref}>
<MaterialUITooltip {...props} title={title}>
{children}
</MaterialUITooltip>
);

View File

@@ -1,4 +1,4 @@
import { adaptV4Theme, createTheme } from '@mui/material/styles';
import { createTheme } from '@mui/material/styles';
import { PRIMARY_COLOR } from 'verdaccio-ui/utils/colors';
const colors = {
@@ -92,30 +92,28 @@ type CustomizedTheme = typeof customizedTheme;
export const getTheme = (themeMode: ThemeMode, primaryColor: string) => {
const palette = applyPrimaryColor(themeMode, primaryColor);
return createTheme(
adaptV4Theme({
typography: {
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Helvetica Neue"',
'Arial',
'sans-serif',
].join(','),
return createTheme({
typography: {
fontFamily: [
'-apple-system',
'BlinkMacSystemFont',
'"Helvetica Neue"',
'Arial',
'sans-serif',
].join(','),
},
palette: {
mode: themeMode,
...palette,
primary: { main: palette.primary },
secondary: { main: palette.secondary },
error: { main: palette.red },
background: {
default: palette.background,
},
palette: {
mode: themeMode,
...palette,
primary: { main: palette.primary },
secondary: { main: palette.secondary },
error: { main: palette.red },
background: {
default: palette.background,
},
},
...customizedTheme,
})
);
},
...customizedTheme,
});
};
export type Theme = ReturnType<typeof getTheme>;

View File

@@ -9,7 +9,7 @@
},
"dialog": {
"registry-info": {
"title": "Registry Info"
"title": "Configuration Details"
}
},
"header": {
@@ -166,5 +166,16 @@
"china": "China",
"germany": "Germany",
"taiwan": "Taiwan"
},
"packageManagers": {
"title": "Package Managers",
"description": "This is the configuration details for the registry. Each package manager could have different configuration, expand each section for more details. If the section is disable review your configuration.",
"yarnclassicDetails": "Yarn classic configuration differs from Yarn 2+ configuration. For more details, please visit [Yarn Classic](https://verdaccio.org/docs/cli-registry#yarn-1x).",
"yarnBerryDetails": "Yarn Berry does not support the `--registry` flag, instead all configurarion should be defined on the `yarnrc.yalm` file in the root of your project. For more details, please visit [Yarn Berry](https://verdaccio.org/docs/cli-registry#yarn-berry-2x)."
},
"language": {
"title": "Translations",
"contribute": "If your language is not listed here or is not fully translated, please contribute to the translation. You can find the source code on [our main repository](https://github.com/verdaccio/verdaccio/blob/master/packages/plugins/ui-theme/src/i18n/ABOUT_TRANSLATIONS.md)",
"description": "This is the configuration details for the language. Select a language to select a client side translation."
}
}

View File

@@ -66,9 +66,9 @@ exports[`<Help /> component should load the component in default state 1`] = `
.emotion-9 {
display: inline-block;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
height: 21px;
height: auto;
white-space: break-spaces;
font-size: 1rem;
}

View File

@@ -22,11 +22,11 @@ export const copyToClipBoardUtility =
export function getCLISetConfigRegistry(
command: string,
scope: string,
scope: string | undefined,
registryUrl: string
): string {
// if there is a scope defined there needs to be a ":" separator between the scope and the registry
return `${command} ${scope}${scope !== '' ? ':' : ''}registry ${registryUrl}`;
return `${command} ${scope ? `${scope}:` : ''}registry ${registryUrl}`;
}
export function getCLISetRegistry(command: string, registryUrl: string): string {
@@ -36,3 +36,18 @@ export function getCLISetRegistry(command: string, registryUrl: string): string
export function getCLIChangePassword(command: string, registryUrl: string): string {
return `${command} profile set password --registry ${registryUrl}`;
}
export function getCLISBerryYamlRegistry(scope: string | undefined, registryUrl: string): string {
return !scope
? `// .yarnrc.yml
npmRegistryServer: "${registryUrl}"
unsafeHttpWhitelist:
- ${new URL(registryUrl).host}`
: `
// .yarnrc.yml
npmRegistryServer: "${registryUrl}"
npmScopes:
${scope.replace('@', '')}:
npmRegistryServer: ${registryUrl}
npmPublishRegistry: ${registryUrl}`;
}

View File

@@ -2,10 +2,11 @@ web:
title: Verdaccio Local Dev
sort_packages: asc
primary_color: #CCC
login: true
login: false
pkgManagers:
- npm
- yarn
- pnpm
plugins: ../

View File

@@ -39,7 +39,6 @@ export default {
__UI_OPTIONS: JSON.stringify({
...configJsonFormat.web,
filename: 'index.html',
verdaccioURL: '//localhost:4873',
base: new URL('/', 'http://localhost:4873'),
}),
template: `${env.SRC_ROOT}/template/index.html`,

View File

@@ -56,7 +56,6 @@ const prodConf = {
primary_color: 'ToReplaceByPrimaryColor',
filename: 'index.html',
favicon: `${env.SRC_ROOT}/template/favicon.ico`,
verdaccioURL: 'ToReplaceByVerdaccio',
version_app: 'ToReplaceByVersion',
template: `${env.SRC_ROOT}/template/index.html`,
debug: false,

View File

@@ -49,13 +49,13 @@
"abortcontroller-polyfill": "1.7.3",
"debug": "4.3.3",
"lodash": "4.17.21",
"node-fetch": "2.6.6",
"node-fetch": "2.6.7",
"request": "2.88.0",
"undici": "4.12.2",
"undici-fetch": "1.0.0-rc.4"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/types": "workspace:11.0.0-6-next.10",
"get-stream": "^6.0.1",
"nock": "13.2.2",

View File

@@ -49,7 +49,7 @@
"lodash": "4.17.21"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/mock": "workspace:6.0.0-6-next.13",
"@verdaccio/proxy": "workspace:6.0.0-6-next.17",
"@verdaccio/helper": "1.0.0",

View File

@@ -33,7 +33,7 @@
"license": "MIT",
"devDependencies": {
"@verdaccio/cli": "workspace:6.0.0-6-next.27",
"@verdaccio/ui-theme": "workspace:6.0.0-6-next.14",
"@verdaccio/ui-theme": "workspace:6.0.0-6-next.15",
"fs-extra": "10.0.0",
"webpack": "5.66.0",
"webpack-bundle-analyzer": "4.5.0",

View File

@@ -59,7 +59,7 @@
"semver": "7.3.5"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/mock": "workspace:6.0.0-6-next.13",
"@verdaccio/types": "workspace:11.0.0-6-next.10",
"@verdaccio/helper": "workspace:1.0.0",

View File

@@ -43,7 +43,7 @@
"@verdaccio/core": "workspace:6.0.0-6-next.4",
"@verdaccio/config": "workspace:6.0.0-6-next.12",
"@verdaccio/utils": "workspace:6.0.0-6-next.10",
"core-js": "3.20.2",
"core-js": "3.20.3",
"debug": "4.3.3",
"fs-extra": "10.0.0",
"lodash": "4.17.21",

View File

@@ -1,5 +1,12 @@
# verdaccio
## 6.0.0-6-next.30
### Patch Changes
- Updated dependencies [7ff4808b]
- @verdaccio/ui-theme@6.0.0-6-next.15
## 6.0.0-6-next.29
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "verdaccio",
"version": "6.0.0-6-next.29",
"version": "6.0.0-6-next.30",
"description": "A lightweight private npm proxy registry",
"main": "build/index.js",
"types": "build/index.d.ts",
@@ -41,7 +41,7 @@
"@verdaccio/hooks": "workspace:6.0.0-6-next.11",
"@verdaccio/logger": "workspace:6.0.0-6-next.9",
"@verdaccio/node-api": "workspace:6.0.0-6-next.26",
"@verdaccio/ui-theme": "workspace:6.0.0-6-next.14",
"@verdaccio/ui-theme": "workspace:6.0.0-6-next.15",
"@verdaccio/utils": "workspace:6.0.0-6-next.10",
"verdaccio-audit": "workspace:11.0.0-6-next.8",
"verdaccio-htpasswd": "workspace:11.0.0-6-next.11"

View File

@@ -43,7 +43,7 @@
"lru-cache": "6.0.0"
},
"devDependencies": {
"@types/node": "16.11.19",
"@types/node": "16.11.20",
"@verdaccio/types": "workspace:11.0.0-6-next.10",
"node-html-parser": "4.1.5",
"supertest": "6.2.1",

977
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,7 @@
"version": "2.0.0-6-next.3",
"devDependencies": {
"@verdaccio/core": "workspace:6.0.0-6-next.4",
"@verdaccio/ui-theme": "workspace:6.0.0-6-next.14",
"@verdaccio/ui-theme": "workspace:6.0.0-6-next.15",
"debug": "4.3.3",
"kleur": "3.0.3",
"lodash": "4.17.21",

View File

@@ -21,6 +21,11 @@ web:
rateLimit:
windowMs: 50000
max: 1000
pkgManagers:
- npm
- yarn
- pnpm
login: true
```
All access restrictions defined to [protect your packages](protect-your-dependencies.md) will also apply to the Web Interface.
@@ -55,6 +60,8 @@ i18n:
| darkMode | boolean | No | false | `>=v4.6.0` | This mode is an special theme for those want to live in the dark side |
| favicon | string | No | false | `>=v5.0.1` | Display a custom favicon, can be local resource or valid url |
| rateLimit | object | No | use `userRateLimit` configuration | `>=v5.4.0` | Increase or decrease rate limit, by default is 5k request every 2 minutes, only limit web api endpoints, the CSS, JS, etcc are ingnored |
| pkgManagers | npm, pnpm or yarn | false | npm | `>=v5.5.0` | Allow customise which package managers on the side bar and registry information dialog are visible |
| login | boolean | true | true or false | `>=v5.5.0` | Allow disable login on the UI (also include web endpoints). |
> The recommended logo size is `40x40` pixels.