Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions dashboard-ui/src/components/layouts/AppLayout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@

import Footer from '@/components/widgets/Footer';
import UpdateBanner from '@/components/widgets/UpdateBanner';
import { useUpdateNotification } from '@/lib/update-notifications';
import { useCLIUpdateNotification } from '@/lib/update-notifications';

export default function AppLayout({ children }: React.PropsWithChildren) {
const { showBanner, currentVersion, latestVersion, dismiss, dontRemindMe } = useUpdateNotification();
const { showBanner, currentVersion, latestVersion, dismiss, dontRemindMe } = useCLIUpdateNotification();

return (
<div className="flex h-screen flex-col overflow-hidden">
Expand Down
43 changes: 37 additions & 6 deletions dashboard-ui/src/components/widgets/NotificationsPopover.tsx
Comment thread
amorey marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -12,39 +12,70 @@
// See the License for the specific language governing permissions and
// limitations under the License.

import { useSubscription } from '@apollo/client/react';
import { ArrowUpCircle } from 'lucide-react';
import { useState } from 'react';

import { Popover, PopoverTrigger, PopoverContent } from '@kubetail/ui/elements/popover';

import { useUpdateNotification } from '@/lib/update-notifications';
import appConfig from '@/app-config';
import * as dashboardOps from '@/lib/graphql/dashboard/ops';
import { useCLIUpdateNotification, useClusterUpdateNotification } from '@/lib/update-notifications';

const ClusterUpdateEntry = ({ kubeContext }: { kubeContext: string }) => {
const { updateAvailable, currentVersion, latestVersion } = useClusterUpdateNotification(kubeContext);

if (!updateAvailable || !latestVersion) return null;

return (
<div className="flex items-start gap-2 rounded border border-blue-200 bg-blue-50 p-2 text-sm text-blue-900 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-100">
<ArrowUpCircle className="mt-0.5 h-4 w-4 shrink-0" />
<p>
Cluster update: {currentVersion} → {latestVersion}
</p>
</div>
);
};

const useActiveKubeContext = (): string | null => {
const { data } = useSubscription(dashboardOps.KUBE_CONFIG_WATCH, {
skip: appConfig.environment !== 'desktop',
});
return data?.kubeConfigWatch?.object?.currentContext ?? null;
};

export const NotificationsPopover = ({ children }: React.PropsWithChildren) => {
const [isOpen, setIsOpen] = useState(false);
const { updateAvailable, currentVersion, latestVersion } = useUpdateNotification();
const { updateAvailable, currentVersion, latestVersion } = useCLIUpdateNotification();
const activeKubeContext = useActiveKubeContext();
const { updateAvailable: clusterUpdateAvailable } = useClusterUpdateNotification(activeKubeContext ?? '');

const hasCliUpdate = updateAvailable && !!latestVersion;
const hasClusterUpdate = appConfig.environment === 'desktop' && !!activeKubeContext && clusterUpdateAvailable;
const hasNotifications = hasCliUpdate || hasClusterUpdate;

return (
<Popover open={isOpen} onOpenChange={setIsOpen}>
<PopoverTrigger asChild>
<div className="relative h-full">
{children}
{updateAvailable && <span className="absolute top-0.5 right-0.5 h-2 w-2 rounded-full bg-blue-500" />}
{hasNotifications && <span className="absolute top-0.5 right-0.5 h-2 w-2 rounded-full bg-blue-500" />}
</div>
</PopoverTrigger>
{isOpen && (
<PopoverContent side="top" className="w-80 mr-1">
<div className="space-y-2">
<p className="text-sm font-medium">Notifications</p>
{updateAvailable && latestVersion ? (
{hasCliUpdate && (
<div className="flex items-start gap-2 rounded border border-blue-200 bg-blue-50 p-2 text-sm text-blue-900 dark:border-blue-800 dark:bg-blue-950 dark:text-blue-100">
<ArrowUpCircle className="mt-0.5 h-4 w-4 shrink-0" />
<p>
CLI update: {currentVersion} → {latestVersion}
</p>
</div>
) : (
<p className="text-sm text-muted-foreground">No new notifications</p>
)}
{hasClusterUpdate && <ClusterUpdateEntry kubeContext={activeKubeContext} />}
{!hasNotifications && <p className="text-sm text-muted-foreground">No new notifications</p>}
</div>
</PopoverContent>
)}
Expand Down
18 changes: 11 additions & 7 deletions dashboard-ui/src/components/widgets/ServerStatus.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ import { Dialog, DialogContent, DialogDescription, DialogTitle, DialogTrigger }
import { Table, TableBody, TableCell, TableRow } from '@kubetail/ui/elements/table';

import appConfig from '@/app-config';
import ClusterAPIInstallButton from '@/components/widgets/ClusterAPIInstallButton';
import * as dashboardOps from '@/lib/graphql/dashboard/ops';
import {
ServerStatus,
Expand All @@ -29,6 +28,7 @@ import {
useKubernetesAPIServerStatus,
useClusterAPIServerStatus,
} from '@/lib/server-status';
import { useClusterUpdateNotification } from '@/lib/update-notifications';
import { cn } from '@/lib/util';

const kubernetesAPIServerStatusMapState = atom(new Map<string, ServerStatus>());
Expand Down Expand Up @@ -56,6 +56,9 @@ const HealthDot = ({ className, status }: { className?: string; status: Status }
case Status.Unknown:
color = 'chrome';
break;
case Status.UpdateAvailable:
color = 'blue';
break;
default:
throw new Error('not implemented');
}
Expand All @@ -67,6 +70,7 @@ const HealthDot = ({ className, status }: { className?: string; status: Status }
'bg-red-500': color === 'red',
'bg-green-500': color === 'green',
'bg-yellow-500': color === 'yellow',
'bg-blue-500': color === 'blue',
})}
/>
);
Expand Down Expand Up @@ -170,6 +174,9 @@ const KubernetesAPIServerStatusRow = ({ kubeContext, dashboardServerStatus }: Se
const ClusterAPIServerStatusRow = ({ kubeContext, dashboardServerStatus }: ServerStatusRowProps) => {
const serverStatusMap = useAtomValue(clusterAPIServerStatusMapState);
const serverStatus = serverStatusMap.get(kubeContext) || new ServerStatus();
const { updateAvailable } = useClusterUpdateNotification(appConfig.environment === 'desktop' ? kubeContext : '');

const showClusterUpdate = appConfig.environment === 'desktop' && updateAvailable;

return (
<TableRow>
Expand All @@ -179,13 +186,10 @@ const ClusterAPIServerStatusRow = ({ kubeContext, dashboardServerStatus }: Serve
) : (
<>
<TableCell className="w-px">
<HealthDot status={serverStatus.status} />
<HealthDot status={showClusterUpdate ? Status.UpdateAvailable : serverStatus.status} />
</TableCell>
<TableCell className="whitespace-normal flex justify-between items-center">
{statusMessage(serverStatus, 'Uknown')}
{appConfig.environment === 'desktop' && serverStatus.status === Status.NotFound && (
<ClusterAPIInstallButton kubeContext={kubeContext} />
)}
<TableCell className="whitespace-normal">
{showClusterUpdate ? 'Update available' : statusMessage(serverStatus, 'Uknown')}
</TableCell>
</>
)}
Expand Down
1 change: 1 addition & 0 deletions dashboard-ui/src/lib/server-status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export const enum Status {
Degraded = 'DEGRADED',
Unknown = 'UNKNOWN',
NotFound = 'NOTFOUND',
UpdateAvailable = 'UPDATE_AVAILABLE',
}

export class ServerStatus {
Expand Down
Loading
Loading