Skip to content

Commit dd14e69

Browse files
authored
Add auto refresh dropdown (#620)
1 parent 736e27f commit dd14e69

8 files changed

Lines changed: 107 additions & 9 deletions

File tree

app/Http/Middleware/HandleInertiaRequests.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,7 @@ public function share(Request $request): array
7676
...parent::share($request),
7777
...$data,
7878
'name' => config('app.name'),
79+
'version' => config('app.version'),
7980
'quote' => ['message' => trim($message), 'author' => trim($author)],
8081
'auth' => [
8182
'user' => $user,

resources/js/components/app-command.tsx

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,17 @@ export default function AppCommand() {
2121

2222
return (
2323
<div>
24-
<Button className="px-1!" variant="outline" size="sm" onClick={() => setOpen(true)}>
24+
<Button className="hidden px-1! lg:flex" variant="outline" size="sm" onClick={() => setOpen(true)}>
2525
<span className="sr-only">Open command menu</span>
2626
<SearchIcon className="ml-1 size-3" />
2727
Search...
2828
<span className="bg-accent flex h-6 items-center justify-center rounded-sm border px-2 text-xs">
2929
<CommandIcon className="mr-1 size-3" /> K
3030
</span>
3131
</Button>
32+
<Button className="lg:hidden" variant="outline" size="sm" onClick={() => setOpen(true)}>
33+
<CommandIcon className="mr-1 size-3" /> K
34+
</Button>
3235
<CommandDialog open={open} onOpenChange={setOpen}>
3336
<CommandInput placeholder="Type a command or search..." />
3437
<CommandList>

resources/js/components/app-header.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import AppCommand from '@/components/app-command';
66
import { SiteSwitch } from '@/components/site-switch';
77
import { usePage } from '@inertiajs/react';
88
import { SharedData } from '@/types';
9+
import Refresh from '@/components/refresh';
910

1011
export function AppHeader() {
1112
const page = usePage<SharedData>();
@@ -26,7 +27,10 @@ export function AppHeader() {
2627
)}
2728
</div>
2829
</div>
29-
<AppCommand />
30+
<div className="flex items-center gap-2">
31+
<AppCommand />
32+
<Refresh />
33+
</div>
3034
</header>
3135
);
3236
}

resources/js/components/app-sidebar.tsx

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,12 +12,13 @@ import {
1212
SidebarMenuSub,
1313
SidebarMenuSubItem,
1414
} from '@/components/ui/sidebar';
15-
import { type NavItem } from '@/types';
16-
import { Link } from '@inertiajs/react';
15+
import { type NavItem, SharedData } from '@/types';
16+
import { Link, usePage } from '@inertiajs/react';
1717
import { BookOpen, ChevronRightIcon, CogIcon, Folder, MousePointerClickIcon, ServerIcon, ZapIcon } from 'lucide-react';
1818
import AppLogo from './app-logo';
1919
import { Icon } from '@/components/icon';
2020
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
21+
import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
2122

2223
const mainNavItems: NavItem[] = [
2324
{
@@ -56,6 +57,8 @@ const footerNavItems: NavItem[] = [
5657
];
5758

5859
export function AppSidebar({ secondNavItems, secondNavTitle }: { secondNavItems?: NavItem[]; secondNavTitle?: string }) {
60+
const page = usePage<SharedData>();
61+
5962
return (
6063
<Sidebar collapsible="icon" className="overflow-hidden [&>[data-sidebar=sidebar]]:flex-row">
6164
{/* This is the first sidebar */}
@@ -67,7 +70,12 @@ export function AppSidebar({ secondNavItems, secondNavTitle }: { secondNavItems?
6770
<SidebarMenuItem>
6871
<SidebarMenuButton size="lg" asChild className="md:h-8 md:p-0">
6972
<Link href={route('servers')} prefetch>
70-
<AppLogo />
73+
<Tooltip>
74+
<TooltipTrigger>
75+
<AppLogo />
76+
</TooltipTrigger>
77+
<TooltipContent side="right">{page.props.version}</TooltipContent>
78+
</Tooltip>
7179
</Link>
7280
</SidebarMenuButton>
7381
</SidebarMenuItem>
Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
2+
import { Button } from '@/components/ui/button';
3+
import { ChevronDownIcon, RefreshCwIcon } from 'lucide-react';
4+
import { useEffect, useState } from 'react';
5+
import { router } from '@inertiajs/react';
6+
7+
export default function Refresh() {
8+
const [poll, setPoll] = useState<{
9+
stop: VoidFunction;
10+
start: VoidFunction;
11+
}>();
12+
const [polling, setPolling] = useState(false);
13+
const storedInterval = (localStorage.getItem('refresh_interval') as '5' | '10' | '30' | '60' | '0') || '10';
14+
const [refreshInterval, setRefreshInterval] = useState<5 | 10 | 30 | 60 | 0>(
15+
storedInterval === '0' ? 0 : (parseInt(storedInterval) as 5 | 10 | 30 | 60),
16+
);
17+
const intervalLabels = {
18+
5: '5s',
19+
10: '10s',
20+
30: '30s',
21+
60: '1m',
22+
0: 'OFF',
23+
};
24+
25+
const refresh = () => {
26+
router.reload({
27+
onStart: () => {
28+
setPolling(true);
29+
},
30+
onFinish: () => {
31+
setPolling(false);
32+
},
33+
});
34+
};
35+
36+
useEffect(() => {
37+
poll?.stop();
38+
if (refreshInterval > 0) {
39+
setPoll(router.poll(refreshInterval * 1000));
40+
} else {
41+
poll?.stop();
42+
setPoll(undefined);
43+
}
44+
localStorage.setItem('refresh_interval', refreshInterval.toString());
45+
// eslint-disable-next-line react-hooks/exhaustive-deps
46+
}, [refreshInterval]);
47+
48+
return (
49+
<div className="flex items-center">
50+
<Button variant="outline" size="sm" className="md:rounded-r-none" onClick={refresh} disabled={polling}>
51+
{polling ? <RefreshCwIcon className="animate-spin" /> : <RefreshCwIcon className="lg:hidden" />}
52+
<span className="hidden md:block">Refresh</span>
53+
</Button>
54+
<DropdownMenu>
55+
<DropdownMenuTrigger asChild>
56+
<Button variant="outline" size="sm" className="hidden rounded-l-none border-l-0 md:flex">
57+
{intervalLabels[refreshInterval] || 'Unknown'}
58+
<ChevronDownIcon />
59+
</Button>
60+
</DropdownMenuTrigger>
61+
<DropdownMenuContent align="end">
62+
<DropdownMenuItem onSelect={() => setRefreshInterval(5)}>5s</DropdownMenuItem>
63+
<DropdownMenuItem onSelect={() => setRefreshInterval(10)}>10s</DropdownMenuItem>
64+
<DropdownMenuItem onSelect={() => setRefreshInterval(30)}>30s</DropdownMenuItem>
65+
<DropdownMenuItem onSelect={() => setRefreshInterval(60)}>1m</DropdownMenuItem>
66+
<DropdownMenuItem onSelect={() => setRefreshInterval(0)}>OFF</DropdownMenuItem>
67+
</DropdownMenuContent>
68+
</DropdownMenu>
69+
</div>
70+
);
71+
}

resources/js/layouts/auth/layout.tsx

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import AppLogoIcon from '@/components/app-logo-icon';
2-
import { Link } from '@inertiajs/react';
2+
import { Link, usePage } from '@inertiajs/react';
33
import { type PropsWithChildren } from 'react';
4+
import { SharedData } from '@/types';
45

56
interface AuthLayoutProps {
67
name?: string;
@@ -9,6 +10,7 @@ interface AuthLayoutProps {
910
}
1011

1112
export default function AuthLayout({ children, title, description }: PropsWithChildren<AuthLayoutProps>) {
13+
const page = usePage<SharedData>();
1214
return (
1315
<div className="bg-background flex min-h-svh flex-col items-center justify-center gap-6 p-6 md:p-10">
1416
<div className="w-full max-w-sm">
@@ -27,6 +29,16 @@ export default function AuthLayout({ children, title, description }: PropsWithCh
2729
</div>
2830
</div>
2931
{children}
32+
<div className="text-muted-foreground/50 text-center text-xs">
33+
VitoDeploy{' '}
34+
<a
35+
href={`https://github.com/vitodeploy/vito/releases/tag/${page.props.version}`}
36+
className="hover:text-primary cursor-pointer"
37+
target="_blank"
38+
>
39+
{page.props.version}
40+
</a>
41+
</div>
3042
</div>
3143
</div>
3244
</div>

resources/js/layouts/server/layout.tsx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -26,15 +26,13 @@ import { ReactNode } from 'react';
2626
import { Server } from '@/types/server';
2727
import ServerHeader from '@/pages/servers/components/header';
2828
import Layout from '@/layouts/app/layout';
29-
import { usePage, usePoll } from '@inertiajs/react';
29+
import { usePage } from '@inertiajs/react';
3030
import { Site } from '@/types/site';
3131
import PHPIcon from '@/icons/php';
3232
import NodeIcon from '@/icons/node';
3333
import siteHelper from '@/lib/site-helper';
3434

3535
export default function ServerLayout({ children }: { children: ReactNode }) {
36-
usePoll(7000);
37-
3836
const page = usePage<{
3937
server: Server;
4038
site?: Site;

resources/js/types/index.d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ export interface Configs {
104104

105105
export interface SharedData {
106106
name: string;
107+
version: string;
107108
quote: { message: string; author: string };
108109
auth: Auth;
109110
ziggy: Config & { location: string };

0 commit comments

Comments
 (0)