mirror of
https://github.com/verdaccio/verdaccio.git
synced 2024-11-08 23:25:51 +01:00
feat: forbidden user interface (#4523)
* feat: forbidden user interface * Delete App.stories.tsx * Update package.json * Delete package.svg * fix
This commit is contained in:
parent
4a81ed791a
commit
c9962fe1d5
7
.changeset/pink-apples-nail.md
Normal file
7
.changeset/pink-apples-nail.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
'@verdaccio/ui-theme': minor
|
||||||
|
'@verdaccio/ui-components': minor
|
||||||
|
'@verdaccio/config': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: forbidden user interface
|
@ -137,7 +137,7 @@
|
|||||||
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
|
"docker": "docker build -t verdaccio/verdaccio:local . --no-cache",
|
||||||
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
"format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
||||||
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
"format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,yml,yaml,md}\"",
|
||||||
"lint": "eslint --max-warnings 100 \"**/*.{js,jsx,ts,tsx}\"",
|
"lint": "eslint --max-warnings 70 \"**/*.{js,jsx,ts,tsx}\"",
|
||||||
"test": "pnpm --filter \"./packages/**\" test",
|
"test": "pnpm --filter \"./packages/**\" test",
|
||||||
"test:e2e:cli": "pnpm --filter ...@verdaccio/e2e-cli-* test -- --coverage=false",
|
"test:e2e:cli": "pnpm --filter ...@verdaccio/e2e-cli-* test -- --coverage=false",
|
||||||
"test:e2e:ui": "pnpm --filter ...@verdaccio/e2e-ui test",
|
"test:e2e:ui": "pnpm --filter ...@verdaccio/e2e-ui test",
|
||||||
|
@ -10,7 +10,7 @@ auth:
|
|||||||
|
|
||||||
uplinks:
|
uplinks:
|
||||||
ver:
|
ver:
|
||||||
url: https://registry.verdaccio.org
|
url: https://registry.npmjs.org
|
||||||
|
|
||||||
security:
|
security:
|
||||||
api:
|
api:
|
||||||
|
@ -7,7 +7,7 @@ web:
|
|||||||
|
|
||||||
uplinks:
|
uplinks:
|
||||||
ver:
|
ver:
|
||||||
url: https://registry.verdaccio.org
|
url: https://registry.npmjs.org
|
||||||
|
|
||||||
log: { type: stdout, format: pretty, level: trace }
|
log: { type: stdout, format: pretty, level: trace }
|
||||||
|
|
||||||
|
@ -1,9 +1,12 @@
|
|||||||
import assert from 'assert';
|
import assert from 'assert';
|
||||||
|
import buildDebug from 'debug';
|
||||||
import _ from 'lodash';
|
import _ from 'lodash';
|
||||||
|
|
||||||
import { errorUtils } from '@verdaccio/core';
|
import { errorUtils } from '@verdaccio/core';
|
||||||
import { PackageAccess } from '@verdaccio/types';
|
import { PackageAccess } from '@verdaccio/types';
|
||||||
|
|
||||||
|
const debug = buildDebug('verdaccio:config:utils');
|
||||||
|
|
||||||
export interface LegacyPackageList {
|
export interface LegacyPackageList {
|
||||||
[key: string]: PackageAccess;
|
[key: string]: PackageAccess;
|
||||||
}
|
}
|
||||||
@ -61,6 +64,7 @@ export function normalisePackageAccess(packages: LegacyPackageList): LegacyPacka
|
|||||||
for (const pkg in packages) {
|
for (const pkg in packages) {
|
||||||
if (Object.prototype.hasOwnProperty.call(packages, pkg)) {
|
if (Object.prototype.hasOwnProperty.call(packages, pkg)) {
|
||||||
const packageAccess = packages[pkg];
|
const packageAccess = packages[pkg];
|
||||||
|
debug('package access %s for %s ', packageAccess, pkg);
|
||||||
const isInvalid = _.isObject(packageAccess) && _.isArray(packageAccess) === false;
|
const isInvalid = _.isObject(packageAccess) && _.isArray(packageAccess) === false;
|
||||||
assert(isInvalid, `CONFIG: bad "'${pkg}'" package description (object expected)`);
|
assert(isInvalid, `CONFIG: bad "'${pkg}'" package description (object expected)`);
|
||||||
|
|
||||||
|
@ -147,9 +147,11 @@
|
|||||||
"error": {
|
"error": {
|
||||||
"unspecific": "Something went wrong.",
|
"unspecific": "Something went wrong.",
|
||||||
"404": {
|
"404": {
|
||||||
"page-not-found": "404 - Page not found",
|
|
||||||
"sorry-we-could-not-find-it": "Sorry, we couldn't find it..."
|
"sorry-we-could-not-find-it": "Sorry, we couldn't find it..."
|
||||||
},
|
},
|
||||||
|
"401": {
|
||||||
|
"sorry-no-access": "Sorry, you need credentials access to see this page."
|
||||||
|
},
|
||||||
"app-context-not-correct-used": "The app context was not used correctly",
|
"app-context-not-correct-used": "The app context was not used correctly",
|
||||||
"theme-context-not-correct-used": "The theme context was not used correctly",
|
"theme-context-not-correct-used": "The theme context was not used correctly",
|
||||||
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
|
"package-meta-is-required-at-detail-context": "packageMeta is required at DetailContext"
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { useSelector } from 'react-redux';
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
import { Loading, NotFound, RootState, VersionLayout } from '@verdaccio/ui-components';
|
import { Forbidden, Loading, NotFound, RootState, VersionLayout } from '@verdaccio/ui-components';
|
||||||
|
|
||||||
const Version: React.FC = () => {
|
const Version: React.FC = () => {
|
||||||
const manifestStore = useSelector((state: RootState) => state.manifest);
|
const manifestStore = useSelector((state: RootState) => state.manifest);
|
||||||
@ -11,10 +11,13 @@ const Version: React.FC = () => {
|
|||||||
return <Loading />;
|
return <Loading />;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (manifestStore.forbidden) {
|
||||||
|
return <Forbidden />;
|
||||||
|
}
|
||||||
|
|
||||||
if (manifestStore.hasNotBeenFound) {
|
if (manifestStore.hasNotBeenFound) {
|
||||||
return <NotFound />;
|
return <NotFound />;
|
||||||
}
|
}
|
||||||
|
|
||||||
return <VersionLayout />;
|
return <VersionLayout />;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -37,7 +37,7 @@ module.exports = Object.assign({}, config, {
|
|||||||
branches: 70,
|
branches: 70,
|
||||||
functions: 76,
|
functions: 76,
|
||||||
lines: 80,
|
lines: 80,
|
||||||
statements: 82,
|
statements: 81,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
@ -31,6 +31,15 @@ export const handlers = [
|
|||||||
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/jquery', (req, res, ctx) => {
|
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/jquery', (req, res, ctx) => {
|
||||||
return res(ctx.json(require('./api/jquery-sidebar.json')));
|
return res(ctx.json(require('./api/jquery-sidebar.json')));
|
||||||
}),
|
}),
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/JSONStream', (req, res, ctx) => {
|
||||||
|
return res(ctx.status(401));
|
||||||
|
}),
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/semver', (req, res, ctx) => {
|
||||||
|
return res(ctx.status(500));
|
||||||
|
}),
|
||||||
|
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/kleur', (req, res, ctx) => {
|
||||||
|
return res(ctx.status(404));
|
||||||
|
}),
|
||||||
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/glob', (req, res, ctx) => {
|
rest.get('http://localhost:9000/-/verdaccio/data/sidebar/glob', (req, res, ctx) => {
|
||||||
return res(ctx.json(require('./api/glob-sidebar.json')));
|
return res(ctx.json(require('./api/glob-sidebar.json')));
|
||||||
}),
|
}),
|
||||||
|
32
packages/ui-components/src/AppTest/App.stories.tsx
Normal file
32
packages/ui-components/src/AppTest/App.stories.tsx
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter } from 'react-router';
|
||||||
|
|
||||||
|
import AppRoute from './AppRoute';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
title: 'App/Main',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const ApplicationStoryBook: any = () => (
|
||||||
|
<MemoryRouter initialEntries={[`/-/web/detail/storybook`]}>
|
||||||
|
<AppRoute />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ApplicationjQuery: any = () => (
|
||||||
|
<MemoryRouter initialEntries={[`/-/web/detail/jquery`]}>
|
||||||
|
<AppRoute />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ApplicationForbidden: any = () => (
|
||||||
|
<MemoryRouter initialEntries={[`/-/web/detail/JSONStream`]}>
|
||||||
|
<AppRoute />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
export const ApplicationNotFound: any = () => (
|
||||||
|
<MemoryRouter initialEntries={[`/-/web/detail/kleur`]}>
|
||||||
|
<AppRoute />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
42
packages/ui-components/src/AppTest/AppRoute.tsx
Normal file
42
packages/ui-components/src/AppTest/AppRoute.tsx
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { Route as ReactRouterDomRoute, Switch } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { NotFound, Route, VersionProvider, loadable } from '../index';
|
||||||
|
|
||||||
|
const VersionPage = loadable(() => import(/* webpackChunkName: "Version" */ './pages/Version'));
|
||||||
|
const Front = loadable(() => import(/* webpackChunkName: "Home" */ './pages/Front'));
|
||||||
|
|
||||||
|
const AppRoute: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<Switch>
|
||||||
|
<ReactRouterDomRoute exact={true} path={Route.ROOT}>
|
||||||
|
<Front />
|
||||||
|
</ReactRouterDomRoute>
|
||||||
|
<ReactRouterDomRoute exact={true} path={Route.PACKAGE}>
|
||||||
|
<VersionProvider>
|
||||||
|
<VersionPage />
|
||||||
|
</VersionProvider>
|
||||||
|
</ReactRouterDomRoute>
|
||||||
|
<ReactRouterDomRoute exact={true} path={Route.PACKAGE_VERSION}>
|
||||||
|
<VersionProvider>
|
||||||
|
<VersionPage />
|
||||||
|
</VersionProvider>
|
||||||
|
</ReactRouterDomRoute>
|
||||||
|
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE_VERSION}>
|
||||||
|
<VersionProvider>
|
||||||
|
<VersionPage />
|
||||||
|
</VersionProvider>
|
||||||
|
</ReactRouterDomRoute>
|
||||||
|
<ReactRouterDomRoute exact={true} path={Route.SCOPE_PACKAGE}>
|
||||||
|
<VersionProvider>
|
||||||
|
<VersionPage />
|
||||||
|
</VersionProvider>
|
||||||
|
</ReactRouterDomRoute>
|
||||||
|
<ReactRouterDomRoute>
|
||||||
|
<NotFound />
|
||||||
|
</ReactRouterDomRoute>
|
||||||
|
</Switch>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AppRoute;
|
3
packages/ui-components/src/AppTest/pages/Front/Home.ts
Normal file
3
packages/ui-components/src/AppTest/pages/Front/Home.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import { Home } from '../../../index';
|
||||||
|
|
||||||
|
export default Home;
|
1
packages/ui-components/src/AppTest/pages/Front/index.ts
Normal file
1
packages/ui-components/src/AppTest/pages/Front/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './Home';
|
28
packages/ui-components/src/AppTest/pages/Version/Version.tsx
Normal file
28
packages/ui-components/src/AppTest/pages/Version/Version.tsx
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { useSelector } from 'react-redux';
|
||||||
|
|
||||||
|
import Forbidden from '../../../components/Forbidden';
|
||||||
|
import { Loading, NotFound, RootState, VersionLayout } from '../../../index';
|
||||||
|
|
||||||
|
const Version: React.FC = () => {
|
||||||
|
const manifestStore = useSelector((state: RootState) => state.manifest);
|
||||||
|
const isLoading = useSelector((state: RootState) => state?.loading?.models.manifest);
|
||||||
|
|
||||||
|
if (isLoading) {
|
||||||
|
return <Loading />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
if (manifestStore.forbidden) {
|
||||||
|
return <Forbidden />;
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-expect-error
|
||||||
|
if (manifestStore.hasNotBeenFound) {
|
||||||
|
return <NotFound />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <VersionLayout />;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Version;
|
@ -0,0 +1 @@
|
|||||||
|
export { default } from './Version';
|
@ -0,0 +1,38 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import { MemoryRouter } from 'react-router';
|
||||||
|
|
||||||
|
import { fireEvent, render, screen } from '../../test/test-react-testing-library';
|
||||||
|
import Forbidden from './Forbidden';
|
||||||
|
|
||||||
|
const mockHistory = jest.fn();
|
||||||
|
|
||||||
|
jest.mock('react-router-dom', () => ({
|
||||||
|
useHistory: () => ({
|
||||||
|
push: mockHistory,
|
||||||
|
}),
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe('<Forbidden /> component', () => {
|
||||||
|
test('should load the component in default state', () => {
|
||||||
|
render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<Forbidden />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
expect(screen.getByTestId('LockIcon')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('error.401.sorry-no-access')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button.go-to-the-home-page')).toBeInTheDocument();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('go to Home Page button click', async () => {
|
||||||
|
const { getByTestId } = render(
|
||||||
|
<MemoryRouter>
|
||||||
|
<Forbidden />
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
|
||||||
|
const node = getByTestId('not-found-go-to-home-button');
|
||||||
|
fireEvent.click(node);
|
||||||
|
expect(mockHistory).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,56 @@
|
|||||||
|
/* eslint-disable react/forbid-component-props */
|
||||||
|
|
||||||
|
/* eslint-disable verdaccio/jsx-no-style */
|
||||||
|
import styled from '@emotion/styled';
|
||||||
|
import LockIcon from '@mui/icons-material/Lock';
|
||||||
|
import Box from '@mui/material/Box';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import React, { useCallback } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useHistory } from 'react-router-dom';
|
||||||
|
|
||||||
|
import { Theme } from '../..';
|
||||||
|
import Heading from '../Heading';
|
||||||
|
|
||||||
|
const Foebidden: React.FC = () => {
|
||||||
|
const history = useHistory();
|
||||||
|
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
const handleGoHome = useCallback(() => {
|
||||||
|
history.push('/');
|
||||||
|
}, [history]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Box
|
||||||
|
alignItems="center"
|
||||||
|
data-testid="404"
|
||||||
|
display="flex"
|
||||||
|
flexDirection="column"
|
||||||
|
flexGrow={1}
|
||||||
|
justifyContent="center"
|
||||||
|
p={2}
|
||||||
|
>
|
||||||
|
<Container>
|
||||||
|
<LockIcon color="primary" style={{ fontSize: 236 }} />
|
||||||
|
</Container>
|
||||||
|
<StyledHeading className="not-found-text" variant="h4">
|
||||||
|
{t('error.401.sorry-no-access')}
|
||||||
|
</StyledHeading>
|
||||||
|
<Button data-testid="not-found-go-to-home-button" onClick={handleGoHome} variant="contained">
|
||||||
|
{t('button.go-to-the-home-page')}
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Foebidden;
|
||||||
|
|
||||||
|
const Container = styled('div')({
|
||||||
|
margin: '0 auto',
|
||||||
|
});
|
||||||
|
|
||||||
|
const StyledHeading = styled(Heading)<{ theme?: Theme }>(({ theme }) => ({
|
||||||
|
color: theme?.palette.mode === 'light' ? theme?.palette.primary.main : theme?.palette.white,
|
||||||
|
marginBottom: 16,
|
||||||
|
}));
|
1
packages/ui-components/src/components/Forbidden/index.ts
Normal file
1
packages/ui-components/src/components/Forbidden/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export { default } from './Forbidden';
|
@ -1,4 +1,6 @@
|
|||||||
|
/* eslint-disable verdaccio/jsx-no-style */
|
||||||
import styled from '@emotion/styled';
|
import styled from '@emotion/styled';
|
||||||
|
import FolderOffIcon from '@mui/icons-material/FolderOff';
|
||||||
import Box from '@mui/material/Box';
|
import Box from '@mui/material/Box';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import React, { useCallback } from 'react';
|
import React, { useCallback } from 'react';
|
||||||
@ -7,8 +9,6 @@ import { useHistory } from 'react-router-dom';
|
|||||||
|
|
||||||
import { Theme } from '../../';
|
import { Theme } from '../../';
|
||||||
import Heading from '../Heading';
|
import Heading from '../Heading';
|
||||||
// @ts-ignore
|
|
||||||
import PackageImg from './img/package.svg';
|
|
||||||
|
|
||||||
const NotFound: React.FC = () => {
|
const NotFound: React.FC = () => {
|
||||||
const history = useHistory();
|
const history = useHistory();
|
||||||
@ -28,7 +28,9 @@ const NotFound: React.FC = () => {
|
|||||||
justifyContent="center"
|
justifyContent="center"
|
||||||
p={2}
|
p={2}
|
||||||
>
|
>
|
||||||
<EmptyPackage alt={t('error.404.page-not-found')} src={PackageImg} />
|
<Container>
|
||||||
|
<FolderOffIcon color="primary" style={{ fontSize: 236 }} />
|
||||||
|
</Container>
|
||||||
<StyledHeading className="not-found-text" variant="h4">
|
<StyledHeading className="not-found-text" variant="h4">
|
||||||
{t('error.404.sorry-we-could-not-find-it')}
|
{t('error.404.sorry-we-could-not-find-it')}
|
||||||
</StyledHeading>
|
</StyledHeading>
|
||||||
@ -41,8 +43,7 @@ const NotFound: React.FC = () => {
|
|||||||
|
|
||||||
export default NotFound;
|
export default NotFound;
|
||||||
|
|
||||||
const EmptyPackage = styled('img')({
|
const Container = styled('div')({
|
||||||
width: '150px',
|
|
||||||
margin: '0 auto',
|
margin: '0 auto',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { MemoryRouter } from 'react-router';
|
import { MemoryRouter } from 'react-router';
|
||||||
|
|
||||||
import { fireEvent, render } from '../../test/test-react-testing-library';
|
import { fireEvent, render, screen } from '../../test/test-react-testing-library';
|
||||||
import NotFound from './NotFound';
|
import NotFound from './NotFound';
|
||||||
|
|
||||||
const mockHistory = jest.fn();
|
const mockHistory = jest.fn();
|
||||||
@ -14,12 +14,15 @@ jest.mock('react-router-dom', () => ({
|
|||||||
|
|
||||||
describe('<NotFound /> component', () => {
|
describe('<NotFound /> component', () => {
|
||||||
test('should load the component in default state', () => {
|
test('should load the component in default state', () => {
|
||||||
const { container } = render(
|
render(
|
||||||
<MemoryRouter>
|
<MemoryRouter>
|
||||||
<NotFound />
|
<NotFound />
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
expect(container.firstChild).toMatchSnapshot();
|
|
||||||
|
expect(screen.getByTestId('FolderOffIcon')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('button.go-to-the-home-page')).toBeInTheDocument();
|
||||||
|
expect(screen.getByText('error.404.sorry-we-could-not-find-it')).toBeInTheDocument();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('go to Home Page button click', async () => {
|
test('go to Home Page button click', async () => {
|
||||||
|
@ -1,171 +0,0 @@
|
|||||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
|
||||||
|
|
||||||
exports[`<NotFound /> component should load the component in default state 1`] = `
|
|
||||||
.emotion-0 {
|
|
||||||
-webkit-align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
|
||||||
display: -webkit-box;
|
|
||||||
display: -webkit-flex;
|
|
||||||
display: -ms-flexbox;
|
|
||||||
display: flex;
|
|
||||||
-webkit-flex-direction: column;
|
|
||||||
-ms-flex-direction: column;
|
|
||||||
flex-direction: column;
|
|
||||||
-webkit-box-flex: 1;
|
|
||||||
-webkit-flex-grow: 1;
|
|
||||||
-ms-flex-positive: 1;
|
|
||||||
flex-grow: 1;
|
|
||||||
-webkit-box-pack: center;
|
|
||||||
-ms-flex-pack: center;
|
|
||||||
-webkit-justify-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-1 {
|
|
||||||
width: 150px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-4 {
|
|
||||||
margin: 0;
|
|
||||||
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
|
|
||||||
font-weight: 400;
|
|
||||||
font-size: 2.125rem;
|
|
||||||
line-height: 1.235;
|
|
||||||
color: #4b5e40;
|
|
||||||
margin-bottom: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-5 {
|
|
||||||
display: -webkit-inline-box;
|
|
||||||
display: -webkit-inline-flex;
|
|
||||||
display: -ms-inline-flexbox;
|
|
||||||
display: inline-flex;
|
|
||||||
-webkit-align-items: center;
|
|
||||||
-webkit-box-align: center;
|
|
||||||
-ms-flex-align: center;
|
|
||||||
align-items: center;
|
|
||||||
-webkit-box-pack: center;
|
|
||||||
-ms-flex-pack: center;
|
|
||||||
-webkit-justify-content: center;
|
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
box-sizing: border-box;
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
background-color: transparent;
|
|
||||||
outline: 0;
|
|
||||||
border: 0;
|
|
||||||
margin: 0;
|
|
||||||
border-radius: 0;
|
|
||||||
padding: 0;
|
|
||||||
cursor: pointer;
|
|
||||||
-webkit-user-select: none;
|
|
||||||
-moz-user-select: none;
|
|
||||||
-ms-user-select: none;
|
|
||||||
user-select: none;
|
|
||||||
vertical-align: middle;
|
|
||||||
-moz-appearance: none;
|
|
||||||
-webkit-appearance: none;
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
color: inherit;
|
|
||||||
font-family: -apple-system,BlinkMacSystemFont,"Helvetica Neue",Arial,sans-serif;
|
|
||||||
font-weight: 500;
|
|
||||||
font-size: 0.875rem;
|
|
||||||
line-height: 1.75;
|
|
||||||
text-transform: uppercase;
|
|
||||||
min-width: 64px;
|
|
||||||
padding: 6px 16px;
|
|
||||||
border-radius: 4px;
|
|
||||||
-webkit-transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
|
||||||
transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,border-color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms,color 250ms cubic-bezier(0.4, 0, 0.2, 1) 0ms;
|
|
||||||
color: #fff;
|
|
||||||
background-color: #4b5e40;
|
|
||||||
box-shadow: 0px 3px 1px -2px rgba(0,0,0,0.2),0px 2px 2px 0px rgba(0,0,0,0.14),0px 1px 5px 0px rgba(0,0,0,0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-5::-moz-focus-inner {
|
|
||||||
border-style: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-5.Mui-disabled {
|
|
||||||
pointer-events: none;
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media print {
|
|
||||||
.emotion-5 {
|
|
||||||
-webkit-print-color-adjust: exact;
|
|
||||||
color-adjust: exact;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-5:hover {
|
|
||||||
-webkit-text-decoration: none;
|
|
||||||
text-decoration: none;
|
|
||||||
background-color: rgb(52, 65, 44);
|
|
||||||
box-shadow: 0px 2px 4px -1px rgba(0,0,0,0.2),0px 4px 5px 0px rgba(0,0,0,0.14),0px 1px 10px 0px rgba(0,0,0,0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (hover: none) {
|
|
||||||
.emotion-5:hover {
|
|
||||||
background-color: #4b5e40;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-5:active {
|
|
||||||
box-shadow: 0px 5px 5px -3px rgba(0,0,0,0.2),0px 8px 10px 1px rgba(0,0,0,0.14),0px 3px 14px 2px rgba(0,0,0,0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-5.Mui-focusVisible {
|
|
||||||
box-shadow: 0px 3px 5px -1px rgba(0,0,0,0.2),0px 6px 10px 0px rgba(0,0,0,0.14),0px 1px 18px 0px rgba(0,0,0,0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-5.Mui-disabled {
|
|
||||||
color: rgba(0, 0, 0, 0.26);
|
|
||||||
box-shadow: none;
|
|
||||||
background-color: rgba(0, 0, 0, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.emotion-6 {
|
|
||||||
overflow: hidden;
|
|
||||||
pointer-events: none;
|
|
||||||
position: absolute;
|
|
||||||
z-index: 0;
|
|
||||||
top: 0;
|
|
||||||
right: 0;
|
|
||||||
bottom: 0;
|
|
||||||
left: 0;
|
|
||||||
border-radius: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
<div
|
|
||||||
class="MuiBox-root emotion-0"
|
|
||||||
data-testid="404"
|
|
||||||
>
|
|
||||||
<img
|
|
||||||
alt="error.404.page-not-found"
|
|
||||||
class="emotion-1 emotion-2"
|
|
||||||
src="[object Object]"
|
|
||||||
/>
|
|
||||||
<h4
|
|
||||||
class="MuiTypography-root MuiTypography-h4 not-found-text emotion-3 emotion-4"
|
|
||||||
>
|
|
||||||
error.404.sorry-we-could-not-find-it
|
|
||||||
</h4>
|
|
||||||
<button
|
|
||||||
class="MuiButtonBase-root MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium MuiButton-root MuiButton-contained MuiButton-containedPrimary MuiButton-sizeMedium MuiButton-containedSizeMedium emotion-5"
|
|
||||||
data-testid="not-found-go-to-home-button"
|
|
||||||
tabindex="0"
|
|
||||||
type="button"
|
|
||||||
>
|
|
||||||
button.go-to-the-home-page
|
|
||||||
<span
|
|
||||||
class="MuiTouchRipple-root emotion-6"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
`;
|
|
@ -22,6 +22,7 @@ export { default as Label } from './components/Label';
|
|||||||
export { default as Logo } from './components/Logo';
|
export { default as Logo } from './components/Logo';
|
||||||
export { default as MenuItem } from './components/MenuItem';
|
export { default as MenuItem } from './components/MenuItem';
|
||||||
export { default as NotFound } from './components/NotFound';
|
export { default as NotFound } from './components/NotFound';
|
||||||
|
export { default as Forbidden } from './components/Forbidden';
|
||||||
export { default as LoginDialog } from './components/LoginDialog';
|
export { default as LoginDialog } from './components/LoginDialog';
|
||||||
export { default as Search } from './components/Search';
|
export { default as Search } from './components/Search';
|
||||||
export { default as HeaderInfoDialog } from './components/HeaderInfoDialog';
|
export { default as HeaderInfoDialog } from './components/HeaderInfoDialog';
|
||||||
|
@ -27,3 +27,13 @@ export const DetailJquery: any = () => (
|
|||||||
</Route>
|
</Route>
|
||||||
</MemoryRouter>
|
</MemoryRouter>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const DetailForbidden: any = () => (
|
||||||
|
<MemoryRouter initialEntries={[`/-/web/detail/JSONStream`]}>
|
||||||
|
<Route exact={true} path="/-/web/detail/:package">
|
||||||
|
<VersionProvider>
|
||||||
|
<Detail />
|
||||||
|
</VersionProvider>
|
||||||
|
</Route>
|
||||||
|
</MemoryRouter>
|
||||||
|
);
|
||||||
|
@ -14,7 +14,8 @@ describe('api', () => {
|
|||||||
|
|
||||||
const handled = await handleResponseType(response);
|
const handled = await handleResponseType(response);
|
||||||
|
|
||||||
expect(handled).toEqual([ok, responseText]);
|
expect(handled[0]).toBeFalsy();
|
||||||
|
expect(handled[1]).toBeDefined();
|
||||||
});
|
});
|
||||||
|
|
||||||
test('should test tgz scenario', async () => {
|
test('should test tgz scenario', async () => {
|
||||||
@ -123,7 +124,7 @@ describe('api', () => {
|
|||||||
})
|
})
|
||||||
);
|
);
|
||||||
|
|
||||||
await expect(api.request('/resource')).rejects.toThrow(new Error('something went wrong'));
|
await expect(api.request('/resource')).rejects.toThrow(new Error('Unknown error'));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('when api returns an error 5.x.x', async () => {
|
test('when api returns an error 5.x.x', async () => {
|
||||||
|
@ -1,5 +1,10 @@
|
|||||||
import storage from './storage';
|
import storage from './storage';
|
||||||
|
|
||||||
|
class CustomError extends Error {
|
||||||
|
// @ts-ignore
|
||||||
|
code: number;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles response according to content type
|
* Handles response according to content type
|
||||||
* @param {object} response
|
* @param {object} response
|
||||||
@ -25,7 +30,8 @@ export function handleResponseType(response: Response): Promise<[boolean, any]>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.all([response.ok, response.text()]);
|
// error handling
|
||||||
|
return Promise.all([response.ok, response]);
|
||||||
}
|
}
|
||||||
|
|
||||||
const AuthHeader = 'Authorization';
|
const AuthHeader = 'Authorization';
|
||||||
@ -55,12 +61,13 @@ class API {
|
|||||||
})
|
})
|
||||||
.then(handleResponseType)
|
.then(handleResponseType)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
if (response[0]) {
|
const [ok, data] = response;
|
||||||
resolve(response[1]);
|
if (ok === true) {
|
||||||
|
resolve(data);
|
||||||
} else {
|
} else {
|
||||||
// eslint-disable-next-line no-console
|
const error = new CustomError(data?.statusText ?? 'Unknown error');
|
||||||
console.error(response);
|
error.code = data?.status ?? 500;
|
||||||
reject(new Error('something went wrong'));
|
reject(error);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -33,6 +33,18 @@ export const manifest = createModel<RootModel>()({
|
|||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
hasNotBeenFound: true,
|
hasNotBeenFound: true,
|
||||||
|
forbidden: false,
|
||||||
|
manifest: undefined,
|
||||||
|
packageName: undefined,
|
||||||
|
packageVersion: undefined,
|
||||||
|
readme: undefined,
|
||||||
|
};
|
||||||
|
},
|
||||||
|
forbidden(state) {
|
||||||
|
return {
|
||||||
|
...state,
|
||||||
|
forbidden: true,
|
||||||
|
hasNotBeenFound: false,
|
||||||
manifest: undefined,
|
manifest: undefined,
|
||||||
packageName: undefined,
|
packageName: undefined,
|
||||||
packageVersion: undefined,
|
packageVersion: undefined,
|
||||||
@ -50,6 +62,7 @@ export const manifest = createModel<RootModel>()({
|
|||||||
...state,
|
...state,
|
||||||
isError: true,
|
isError: true,
|
||||||
hasNotBeenFound: false,
|
hasNotBeenFound: false,
|
||||||
|
forbidden: false,
|
||||||
manifest: undefined,
|
manifest: undefined,
|
||||||
packageName: undefined,
|
packageName: undefined,
|
||||||
packageVersion: undefined,
|
packageVersion: undefined,
|
||||||
@ -64,6 +77,7 @@ export const manifest = createModel<RootModel>()({
|
|||||||
packageVersion,
|
packageVersion,
|
||||||
readme,
|
readme,
|
||||||
hasNotBeenFound: false,
|
hasNotBeenFound: false,
|
||||||
|
forbidden: false,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
@ -91,7 +105,11 @@ export const manifest = createModel<RootModel>()({
|
|||||||
);
|
);
|
||||||
dispatch.manifest.saveManifest({ packageName, packageVersion, manifest, readme });
|
dispatch.manifest.saveManifest({ packageName, packageVersion, manifest, readme });
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
|
if (error.code === 404) {
|
||||||
dispatch.manifest.notFound();
|
dispatch.manifest.notFound();
|
||||||
|
} else {
|
||||||
|
dispatch.manifest.forbidden();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}),
|
}),
|
||||||
|
Loading…
Reference in New Issue
Block a user