LiveLogs.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134
  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. TimeZone,
  12. } from '@grafana/ui';
  13. import ElapsedTime from './ElapsedTime';
  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. timeZone: TimeZone;
  44. stopLive: () => void;
  45. }
  46. export interface State {
  47. renderCount: number;
  48. }
  49. class LiveLogs extends PureComponent<Props, State> {
  50. private liveEndDiv: HTMLDivElement = null;
  51. constructor(props: Props) {
  52. super(props);
  53. this.state = { renderCount: 0 };
  54. }
  55. componentDidUpdate(prevProps: Props) {
  56. const prevRows: LogRowModel[] = prevProps.logsResult ? prevProps.logsResult.rows : [];
  57. const rows: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : [];
  58. if (prevRows !== rows) {
  59. this.setState({
  60. renderCount: this.state.renderCount + 1,
  61. });
  62. }
  63. if (this.liveEndDiv) {
  64. this.liveEndDiv.scrollIntoView(false);
  65. }
  66. }
  67. render() {
  68. const { theme, timeZone } = this.props;
  69. const { renderCount } = this.state;
  70. const styles = getStyles(theme);
  71. const rowsToRender: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : [];
  72. const showUtc = timeZone === 'utc';
  73. return (
  74. <>
  75. <div className={cx(['logs-rows', styles.logsRowsLive])}>
  76. {rowsToRender.map((row: any, index) => {
  77. return (
  78. <div
  79. className={row.fresh ? cx(['logs-row', styles.logsRowFresh]) : cx(['logs-row', styles.logsRowOld])}
  80. key={`${row.timeEpochMs}-${index}`}
  81. >
  82. {showUtc && (
  83. <div className="logs-row__localtime" title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>
  84. {row.timeUtc}
  85. </div>
  86. )}
  87. {!showUtc && (
  88. <div className="logs-row__localtime" title={`${row.timeUtc} (${row.timeFromNow})`}>
  89. {row.timeLocal}
  90. </div>
  91. )}
  92. <div className="logs-row__message">{row.entry}</div>
  93. </div>
  94. );
  95. })}
  96. <div
  97. ref={element => {
  98. this.liveEndDiv = element;
  99. if (this.liveEndDiv) {
  100. this.liveEndDiv.scrollIntoView(false);
  101. }
  102. }}
  103. />
  104. </div>
  105. <div className={cx([styles.logsRowsIndicator])}>
  106. <span>
  107. Last line received: <ElapsedTime renderCount={renderCount} humanize={true} /> ago
  108. </span>
  109. <LinkButton
  110. onClick={this.props.stopLive}
  111. size="md"
  112. variant="transparent"
  113. style={{ color: theme.colors.orange }}
  114. >
  115. Stop Live
  116. </LinkButton>
  117. </div>
  118. </>
  119. );
  120. }
  121. }
  122. export const LiveLogsWithTheme = withTheme(LiveLogs);