ServiceAccountTokensTable.tsx 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. import { css } from '@emotion/css';
  2. import React, { FC } from 'react';
  3. import { dateTimeFormat, GrafanaTheme2, TimeZone } from '@grafana/data';
  4. import { DeleteButton, Icon, Tooltip, useStyles2, useTheme2 } from '@grafana/ui';
  5. import { contextSrv } from 'app/core/core';
  6. import { AccessControlAction } from 'app/types';
  7. import { ApiKey } from '../../types';
  8. interface Props {
  9. tokens: ApiKey[];
  10. timeZone: TimeZone;
  11. onDelete: (token: ApiKey) => void;
  12. }
  13. export const ServiceAccountTokensTable: FC<Props> = ({ tokens, timeZone, onDelete }) => {
  14. const theme = useTheme2();
  15. const styles = getStyles(theme);
  16. return (
  17. <>
  18. <table className="filter-table">
  19. <thead>
  20. <tr>
  21. <th>Name</th>
  22. <th>Expires</th>
  23. <th>Created</th>
  24. <th style={{ width: '34px' }} />
  25. </tr>
  26. </thead>
  27. <tbody>
  28. {tokens.map((key) => {
  29. return (
  30. <tr key={key.id} className={styles.tableRow(key.hasExpired)}>
  31. <td>{key.name}</td>
  32. <td>
  33. <TokenExpiration timeZone={timeZone} token={key} />
  34. </td>
  35. <td>{formatDate(timeZone, key.created)}</td>
  36. {contextSrv.hasPermission(AccessControlAction.ServiceAccountsDelete) && (
  37. <td>
  38. <DeleteButton aria-label="Delete service account token" size="sm" onConfirm={() => onDelete(key)} />
  39. </td>
  40. )}
  41. </tr>
  42. );
  43. })}
  44. </tbody>
  45. </table>
  46. </>
  47. );
  48. };
  49. function formatDate(timeZone: TimeZone, expiration?: string): string {
  50. if (!expiration) {
  51. return 'No expiration date';
  52. }
  53. return dateTimeFormat(expiration, { timeZone });
  54. }
  55. function formatSecondsLeftUntilExpiration(secondsUntilExpiration: number): string {
  56. const days = Math.floor(secondsUntilExpiration / (3600 * 24));
  57. const daysFormat = days > 1 ? `${days} days` : `${days} day`;
  58. return `Expires in ${daysFormat}`;
  59. }
  60. interface TokenExpirationProps {
  61. timeZone: TimeZone;
  62. token: ApiKey;
  63. }
  64. const TokenExpiration = ({ timeZone, token }: TokenExpirationProps) => {
  65. const styles = useStyles2(getStyles);
  66. if (!token.expiration) {
  67. return <span className={styles.neverExpire}>Never</span>;
  68. }
  69. if (token.secondsUntilExpiration) {
  70. return (
  71. <span className={styles.secondsUntilExpiration}>
  72. {formatSecondsLeftUntilExpiration(token.secondsUntilExpiration)}
  73. </span>
  74. );
  75. }
  76. if (token.hasExpired) {
  77. return (
  78. <span className={styles.hasExpired}>
  79. Expired
  80. <span className={styles.tooltipContainer}>
  81. <Tooltip content="This token has expired">
  82. <Icon name="exclamation-triangle" className={styles.toolTipIcon} />
  83. </Tooltip>
  84. </span>
  85. </span>
  86. );
  87. }
  88. return <span>{formatDate(timeZone, token.expiration)}</span>;
  89. };
  90. const getStyles = (theme: GrafanaTheme2) => ({
  91. tableRow: (hasExpired: boolean | undefined) => css`
  92. color: ${hasExpired ? theme.colors.text.secondary : theme.colors.text.primary};
  93. `,
  94. tooltipContainer: css`
  95. margin-left: ${theme.spacing(1)};
  96. `,
  97. toolTipIcon: css`
  98. color: ${theme.colors.error.text};
  99. `,
  100. secondsUntilExpiration: css`
  101. color: ${theme.colors.warning.text};
  102. `,
  103. hasExpired: css`
  104. color: ${theme.colors.error.text};
  105. `,
  106. neverExpire: css`
  107. color: ${theme.colors.text.secondary};
  108. `,
  109. });