DashboardPage.tsx 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196
  1. import {
  2. DocumentTitle,
  3. K8sResourceCommon,
  4. k8sPatch,
  5. useK8sModel,
  6. useK8sWatchResource,
  7. } from '@openshift-console/dynamic-plugin-sdk';
  8. import {
  9. Alert,
  10. Card,
  11. CardBody,
  12. CardTitle,
  13. PageSection,
  14. Spinner,
  15. Switch,
  16. Title,
  17. } from '@patternfly/react-core';
  18. import type { FC } from 'react';
  19. import { useState } from 'react';
  20. import NowPlayingCard from './NowPlayingCard';
  21. import GamingCard from './GamingCard';
  22. const TUNED_NAMESPACE = 'openshift-cluster-node-tuning-operator';
  23. const TUNED_NAME = 'custom-tuned-profile';
  24. const POWERSAVE_PROFILE = 'homelab-powersave';
  25. const PERFORMANCE_PROFILE = 'homelab-performance';
  26. const TunedGVK = {
  27. group: 'tuned.openshift.io',
  28. version: 'v1',
  29. kind: 'Tuned',
  30. };
  31. interface TunedResource extends K8sResourceCommon {
  32. spec?: {
  33. recommend?: Array<{
  34. profile?: string;
  35. [key: string]: unknown;
  36. }>;
  37. };
  38. }
  39. const DashboardPage: FC = () => {
  40. const [patching, setPatching] = useState(false);
  41. const [patchError, setPatchError] = useState<string>();
  42. const [tunedModel, modelLoading] = useK8sModel(TunedGVK);
  43. const [tuned, loaded, loadError] =
  44. useK8sWatchResource<TunedResource>({
  45. groupVersionKind: TunedGVK,
  46. name: TUNED_NAME,
  47. namespace: TUNED_NAMESPACE,
  48. });
  49. const recommendations = tuned?.spec?.recommend ?? [];
  50. const profileIndex = recommendations.findIndex(
  51. (recommendation) =>
  52. recommendation.profile === POWERSAVE_PROFILE ||
  53. recommendation.profile === PERFORMANCE_PROFILE,
  54. );
  55. const currentProfile =
  56. profileIndex >= 0
  57. ? recommendations[profileIndex]?.profile
  58. : undefined;
  59. const performanceEnabled =
  60. currentProfile === PERFORMANCE_PROFILE;
  61. const profileKnown =
  62. currentProfile === POWERSAVE_PROFILE ||
  63. currentProfile === PERFORMANCE_PROFILE;
  64. const changePerformanceMode = async (enabled: boolean) => {
  65. if (!tunedModel || !tuned || profileIndex < 0) {
  66. return;
  67. }
  68. setPatching(true);
  69. setPatchError(undefined);
  70. try {
  71. await k8sPatch({
  72. model: tunedModel,
  73. resource: tuned,
  74. data: [
  75. {
  76. op: 'replace',
  77. path: `/spec/recommend/${profileIndex}/profile`,
  78. value: enabled
  79. ? PERFORMANCE_PROFILE
  80. : POWERSAVE_PROFILE,
  81. },
  82. ],
  83. });
  84. } catch (error) {
  85. setPatchError(
  86. error instanceof Error
  87. ? error.message
  88. : String(error),
  89. );
  90. } finally {
  91. setPatching(false);
  92. }
  93. };
  94. return (
  95. <>
  96. <DocumentTitle>Dashboard</DocumentTitle>
  97. <PageSection>
  98. <Title headingLevel="h1" size="2xl">
  99. Homelab Dashboard
  100. </Title>
  101. </PageSection>
  102. <PageSection>
  103. <Card>
  104. <CardTitle>Cluster performance</CardTitle>
  105. <CardBody>
  106. {!loaded || modelLoading ? (
  107. <Spinner />
  108. ) : loadError ? (
  109. <Alert
  110. isInline
  111. variant="danger"
  112. title="Unable to read TuneD configuration"
  113. >
  114. {String(loadError)}
  115. </Alert>
  116. ) : (
  117. <>
  118. <Switch
  119. id="cluster-performance-mode"
  120. label="Performance mode"
  121. isChecked={performanceEnabled}
  122. isDisabled={patching || !profileKnown}
  123. onChange={(_, checked) =>
  124. void changePerformanceMode(checked)
  125. }
  126. /><br /><br />
  127. <p>
  128. Current profile:{' '}
  129. <strong>
  130. {currentProfile ?? 'Unknown'}
  131. </strong>
  132. </p>
  133. {patching && <Spinner size="md" />}
  134. {!profileKnown && (
  135. <Alert
  136. isInline
  137. variant="warning"
  138. title="Unknown TuneD profile"
  139. >
  140. Expected {POWERSAVE_PROFILE} or{' '}
  141. {PERFORMANCE_PROFILE}.
  142. </Alert>
  143. )}
  144. {patchError && (
  145. <Alert
  146. isInline
  147. variant="danger"
  148. title="Unable to change performance mode"
  149. >
  150. {patchError}
  151. </Alert>
  152. )}
  153. </>
  154. )}
  155. </CardBody>
  156. </Card>
  157. </PageSection>
  158. <PageSection>
  159. <NowPlayingCard />
  160. </PageSection>
  161. <PageSection>
  162. <GamingCard />
  163. </PageSection>
  164. </>
  165. );
  166. };
  167. export default DashboardPage;