LiveLogs.tsx 3.3 KB

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