Merge pull request #6882 from nwind/feat-log-ansi-color

feat: log 组件支持简单 ansi 颜色,可配置 credentials 设置 Closes #6039
This commit is contained in:
hsm-lv 2023-05-15 19:57:19 +08:00 committed by GitHub
commit 6b978cc2dc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 92 additions and 25 deletions

View File

@ -93,7 +93,6 @@ public class StreamingResponseBodyController {
2. 设置 `maxLength`,限制最大显示行数
- 优点:某一行日志很长的时候会自动折行
- 缺点:无法查看之前的日志
3. 试试通过 `"disableColor": false` 关闭 ANSI 颜色
## 自动滚动到底部
@ -160,14 +159,16 @@ public class StreamingResponseBodyController {
## 属性表
| 属性名 | 类型 | 默认值 | 说明 |
| ----------- | --------- | ------ | -------------------------------------------------------- |
| height | `number` | 500 | 展示区域高度 |
| className | `string` | | 外层 CSS 类名 |
| autoScroll | `boolean` | true | 是否自动滚动 |
| placeholder | `string` | | 加载中的文字 |
| encoding | `string` | utf-8 | 返回内容的字符编码 |
| source | `string` | | 接口 |
| rowHeight | `number` | | 设置每行高度,将会开启虚拟渲染 |
| maxLength | `number` | | 最大显示行数 |
| operation | `Array` | | 可选日志操作:['stop','restart',clear','showLineNumber','filter'] |
| 属性名 | 类型 | 默认值 | 说明 |
| ------------ | --------- | --------- | ----------------------------------------------------------------- |
| height | `number` | 500 | 展示区域高度 |
| className | `string` | | 外层 CSS 类名 |
| autoScroll | `boolean` | true | 是否自动滚动 |
| disableColor | `boolean` | false | 是否禁用 ansi 颜色支持 |
| placeholder | `string` | | 加载中的文字 |
| encoding | `string` | utf-8 | 返回内容的字符编码 |
| source | `string` | | 接口 |
| credentials | `string` | 'include' | fetch 的 credentials 设置 |
| rowHeight | `number` | | 设置每行高度,将会开启虚拟渲染 |
| maxLength | `number` | | 最大显示行数 |
| operation | `Array` | | 可选日志操作:['stop','restart',clear','showLineNumber','filter'] |

View File

@ -6,6 +6,29 @@ import {buildApi, isApiOutdated, Renderer, RendererProps} from 'amis-core';
import {BaseSchema} from '../Schema';
import {Icon, SearchBox, VirtualList} from 'amis-ui';
const foregroundColors = {
'30': 'black',
'31': 'red',
'32': 'green',
'33': 'yellow',
'34': 'blue',
'35': 'magenta',
'36': 'cyan',
'37': 'white',
'90': 'grey'
} as {[key: string]: string};
const backgroundColors = {
'40': 'black',
'41': 'red',
'42': 'green',
'43': 'yellow',
'44': 'blue',
'45': 'magenta',
'46': 'cyan',
'47': 'white'
} as {[key: string]: string};
export type LogOperation =
| 'stop'
| 'restart'
@ -62,6 +85,11 @@ export interface LogSchema extends BaseSchema {
*
*/
operation?: Array<LogOperation>;
/**
* credentials
*/
credentials?: string;
}
export interface LogProps
@ -179,14 +207,10 @@ export class Log extends React.Component<LogProps, LogState> {
filterWord = (logs: string[], lastLine: string, word: string) => {
let originLogs = logs;
let originLastLine = lastLine;
if (
word !== '' &&
word !== undefined &&
word !== null &&
word.length > 0) {
if (word !== '' && word !== undefined && word !== null && word.length > 0) {
logs = logs.filter(line => line.includes(word));
if (!lastLine.includes(word)) {
lastLine = "";
lastLine = '';
}
}
this.setState({
@ -194,12 +218,20 @@ export class Log extends React.Component<LogProps, LogState> {
lastLine: lastLine,
logs: logs,
originLogs: originLogs,
originLastLine: originLastLine,
originLastLine: originLastLine
});
}
};
async loadLogs() {
const {source, data, env, translate: __, encoding, maxLength} = this.props;
const {
source,
data,
env,
translate: __,
encoding,
maxLength,
credentials = 'include'
} = this.props;
// 因为这里返回结果是流式的,和普通 api 请求不一样,如果直接用 fetcher 经过 responseAdaptor 可能会导致出错,所以就直接 fetch 了
const api = buildApi(source, data);
if (!api.url) {
@ -209,7 +241,7 @@ export class Log extends React.Component<LogProps, LogState> {
method: api.method?.toLocaleUpperCase() || 'GET',
headers: (api.headers as Record<string, string>) || undefined,
body: api.data ? JSON.stringify(api.data) : undefined,
credentials: 'include'
credentials: credentials as RequestCredentials
});
if (res.status === 200) {
const body = res.body;
@ -262,18 +294,46 @@ export class Log extends React.Component<LogProps, LogState> {
}
}
// 简单支持 ansi 颜色,只支持一行,不支持嵌套
ansiColrToHtml(line: string) {
const {disableColor} = this.props;
if (disableColor === true) {
return line;
}
const match = line.match(/\u001b\[([^m]+)m/);
if (match) {
const colorNumber = match[1];
if (colorNumber) {
line = line.replace(/\u001b[^m]*?m/g, '');
if (colorNumber in foregroundColors) {
return (
<span style={{color: foregroundColors[colorNumber]}}>{line}</span>
);
} else if (colorNumber in backgroundColors) {
return (
<span style={{backgroundColor: backgroundColors[colorNumber]}}>
{line.replace(/\u001b[^m]*?m/g, '')}
</span>
);
}
}
}
return line;
}
renderHighlightWord(line: string) {
const {classnames: cx} = this.props;
let {filterWord} = this.state;
if (filterWord === '') {
return line;
return this.ansiColrToHtml(line);
}
let items = line.split(filterWord);
return items.map((item, index) => {
if (index < items.length - 1) {
return (
<span>
{item}
{this.ansiColrToHtml(item)}
<span className={cx('Log-line-highlight')}>{filterWord}</span>
</span>
);
@ -404,7 +464,13 @@ export class Log extends React.Component<LogProps, LogState> {
<SearchBox
className={cx('Log-filter-box')}
placeholder="过滤词"
onChange={(value) => this.filterWord(this.state.originLogs, this.state.lastLine, value)}
onChange={value =>
this.filterWord(
this.state.originLogs,
this.state.lastLine,
value
)
}
value={this.state.filterWord}
/>
)}

Binary file not shown.