LiveLogs.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import React, { PureComponent } from 'react';
  2. import { css, cx } from 'emotion';
  3. import { Themeable, withTheme, GrafanaTheme, selectThemeVariant, LinkButton } from '@grafana/ui';
  4. import { LogsModel, LogRowModel } from 'app/core/logs_model';
  5. import ElapsedTime from './ElapsedTime';
  6. import { ButtonSize, ButtonVariant } from '@grafana/ui/src/components/Button/AbstractButton';
  7. const getStyles = (theme: GrafanaTheme) => ({
  8. logsRowsLive: css`
  9. label: logs-rows-live;
  10. display: flex;
  11. flex-flow: column nowrap;
  12. height: 65vh;
  13. overflow-y: auto;
  14. :first-child {
  15. margin-top: auto !important;
  16. }
  17. `,
  18. logsRowFresh: css`
  19. label: logs-row-fresh;
  20. color: ${theme.colors.text};
  21. background-color: ${selectThemeVariant({ light: theme.colors.gray6, dark: theme.colors.gray1 }, theme.type)};
  22. `,
  23. logsRowOld: css`
  24. label: logs-row-old;
  25. opacity: 0.8;
  26. `,
  27. logsRowsIndicator: css`
  28. font-size: ${theme.typography.size.md};
  29. padding: ${theme.spacing.sm} 0;
  30. display: flex;
  31. align-items: center;
  32. `,
  33. });
  34. export interface Props extends Themeable {
  35. logsResult?: LogsModel;
  36. stopLive: () => void;
  37. }
  38. export interface State {
  39. renderCount: number;
  40. }
  41. class LiveLogs extends PureComponent<Props, State> {
  42. private liveEndDiv: HTMLDivElement = null;
  43. constructor(props: Props) {
  44. super(props);
  45. this.state = { renderCount: 0 };
  46. }
  47. componentDidUpdate(prevProps: Props) {
  48. const prevRows: LogRowModel[] = prevProps.logsResult ? prevProps.logsResult.rows : [];
  49. const rows: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : [];
  50. if (prevRows !== rows) {
  51. this.setState({
  52. renderCount: this.state.renderCount + 1,
  53. });
  54. }
  55. if (this.liveEndDiv) {
  56. this.liveEndDiv.scrollIntoView(false);
  57. }
  58. }
  59. render() {
  60. const { theme } = this.props;
  61. const { renderCount } = this.state;
  62. const styles = getStyles(theme);
  63. const rowsToRender: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : [];
  64. return (
  65. <>
  66. <div className={cx(['logs-rows', styles.logsRowsLive])}>
  67. {rowsToRender.map((row: any, index) => {
  68. return (
  69. <div
  70. className={row.fresh ? cx(['logs-row', styles.logsRowFresh]) : cx(['logs-row', styles.logsRowOld])}
  71. key={`${row.timeEpochMs}-${index}`}
  72. >
  73. <div className="logs-row__localtime" title={`${row.timestamp} (${row.timeFromNow})`}>
  74. {row.timeLocal}
  75. </div>
  76. <div className="logs-row__message">{row.entry}</div>
  77. </div>
  78. );
  79. })}
  80. <div
  81. ref={element => {
  82. this.liveEndDiv = element;
  83. if (this.liveEndDiv) {
  84. this.liveEndDiv.scrollIntoView(false);
  85. }
  86. }}
  87. />
  88. </div>
  89. <div className={cx([styles.logsRowsIndicator])}>
  90. <span>
  91. Last line received: <ElapsedTime renderCount={renderCount} humanize={true} /> ago
  92. </span>
  93. <LinkButton
  94. onClick={this.props.stopLive}
  95. size={ButtonSize.Medium}
  96. variant={ButtonVariant.Transparent}
  97. style={{ color: theme.colors.orange }}
  98. >
  99. Stop Live
  100. </LinkButton>
  101. </div>
  102. </>
  103. );
  104. }
  105. }
  106. export const LiveLogsWithTheme = withTheme(LiveLogs);