Skip to content

Commit f1b0db4

Browse files
committed
feat: Show restart button in the top bar
This reverts the revert commit 91b6534. https://harperdb.atlassian.net/browse/STUDIO-358
1 parent 91b6534 commit f1b0db4

File tree

4 files changed

+62
-19
lines changed

4 files changed

+62
-19
lines changed

src/components/Breadcrumbs.tsx

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,21 @@
1+
import { RestartButton } from '@/components/RestartButton';
2+
import { isLocalStudio } from '@/config/constants';
3+
import { useInstanceClientIdParams } from '@/config/useInstanceClient';
14
import { Cluster, Instance, Organization } from '@/lib/api.patch';
25
import { capitalizeWords } from '@/lib/string/capitalizeWords';
3-
import { Link, useLocation, useRouteContext } from '@tanstack/react-router';
6+
import { Link, useLocation, useParams, useRouteContext } from '@tanstack/react-router';
47
import { HomeIcon } from 'lucide-react';
58
import { useMemo } from 'react';
69

7-
export function Breadcrumbs() {
10+
export function Breadcrumbs({ restartRequired }: { restartRequired?: boolean }) {
811
const location = useLocation();
9-
const { organization, instance, cluster }: { organization?: Organization; instance?: Instance; cluster?: Cluster } = useRouteContext({ strict: false });
12+
13+
const { organization, instance, cluster }: {
14+
organization?: Organization;
15+
instance?: Instance;
16+
cluster?: Cluster
17+
} = useRouteContext({ strict: false });
18+
1019
const breadcrumbs = useMemo(() => {
1120
const routeHistory = location.pathname.split('/')
1221
.filter((x) => x && x.length > 0);
@@ -65,8 +74,24 @@ export function Breadcrumbs() {
6574
}, [location.pathname, cluster?.name, instance?.name, organization?.name]);
6675

6776
return (
68-
<div role="list" className="flex items-center space-x-0 lg:space-x-2 xl:space-x-4 sm:max-w-9/10 max-w-[calc(100%-56px)]">
77+
<div role="list" className="flex items-center space-x-2 xl:space-x-4 sm:max-w-9/10 max-w-[calc(100%-56px)]">
6978
{...breadcrumbs}
79+
{restartRequired && <BreadcrumbsRestartButton />}
7080
</div>
7181
);
7282
}
83+
84+
function BreadcrumbsRestartButton() {
85+
const { instanceId }: { instanceId?: string; clusterId: string; } = useParams({ strict: false });
86+
const targetNoun = (instanceId || isLocalStudio) ? 'Instance' : 'Cluster';
87+
const instanceParams = useInstanceClientIdParams();
88+
89+
return <RestartButton
90+
className="animate-glow-pulse"
91+
targetNoun={targetNoun}
92+
instanceClient={instanceParams.instanceClient}
93+
operation="restart"
94+
hideText={true}
95+
tooltip={`This ${targetNoun} is requesting a restart, when convenient, to apply your latest changes.`}
96+
/>;
97+
}

src/components/RestartButton.tsx

Lines changed: 15 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,23 @@ import { cx, VariantProps } from 'class-variance-authority';
88
import { RotateCcwIcon } from 'lucide-react';
99

1010
interface RestartButtonParams extends InstanceClientConfig, VariantProps<typeof buttonVariants> {
11-
targetNoun: 'Instance' | 'Cluster';
12-
operation: 'restart_service' | 'restart';
1311
className?: string;
1412
disabled?: boolean;
13+
hideText?: boolean;
14+
operation: 'restart_service' | 'restart';
15+
targetNoun: 'Instance' | 'Cluster';
16+
tooltip?: string;
1517
}
1618

1719
export function RestartButton({
18-
targetNoun,
20+
className,
21+
disabled,
22+
hideText,
1923
instanceClient,
2024
operation,
25+
targetNoun,
26+
tooltip,
2127
variant,
22-
className,
23-
disabled,
2428
}: RestartButtonParams) {
2529
const {
2630
onRestartClick: onRestartClusterClick,
@@ -36,13 +40,15 @@ export function RestartButton({
3640
disabled={disabled || isRestartPending || isRestartClusterPending}
3741
>
3842
<RotateCcwIcon />
39-
<span className="hidden md:inline-block">Restart {targetNoun}</span>
43+
{hideText !== true && <span className="hidden md:inline-block">Restart {targetNoun}</span>}
4044
</Button>
4145
</TooltipTrigger>
4246
<TooltipContent>
43-
{operation === 'restart_service'
44-
? 'Restarts all service threads to apply changes. No downtime expected. Performance may be briefly slower during restart.'
45-
: 'This fully restarts the Harper service and causes downtime.'}
47+
{tooltip
48+
? tooltip
49+
: operation === 'restart_service'
50+
? 'Restarts all service threads to apply changes. No downtime expected. Performance may be briefly slower during restart.'
51+
: 'This fully restarts the Harper service and causes downtime.'}
4652
</TooltipContent>
4753
</Tooltip>);
4854
}

src/features/instance/InstanceNavBar.tsx

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,14 @@ import {
88
DropdownMenuSeparator,
99
DropdownMenuTrigger,
1010
} from '@/components/ui/dropdownMenu';
11+
import { useInstanceClientIdParams } from '@/config/useInstanceClient';
1112
import { RegistrationInfoResponse } from '@/features/instance/operations/queries/getRegistrationInfo';
13+
import { getStatusQueryOptions } from '@/features/instance/operations/queries/getStatus';
1214
import { useInstanceManagePermission } from '@/hooks/usePermissions';
1315
import { excludeFalsy } from '@/lib/arrays/excludeFalsy';
1416
import { wasAReleasedBeforeB } from '@/lib/string/wasAReleasedBeforeB';
1517
import { buildAbsoluteLinkToPage } from '@/lib/urls/buildAbsoluteLinkToPage';
18+
import { useQuery } from '@tanstack/react-query';
1619
import { Link, useLoaderData, useParams } from '@tanstack/react-router';
1720
import { DatabaseIcon, GaugeIcon, Menu, NotepadTextIcon, PackageIcon, ServerIcon, SettingsIcon } from 'lucide-react';
1821
import { ReactNode, useMemo } from 'react';
@@ -30,6 +33,11 @@ export function InstanceNavBar() {
3033
const canManage = useInstanceManagePermission();
3134
const params = useParams({ strict: false });
3235

36+
37+
const instanceParams = useInstanceClientIdParams();
38+
const { data: statusData } = useQuery(getStatusQueryOptions(instanceParams));
39+
const restartRequired = statusData?.restartRequired ?? false;
40+
3341
const { version }: RegistrationInfoResponse = useLoaderData({ strict: false });
3442
const apisAvailable = wasAReleasedBeforeB('4.7.0-beta.7', version);
3543
const statusAvailable = wasAReleasedBeforeB('4.6.0', version);
@@ -70,16 +78,16 @@ export function InstanceNavBar() {
7078
].filter(excludeFalsy) satisfies Link[], [canManage, params, apisAvailable, statusAvailable]);
7179
return (
7280
<>
73-
<DesktopInstanceNavBar links={links} />
74-
<MobileInstanceNavBar links={links} />
81+
<DesktopInstanceNavBar links={links} restartRequired={restartRequired} />
82+
<MobileInstanceNavBar links={links} restartRequired={restartRequired} />
7583
</>
7684
);
7785
}
7886

79-
function DesktopInstanceNavBar({ links }: { links: Link[] }) {
87+
function DesktopInstanceNavBar({ links, restartRequired }: { links: Link[], restartRequired: boolean }) {
8088
return (
8189
<div className="hidden md:flex items-center justify-between h-full text-sm text-white">
82-
<Breadcrumbs />
90+
<Breadcrumbs restartRequired={restartRequired} />
8391
<div className="flex space-x-2">
8492
{links.map(({ shortName, ...link }) => (
8593
<Link
@@ -100,11 +108,11 @@ function DesktopInstanceNavBar({ links }: { links: Link[] }) {
100108
);
101109
}
102110

103-
function MobileInstanceNavBar({ links }: { links: Link[] }) {
111+
function MobileInstanceNavBar({ links, restartRequired }: { links: Link[], restartRequired: boolean }) {
104112
return (
105113
<>
106114
<div className="flex md:hidden items-center justify-between p-2 text-white">
107-
<Breadcrumbs />
115+
<Breadcrumbs restartRequired={restartRequired} />
108116
<DropdownMenu>
109117
<DropdownMenuTrigger asChild>
110118
<Button variant="ghost" className="p-0">

src/features/instance/operations/queries/getStatus.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ interface StatusResponse {
4040
export function getStatusQueryOptions({ entityId, instanceClient }: InstanceClientIdConfig) {
4141
return queryOptions({
4242
queryKey: [entityId, 'get_status'] as const,
43+
staleTime: 9_000,
44+
refetchInterval: 10_000,
45+
retryDelay: 10_000,
46+
throwOnError: false,
4347
queryFn: async () => {
4448
const { data } = await instanceClient.post<StatusResponse>('/', {
4549
operation: 'get_status',

0 commit comments

Comments
 (0)