Skip to content

Commit

Permalink
frontend: Add support for multiple clusters to the VersionButton
Browse files Browse the repository at this point in the history
  • Loading branch information
joaquimrocha committed May 8, 2023
1 parent 3b6b79b commit 85bdc1f
Show file tree
Hide file tree
Showing 2 changed files with 126 additions and 34 deletions.
148 changes: 116 additions & 32 deletions frontend/src/components/Sidebar/VersionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ import { useSnackbar } from 'notistack';
import React from 'react';
import { useTranslation } from 'react-i18next';
import semver from 'semver';
import { getVersion, useCluster } from '../../lib/k8s';
import { getVersion, getVersionForCluster, useCluster } from '../../lib/k8s';
import { StringDict } from '../../lib/k8s/cluster';
import { getClusterGroup } from '../../lib/util';
import { useTypedSelector } from '../../redux/reducers/reducers';
import { Tabs } from '../common';
import { NameValueTable } from '../common/SimpleTable';

const versionSnackbarHideTimeout = 5000; // ms
Expand All @@ -35,12 +37,22 @@ export default function VersionButton() {
const sidebar = useTypedSelector(state => state.ui.sidebar);
const { enqueueSnackbar } = useSnackbar();
const classes = useVersionButtonStyle();
const [clusterVersion, setClusterVersion] = React.useState<StringDict | null>(null);
const [clusterVersions, setClusterVersions] = React.useState<{
[key: string]: StringDict | null;
}>({});
const cluster = useCluster();
const [open, setOpen] = React.useState(false);
const { t } = useTranslation('glossary');
const clusters = React.useMemo(() => {
return getClusterGroup();
}, [cluster]);

function getVersionRows() {
function getVersionRows(clusterName: string) {
if (!Object.values(clusterVersions).length) {
return [];
}

const clusterVersion = clusterVersions[clusterName];
if (!clusterVersion) {
return [];
}
Expand Down Expand Up @@ -73,42 +85,70 @@ export default function VersionButton() {
() => {
let stillAlive = true;
function fetchVersion() {
getVersion()
.then((results: StringDict) => {
Promise.allSettled(clusters.map(cluster => getVersionForCluster(cluster || '')))
.then(results => {
if (!stillAlive) {
return;
}
const newVersions: typeof clusterVersions = {};
for (const result of results) {
const { status } = result;

setClusterVersion(results);
let versionChange = 0;
if (clusterVersion && results && results.gitVersion) {
versionChange = semver.compare(results.gitVersion, clusterVersion.gitVersion);

let msg = '';
if (versionChange > 0) {
msg = t('cluster|Cluster version upgraded to {{ gitVersion }}', {
gitVersion: results.gitVersion,
});
} else if (versionChange < 0) {
msg = t('cluster|Cluster version downgraded to {{ gitVersion }}', {
gitVersion: results.gitVersion,
});
if (status === 'rejected') {
console.error(
'Getting the version for a cluster:',
(result as PromiseRejectedResult).reason
);
continue;
}

if (msg) {
enqueueSnackbar(msg, {
key: 'version',
preventDuplicate: true,
autoHideDuration: versionSnackbarHideTimeout,
variant: 'info',
});
const [cluster, clusterVersion] = (
result as PromiseFulfilledResult<[string, StringDict]>
).value;

newVersions[cluster] = clusterVersion;
let versionChange = 0;

if (clusterVersion && clusterVersion && clusterVersion.gitVersion) {
versionChange = semver.compare(
clusterVersion.gitVersion,
clusterVersion.gitVersion
);

let msg = '';
if (versionChange > 0) {
msg = t('cluster|Cluster version upgraded to {{ gitVersion }}', {
gitVersion: clusterVersion.gitVersion,
});
} else if (versionChange < 0) {
msg = t('cluster|Cluster version downgraded to {{ gitVersion }}', {
gitVersion: clusterVersion.gitVersion,
});
}

if (msg) {
enqueueSnackbar(msg, {
key: 'version',
preventDuplicate: true,
autoHideDuration: versionSnackbarHideTimeout,
variant: 'info',
});
}
}
}

setClusterVersions(newVersions);
})
.catch((error: Error) => console.error('Getting the cluster version:', error));

for (const cluster of []) {
getVersion(cluster)
.then()
.catch((error: Error) => console.error('Getting the cluster version:', error));
}
}

if (!clusterVersion) {
if (Object.keys(clusterVersions).length === 0) {
fetchVersion();
}

Expand All @@ -122,34 +162,78 @@ export default function VersionButton() {
};
},
// eslint-disable-next-line
[clusterVersion]
[clusterVersions]
);

// Use the location to make sure the version is changed, as it depends on the cluster
// (defined in the URL ATM).
// @todo: Update this if the active cluster management is changed.
React.useEffect(() => {
setClusterVersion(null);
setClusterVersions(versions => {
if (!cluster || !versions[cluster]) {
return {};
}
return versions;
});
}, [cluster]);

function handleClose() {
setOpen(false);
}

return !clusterVersion ? null : (
const clusterVersionText = React.useMemo(() => {
let versionText: string[] = [];
// We only up to two versions (if they are different). If more
// than 2 different versions exist, then we show the 1st one + ... .
for (const versionInfo of Object.values(clusterVersions)) {
if (versionText.length > 2) {
break;
}

if (versionText.length === 0 && !!versionInfo?.gitVersion) {
versionText.push(versionInfo.gitVersion);
} else if (versionText[0] === (versionInfo?.gitVersion || '')) {
// If it's the same version, we just check the next cluster's.
continue;
} else if (!!versionInfo?.gitVersion) {
versionText.push(versionInfo.gitVersion);
}
}

if (versionText.length > 2) {
versionText = [versionText[0], '...'];
}

return versionText.join('+');
}, [clusterVersions]);

return Object.keys(clusterVersions).length === 0 ? null : (
<Box mx="auto" py=".2em" className={classes.versionBox}>
<Button onClick={() => setOpen(true)} style={{ textTransform: 'none' }}>
<Box display={sidebar.isSidebarOpen ? 'flex' : 'block'} alignItems="center">
<Box>
<Icon color="#adadad" icon="mdi:kubernetes" className={classes.versionIcon} />
</Box>
<Box>{clusterVersion.gitVersion}</Box>
<Box>{clusterVersionText}</Box>
</Box>
</Button>
<Dialog open={open} onClose={handleClose}>
<DialogTitle>{t('Kubernetes Version')}</DialogTitle>
<DialogContent>
<NameValueTable rows={getVersionRows()} />
{Object.keys(clusterVersions).length === 1 ? (
<NameValueTable rows={getVersionRows(cluster || '')} />
) : (
<Tabs
tabs={Object.keys(clusterVersions).map(clusterName => ({
label: clusterName,
component: (
<Box mt={2}>
<NameValueTable rows={getVersionRows(clusterName)} />
</Box>
),
}))}
/>
)}
</DialogContent>
<DialogActions>
<Button onClick={handleClose} color="primary">
Expand Down
12 changes: 10 additions & 2 deletions frontend/src/lib/k8s/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -102,8 +102,16 @@ export function useCluster() {
return cluster;
}

export function getVersion(): Promise<StringDict> {
return request('/version');
export function getVersion(cluster = ''): Promise<StringDict> {
return request('/version', { cluster });
}

export function getVersionForCluster(cluster = ''): Promise<[string, StringDict]> {
return new Promise((resolve, reject) => {
request('/version', { cluster })
.then(versionInfo => resolve([cluster, versionInfo]))
.catch(e => reject({ cluster: e }));
});
}

export type CancellablePromise = Promise<() => void>;
Expand Down

0 comments on commit 85bdc1f

Please sign in to comment.