|
@@ -0,0 +1,185 @@
|
|
|
|
|
+import {
|
|
|
|
|
+ DocumentTitle,
|
|
|
|
|
+ K8sResourceCommon,
|
|
|
|
|
+ k8sPatch,
|
|
|
|
|
+ useK8sModel,
|
|
|
|
|
+ useK8sWatchResource,
|
|
|
|
|
+} from '@openshift-console/dynamic-plugin-sdk';
|
|
|
|
|
+
|
|
|
|
|
+import {
|
|
|
|
|
+ Alert,
|
|
|
|
|
+ Card,
|
|
|
|
|
+ CardBody,
|
|
|
|
|
+ CardTitle,
|
|
|
|
|
+ PageSection,
|
|
|
|
|
+ Spinner,
|
|
|
|
|
+ Switch,
|
|
|
|
|
+ Title,
|
|
|
|
|
+} from '@patternfly/react-core';
|
|
|
|
|
+
|
|
|
|
|
+import type { FC } from 'react';
|
|
|
|
|
+import { useState } from 'react';
|
|
|
|
|
+
|
|
|
|
|
+const TUNED_NAMESPACE = 'openshift-cluster-node-tuning-operator';
|
|
|
|
|
+const TUNED_NAME = 'custom-tuned-profile';
|
|
|
|
|
+
|
|
|
|
|
+const POWERSAVE_PROFILE = 'homelab-powersave';
|
|
|
|
|
+const PERFORMANCE_PROFILE = 'homelab-performance';
|
|
|
|
|
+
|
|
|
|
|
+const TunedGVK = {
|
|
|
|
|
+ group: 'tuned.openshift.io',
|
|
|
|
|
+ version: 'v1',
|
|
|
|
|
+ kind: 'Tuned',
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+interface TunedResource extends K8sResourceCommon {
|
|
|
|
|
+ spec?: {
|
|
|
|
|
+ recommend?: Array<{
|
|
|
|
|
+ profile?: string;
|
|
|
|
|
+ [key: string]: unknown;
|
|
|
|
|
+ }>;
|
|
|
|
|
+ };
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
|
|
+const DashboardPage: FC = () => {
|
|
|
|
|
+ const [patching, setPatching] = useState(false);
|
|
|
|
|
+ const [patchError, setPatchError] = useState<string>();
|
|
|
|
|
+
|
|
|
|
|
+ const [tunedModel, modelLoading] = useK8sModel(TunedGVK);
|
|
|
|
|
+
|
|
|
|
|
+ const [tuned, loaded, loadError] =
|
|
|
|
|
+ useK8sWatchResource<TunedResource>({
|
|
|
|
|
+ groupVersionKind: TunedGVK,
|
|
|
|
|
+ name: TUNED_NAME,
|
|
|
|
|
+ namespace: TUNED_NAMESPACE,
|
|
|
|
|
+ });
|
|
|
|
|
+
|
|
|
|
|
+ const recommendations = tuned?.spec?.recommend ?? [];
|
|
|
|
|
+
|
|
|
|
|
+ const profileIndex = recommendations.findIndex(
|
|
|
|
|
+ (recommendation) =>
|
|
|
|
|
+ recommendation.profile === POWERSAVE_PROFILE ||
|
|
|
|
|
+ recommendation.profile === PERFORMANCE_PROFILE,
|
|
|
|
|
+ );
|
|
|
|
|
+
|
|
|
|
|
+ const currentProfile =
|
|
|
|
|
+ profileIndex >= 0
|
|
|
|
|
+ ? recommendations[profileIndex]?.profile
|
|
|
|
|
+ : undefined;
|
|
|
|
|
+
|
|
|
|
|
+ const performanceEnabled =
|
|
|
|
|
+ currentProfile === PERFORMANCE_PROFILE;
|
|
|
|
|
+
|
|
|
|
|
+ const profileKnown =
|
|
|
|
|
+ currentProfile === POWERSAVE_PROFILE ||
|
|
|
|
|
+ currentProfile === PERFORMANCE_PROFILE;
|
|
|
|
|
+
|
|
|
|
|
+ const changePerformanceMode = async (enabled: boolean) => {
|
|
|
|
|
+ if (!tunedModel || !tuned || profileIndex < 0) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ setPatching(true);
|
|
|
|
|
+ setPatchError(undefined);
|
|
|
|
|
+
|
|
|
|
|
+ try {
|
|
|
|
|
+ await k8sPatch({
|
|
|
|
|
+ model: tunedModel,
|
|
|
|
|
+ resource: tuned,
|
|
|
|
|
+ data: [
|
|
|
|
|
+ {
|
|
|
|
|
+ op: 'replace',
|
|
|
|
|
+ path: `/spec/recommend/${profileIndex}/profile`,
|
|
|
|
|
+ value: enabled
|
|
|
|
|
+ ? PERFORMANCE_PROFILE
|
|
|
|
|
+ : POWERSAVE_PROFILE,
|
|
|
|
|
+ },
|
|
|
|
|
+ ],
|
|
|
|
|
+ });
|
|
|
|
|
+ } catch (error) {
|
|
|
|
|
+ setPatchError(
|
|
|
|
|
+ error instanceof Error
|
|
|
|
|
+ ? error.message
|
|
|
|
|
+ : String(error),
|
|
|
|
|
+ );
|
|
|
|
|
+ } finally {
|
|
|
|
|
+ setPatching(false);
|
|
|
|
|
+ }
|
|
|
|
|
+ };
|
|
|
|
|
+
|
|
|
|
|
+ return (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <DocumentTitle>Dashboard</DocumentTitle>
|
|
|
|
|
+
|
|
|
|
|
+ <PageSection>
|
|
|
|
|
+ <Title headingLevel="h1" size="2xl">
|
|
|
|
|
+ Homelab Dashboard
|
|
|
|
|
+ </Title>
|
|
|
|
|
+ </PageSection>
|
|
|
|
|
+
|
|
|
|
|
+ <PageSection>
|
|
|
|
|
+ <Card>
|
|
|
|
|
+ <CardTitle>Cluster performance</CardTitle>
|
|
|
|
|
+
|
|
|
|
|
+ <CardBody>
|
|
|
|
|
+ {!loaded || modelLoading ? (
|
|
|
|
|
+ <Spinner />
|
|
|
|
|
+ ) : loadError ? (
|
|
|
|
|
+ <Alert
|
|
|
|
|
+ isInline
|
|
|
|
|
+ variant="danger"
|
|
|
|
|
+ title="Unable to read TuneD configuration"
|
|
|
|
|
+ >
|
|
|
|
|
+ {String(loadError)}
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ ) : (
|
|
|
|
|
+ <>
|
|
|
|
|
+ <Switch
|
|
|
|
|
+ id="cluster-performance-mode"
|
|
|
|
|
+ label="Performance mode"
|
|
|
|
|
+ isChecked={performanceEnabled}
|
|
|
|
|
+ isDisabled={patching || !profileKnown}
|
|
|
|
|
+ onChange={(_, checked) =>
|
|
|
|
|
+ void changePerformanceMode(checked)
|
|
|
|
|
+ }
|
|
|
|
|
+ /><br />
|
|
|
|
|
+
|
|
|
|
|
+ <p>
|
|
|
|
|
+ Current profile:{' '}
|
|
|
|
|
+ <strong>
|
|
|
|
|
+ {currentProfile ?? 'Unknown'}
|
|
|
|
|
+ </strong>
|
|
|
|
|
+ </p>
|
|
|
|
|
+
|
|
|
|
|
+ {patching && <Spinner size="md" />}
|
|
|
|
|
+
|
|
|
|
|
+ {!profileKnown && (
|
|
|
|
|
+ <Alert
|
|
|
|
|
+ isInline
|
|
|
|
|
+ variant="warning"
|
|
|
|
|
+ title="Unknown TuneD profile"
|
|
|
|
|
+ >
|
|
|
|
|
+ Expected {POWERSAVE_PROFILE} or{' '}
|
|
|
|
|
+ {PERFORMANCE_PROFILE}.
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ )}
|
|
|
|
|
+
|
|
|
|
|
+ {patchError && (
|
|
|
|
|
+ <Alert
|
|
|
|
|
+ isInline
|
|
|
|
|
+ variant="danger"
|
|
|
|
|
+ title="Unable to change performance mode"
|
|
|
|
|
+ >
|
|
|
|
|
+ {patchError}
|
|
|
|
|
+ </Alert>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </>
|
|
|
|
|
+ )}
|
|
|
|
|
+ </CardBody>
|
|
|
|
|
+ </Card>
|
|
|
|
|
+ </PageSection>
|
|
|
|
|
+ </>
|
|
|
|
|
+ );
|
|
|
|
|
+};
|
|
|
|
|
+
|
|
|
|
|
+export default DashboardPage;
|