LiveLogs.tsx 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  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, TimeZone } from '@grafana/data';
  5. import ElapsedTime from './ElapsedTime';
  6. const getStyles = (theme: GrafanaTheme) => ({
  7. logsRowsLive: css`
  8. label: logs-rows-live;
  9. display: flex;
  10. flex-flow: column nowrap;
  11. height: 65vh;
  12. overflow-y: auto;
  13. :first-child {
  14. margin-top: auto !important;
  15. }
  16. `,
  17. logsRowFresh: css`
  18. label: logs-row-fresh;
  19. color: ${theme.colors.text};
  20. background-color: ${selectThemeVariant({ light: theme.colors.gray6, dark: theme.colors.gray1 }, theme.type)};
  21. `,
  22. logsRowOld: css`
  23. label: logs-row-old;
  24. opacity: 0.8;
  25. `,
  26. logsRowsIndicator: css`
  27. font-size: ${theme.typography.size.md};
  28. padding: ${theme.spacing.sm} 0;
  29. display: flex;
  30. align-items: center;
  31. `,
  32. });
  33. export interface Props extends Themeable {
  34. logsResult?: LogsModel;
  35. timeZone: TimeZone;
  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, timeZone } = this.props;
  61. const { renderCount } = this.state;
  62. const styles = getStyles(theme);
  63. const rowsToRender: LogRowModel[] = this.props.logsResult ? this.props.logsResult.rows : [];
  64. const showUtc = timeZone === 'utc';
  65. return (
  66. <>
  67. <div className={cx(['logs-rows', styles.logsRowsLive])}>
  68. {rowsToRender.map((row: any, index) => {
  69. return (
  70. <div
  71. className={row.fresh ? cx(['logs-row', styles.logsRowFresh]) : cx(['logs-row', styles.logsRowOld])}
  72. key={`${row.timeEpochMs}-${index}`}
  73. >
  74. {showUtc && (
  75. <div className="logs-row__localtime" title={`Local: ${row.timeLocal} (${row.timeFromNow})`}>
  76. {row.timeUtc}
  77. </div>
  78. )}
  79. {!showUtc && (
  80. <div className="logs-row__localtime" title={`${row.timeUtc} (${row.timeFromNow})`}>
  81. {row.timeLocal}
  82. </div>
  83. )}
  84. <div className="logs-row__message">{row.entry}</div>
  85. </div>
  86. );
  87. })}
  88. <div
  89. ref={element => {
  90. this.liveEndDiv = element;
  91. if (this.liveEndDiv) {
  92. this.liveEndDiv.scrollIntoView(false);
  93. }
  94. }}
  95. />
  96. </div>
  97. <div className={cx([styles.logsRowsIndicator])}>
  98. <span>
  99. Last line received: <ElapsedTime renderCount={renderCount} humanize={true} /> ago
  100. </span>
  101. <LinkButton
  102. onClick={this.props.stopLive}
  103. size="md"
  104. variant="transparent"
  105. style={{ color: theme.colors.orange }}
  106. >
  107. Stop Live
  108. </LinkButton>
  109. </div>
  110. </>
  111. );
  112. }
  113. }
  114. export const LiveLogsWithTheme = withTheme(LiveLogs);