core-components: added new LogViewer component
Signed-off-by: Patrik Oldsberg <poldsberg@gmail.com>
This commit is contained in:
@@ -62,6 +62,8 @@
|
||||
"react-syntax-highlighter": "^15.4.3",
|
||||
"react-text-truncate": "^0.16.0",
|
||||
"react-use": "^17.2.4",
|
||||
"react-virtualized-auto-sizer": "^1.0.6",
|
||||
"react-window": "^1.8.6",
|
||||
"remark-gfm": "^2.0.0",
|
||||
"zen-observable": "^0.8.15"
|
||||
},
|
||||
@@ -89,6 +91,8 @@
|
||||
"@types/node": "^14.14.32",
|
||||
"@types/react-helmet": "^6.1.0",
|
||||
"@types/react-syntax-highlighter": "^13.5.2",
|
||||
"@types/react-virtualized-auto-sizer": "^1.0.1",
|
||||
"@types/react-window": "^1.8.5",
|
||||
"@types/zen-observable": "^0.8.0"
|
||||
},
|
||||
"files": [
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2020 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import React from 'react';
|
||||
import { LogViewer } from './LogViewer';
|
||||
|
||||
export default {
|
||||
title: 'Data Display/LogViewer',
|
||||
component: LogViewer,
|
||||
};
|
||||
|
||||
const exampleLog = `Starting up task with 3 steps
|
||||
Beginning step Fetch Skeleton + Template
|
||||
[32minfo[39m: Fetching template content from remote URL {"timestamp":"2021-12-03T15:47:11.625Z"}
|
||||
[32minfo[39m: Listing files and directories in template {"timestamp":"2021-12-03T15:47:12.797Z"}
|
||||
[32minfo[39m: Processing 33 template files/directories with input values {"component_id":"srnthsrthntrhsn","description":"rnthsrtnhssrthnrsthn","destination":{"host":"github.com","owner":"rtshnsrtmhrstmh","repo":"srtmhsrtmhrsthms"},"owner":"rstnhrstnhsrthn","timestamp":"2021-12-03T15:47:12.801Z"}
|
||||
[32minfo[39m: Writing file .editorconfig to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.816Z"}
|
||||
[32minfo[39m: Writing file .eslintignore to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.818Z"}
|
||||
[32minfo[39m: Writing file .eslintrc.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.820Z"}
|
||||
[32minfo[39m: Writing directory .github/ to template output path. {"timestamp":"2021-12-03T15:47:12.823Z"}
|
||||
[32minfo[39m: Writing file .gitignore to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.824Z"}
|
||||
[32minfo[39m: Writing file README.md to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.827Z"}
|
||||
[32minfo[39m: Writing file babel.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.829Z"}
|
||||
[32minfo[39m: Writing file catalog-info.yaml to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.831Z"}
|
||||
[32minfo[39m: Writing directory docs/ to template output path. {"timestamp":"2021-12-03T15:47:12.834Z"}
|
||||
[32minfo[39m: Writing file jest.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.836Z"}
|
||||
[32minfo[39m: Writing file mkdocs.yml to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.838Z"}
|
||||
[32minfo[39m: Writing file next-env.d.ts to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.841Z"}
|
||||
[32minfo[39m: Writing file next.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.844Z"}
|
||||
[32minfo[39m: Writing file package.json to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.845Z"}
|
||||
[32minfo[39m: Writing file prettier.config.js to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.848Z"}
|
||||
[32minfo[39m: Writing directory public/ to template output path. {"timestamp":"2021-12-03T15:47:12.849Z"}
|
||||
[32minfo[39m: Writing directory src/ to template output path. {"timestamp":"2021-12-03T15:47:12.850Z"}
|
||||
[32minfo[39m: Writing file tsconfig.json to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.851Z"}
|
||||
[32minfo[39m: Writing directory .github/workflows/ to template output path. {"timestamp":"2021-12-03T15:47:12.853Z"}
|
||||
[32minfo[39m: Writing file docs/index.md to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.854Z"}
|
||||
[32minfo[39m: Writing directory public/static/ to template output path. {"timestamp":"2021-12-03T15:47:12.857Z"}
|
||||
[32minfo[39m: Writing directory src/__tests__/ to template output path. {"timestamp":"2021-12-03T15:47:12.858Z"}
|
||||
[32minfo[39m: Writing directory src/components/ to template output path. {"timestamp":"2021-12-03T15:47:12.858Z"}
|
||||
[32minfo[39m: Writing directory src/pages/ to template output path. {"timestamp":"2021-12-03T15:47:12.859Z"}
|
||||
[32minfo[39m: Copying file/directory .github/workflows/build.yml without processing. {"timestamp":"2021-12-03T15:47:12.859Z"}
|
||||
[32minfo[39m: Writing file .github/workflows/build.yml to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.860Z"}
|
||||
[32minfo[39m: Writing file public/static/fonts.css to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.861Z"}
|
||||
[32minfo[39m: Writing file src/components/Header.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.863Z"}
|
||||
[32minfo[39m: Writing file src/__tests__/index.test.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.865Z"}
|
||||
[32minfo[39m: Writing file src/pages/_app.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.868Z"}
|
||||
[32minfo[39m: Writing file src/pages/_document.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.871Z"}
|
||||
[32minfo[39m: Writing directory src/pages/api/ to template output path. {"timestamp":"2021-12-03T15:47:12.873Z"}
|
||||
[32minfo[39m: Writing file src/pages/index.tsx to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.874Z"}
|
||||
[32minfo[39m: Writing file src/pages/api/ping.ts to template output path with mode 33188. {"timestamp":"2021-12-03T15:47:12.877Z"}
|
||||
[32minfo[39m: Template result written to /var/folders/k6/9s7hd6w17115xlgwnsp0wsbr0000gn/T/5c9f8584-fded-4741-b6ef-46d94ff2cbdb {"timestamp":"2021-12-03T15:47:12.878Z"}
|
||||
Finished step Fetch Skeleton + Template
|
||||
Beginning step Publish
|
||||
HttpError: Not Found
|
||||
at /Users/patriko/dev/backstage/node_modules/@octokit/request/dist-node/index.js:86:21
|
||||
at runMicrotasks (<anonymous>)
|
||||
at processTicksAndRejections (internal/process/task_queues.js:95:5)
|
||||
at async Object.handler (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/actions/builtin/publish/github.ts:156:20)
|
||||
at async HandlebarsWorkflowRunner.execute (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/tasks/HandlebarsWorkflowRunner.ts:254:11)
|
||||
at async TaskWorker.runOneTask (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts:110:13)
|
||||
at async eval (webpack-internal:///../../plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts:100:9)
|
||||
Run completed with status: failed`;
|
||||
|
||||
export const ExampleLogViewer = () => (
|
||||
<div style={{ height: 240 }}>
|
||||
<LogViewer text={exampleLog} />
|
||||
</div>
|
||||
);
|
||||
@@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { makeStyles } from '@material-ui/core/styles';
|
||||
import React, { useMemo } from 'react';
|
||||
import AutoSizer from 'react-virtualized-auto-sizer';
|
||||
import { FixedSizeList } from 'react-window';
|
||||
import { AnsiProcessor } from './AnsiProcessor';
|
||||
import startCase from 'lodash/startCase';
|
||||
import * as colors from '@material-ui/core/colors';
|
||||
|
||||
export interface LogViewerProps {
|
||||
text: string;
|
||||
noLineNumbers?: boolean;
|
||||
}
|
||||
|
||||
export type AnsiColor =
|
||||
| 'black'
|
||||
| 'red'
|
||||
| 'green'
|
||||
| 'yellow'
|
||||
| 'blue'
|
||||
| 'magenta'
|
||||
| 'cyan'
|
||||
| 'white'
|
||||
| 'grey';
|
||||
|
||||
export interface ChunkModifiers {
|
||||
foreground?: AnsiColor;
|
||||
background?: AnsiColor;
|
||||
bold?: boolean;
|
||||
italic?: boolean;
|
||||
underline?: boolean;
|
||||
}
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
root: {
|
||||
fontFamily: '"Monaco", monospace',
|
||||
fontSize: theme.typography.fontSize,
|
||||
background: theme.palette.background.paper,
|
||||
},
|
||||
line: {
|
||||
whiteSpace: 'pre',
|
||||
},
|
||||
lineNumber: {
|
||||
display: 'inline-block',
|
||||
textAlign: 'end',
|
||||
width: 60,
|
||||
marginRight: theme.spacing(1),
|
||||
},
|
||||
modifierBold: {
|
||||
fontWeight: theme.typography.fontWeightBold,
|
||||
},
|
||||
modifierItalic: {
|
||||
fontStyle: 'italic',
|
||||
},
|
||||
modifierUnderline: {
|
||||
textDecoration: 'underline',
|
||||
},
|
||||
modifierForegroundBlack: {
|
||||
color: colors.common.black,
|
||||
},
|
||||
modifierForegroundRed: {
|
||||
color: colors.red[500],
|
||||
},
|
||||
modifierForegroundGreen: {
|
||||
color: colors.green[500],
|
||||
},
|
||||
modifierForegroundYellow: {
|
||||
color: colors.yellow[500],
|
||||
},
|
||||
modifierForegroundBlue: {
|
||||
color: colors.blue[500],
|
||||
},
|
||||
modifierForegroundMagenta: {
|
||||
color: colors.purple[500],
|
||||
},
|
||||
modifierForegroundCyan: {
|
||||
color: colors.cyan[500],
|
||||
},
|
||||
modifierForegroundWhite: {
|
||||
color: colors.common.white,
|
||||
},
|
||||
modifierForegroundGrey: {
|
||||
color: colors.grey[500],
|
||||
},
|
||||
modifierBackgroundBlack: {
|
||||
color: colors.common.black,
|
||||
},
|
||||
modifierBackgroundRed: {
|
||||
color: colors.red[500],
|
||||
},
|
||||
modifierBackgroundGreen: {
|
||||
color: colors.green[500],
|
||||
},
|
||||
modifierBackgroundYellow: {
|
||||
color: colors.yellow[500],
|
||||
},
|
||||
modifierBackgroundBlue: {
|
||||
color: colors.blue[500],
|
||||
},
|
||||
modifierBackgroundMagenta: {
|
||||
color: colors.purple[500],
|
||||
},
|
||||
modifierBackgroundCyan: {
|
||||
color: colors.cyan[500],
|
||||
},
|
||||
modifierBackgroundWhite: {
|
||||
color: colors.common.white,
|
||||
},
|
||||
modifierBackgroundGrey: {
|
||||
color: colors.grey[500],
|
||||
},
|
||||
}));
|
||||
|
||||
function getModifierClasses(
|
||||
classes: ReturnType<typeof useStyles>,
|
||||
modifiers: ChunkModifiers,
|
||||
) {
|
||||
const classNames = new Array<string>();
|
||||
if (modifiers.bold) {
|
||||
classNames.push(classes.modifierBold);
|
||||
}
|
||||
if (modifiers.italic) {
|
||||
classNames.push(classes.modifierItalic);
|
||||
}
|
||||
if (modifiers.underline) {
|
||||
classNames.push(classes.modifierUnderline);
|
||||
}
|
||||
if (modifiers.foreground) {
|
||||
const key = `modifierForeground${startCase(
|
||||
modifiers.foreground,
|
||||
)}` as keyof typeof classes;
|
||||
classNames.push(classes[key]);
|
||||
}
|
||||
if (modifiers.background) {
|
||||
const key = `modifierBackground${startCase(
|
||||
modifiers.background,
|
||||
)}` as keyof typeof classes;
|
||||
classNames.push(classes[key]);
|
||||
}
|
||||
return classNames.join(' ');
|
||||
}
|
||||
|
||||
export function LogViewer(props: LogViewerProps) {
|
||||
const { noLineNumbers } = props;
|
||||
const classes = useStyles();
|
||||
|
||||
// The processor keeps state that optimizes appending to the text
|
||||
const processor = useMemo(() => new AnsiProcessor(), []);
|
||||
const lines = processor.process(props.text);
|
||||
|
||||
return (
|
||||
<AutoSizer>
|
||||
{({ height, width }) => (
|
||||
<FixedSizeList
|
||||
className={classes.root}
|
||||
height={height}
|
||||
width={width}
|
||||
itemData={lines}
|
||||
itemSize={20}
|
||||
itemCount={lines.length}
|
||||
>
|
||||
{({ index, style, data }) => (
|
||||
<div style={{ ...style }} className={classes.line}>
|
||||
{!noLineNumbers && (
|
||||
<span className={classes.lineNumber}>{index + 1}</span>
|
||||
)}
|
||||
{data[index].map(({ text, modifiers }, i) => (
|
||||
<span
|
||||
key={i}
|
||||
className={getModifierClasses(classes, modifiers)}
|
||||
>
|
||||
{text}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
)}
|
||||
</FixedSizeList>
|
||||
)}
|
||||
</AutoSizer>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
/*
|
||||
* Copyright 2021 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
export { LogViewer } from './LogViewer';
|
||||
export type { LogViewerProps } from './LogViewer';
|
||||
@@ -30,6 +30,7 @@ export * from './HeaderIconLinkRow';
|
||||
export * from './HorizontalScrollGrid';
|
||||
export * from './Lifecycle';
|
||||
export * from './Link';
|
||||
export * from './LogViewer';
|
||||
export * from './MarkdownContent';
|
||||
export * from './OAuthRequestDialog';
|
||||
export * from './OverflowTooltip';
|
||||
|
||||
@@ -6974,13 +6974,6 @@
|
||||
dependencies:
|
||||
"@types/node" "*"
|
||||
|
||||
"@types/ansi-regex@^5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.npmjs.org/@types/ansi-regex/-/ansi-regex-5.0.0.tgz#569a5189a92cc46d63fb2ad91e6b130f33d999c1"
|
||||
integrity sha512-SQafVL3pXFh/5qq/nN6p5858g//zSVzcb8JzCLtoVxm8YNPggMQfEIm7aaTNysxpw1S+lFTaW8kv+aR0/CEhCA==
|
||||
dependencies:
|
||||
ansi-regex "*"
|
||||
|
||||
"@types/archiver@^5.1.0":
|
||||
version "5.3.0"
|
||||
resolved "https://registry.npmjs.org/@types/archiver/-/archiver-5.3.0.tgz#2b34ba56d4d7102d256b922c7e91e09eab79db6f"
|
||||
@@ -9242,11 +9235,6 @@ ansi-html@0.0.7, ansi-html@^0.0.7:
|
||||
resolved "https://registry.npmjs.org/ansi-html/-/ansi-html-0.0.7.tgz#813584021962a9e9e6fd039f940d12f56ca7859e"
|
||||
integrity sha1-gTWEAhliqenm/QOflA0S9WynhZ4=
|
||||
|
||||
ansi-regex@*, ansi-regex@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
|
||||
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
|
||||
|
||||
ansi-regex@^2.0.0:
|
||||
version "2.1.1"
|
||||
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
|
||||
@@ -9267,6 +9255,11 @@ ansi-regex@^5.0.0, ansi-regex@^5.0.1:
|
||||
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
|
||||
|
||||
ansi-regex@^6.0.1:
|
||||
version "6.0.1"
|
||||
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a"
|
||||
integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==
|
||||
|
||||
ansi-styles@^2.2.1:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz#b432dd3358b634cf75e1e4664368240533c1ddbe"
|
||||
|
||||
Reference in New Issue
Block a user