LiveLogs.tsx 3.2 KB

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