diff --git a/app/Http/Middleware/HandleInertiaRequests.php b/app/Http/Middleware/HandleInertiaRequests.php
index 91df4dd8..9c9d05ce 100644
--- a/app/Http/Middleware/HandleInertiaRequests.php
+++ b/app/Http/Middleware/HandleInertiaRequests.php
@@ -76,6 +76,7 @@ public function share(Request $request): array
...parent::share($request),
...$data,
'name' => config('app.name'),
+ 'version' => config('app.version'),
'quote' => ['message' => trim($message), 'author' => trim($author)],
'auth' => [
'user' => $user,
diff --git a/resources/js/components/app-command.tsx b/resources/js/components/app-command.tsx
index e942e564..d60881d7 100644
--- a/resources/js/components/app-command.tsx
+++ b/resources/js/components/app-command.tsx
@@ -21,7 +21,7 @@ export default function AppCommand() {
return (
-
-
+
);
}
diff --git a/resources/js/components/app-sidebar.tsx b/resources/js/components/app-sidebar.tsx
index 8ea67c39..3bfda8d1 100644
--- a/resources/js/components/app-sidebar.tsx
+++ b/resources/js/components/app-sidebar.tsx
@@ -12,12 +12,13 @@ import {
SidebarMenuSub,
SidebarMenuSubItem,
} from '@/components/ui/sidebar';
-import { type NavItem } from '@/types';
-import { Link } from '@inertiajs/react';
+import { type NavItem, SharedData } from '@/types';
+import { Link, usePage } from '@inertiajs/react';
import { BookOpen, ChevronRightIcon, CogIcon, Folder, MousePointerClickIcon, ServerIcon, ZapIcon } from 'lucide-react';
import AppLogo from './app-logo';
import { Icon } from '@/components/icon';
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
+import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip';
const mainNavItems: NavItem[] = [
{
@@ -56,6 +57,8 @@ const footerNavItems: NavItem[] = [
];
export function AppSidebar({ secondNavItems, secondNavTitle }: { secondNavItems?: NavItem[]; secondNavTitle?: string }) {
+ const page = usePage();
+
return (
{/* This is the first sidebar */}
@@ -67,7 +70,12 @@ export function AppSidebar({ secondNavItems, secondNavTitle }: { secondNavItems?
-
+
+
+
+
+ {page.props.version}
+
diff --git a/resources/js/components/refresh.tsx b/resources/js/components/refresh.tsx
new file mode 100644
index 00000000..17c79307
--- /dev/null
+++ b/resources/js/components/refresh.tsx
@@ -0,0 +1,71 @@
+import { DropdownMenu, DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from '@/components/ui/dropdown-menu';
+import { Button } from '@/components/ui/button';
+import { ChevronDownIcon, RefreshCwIcon } from 'lucide-react';
+import { useEffect, useState } from 'react';
+import { router } from '@inertiajs/react';
+
+export default function Refresh() {
+ const [poll, setPoll] = useState<{
+ stop: VoidFunction;
+ start: VoidFunction;
+ }>();
+ const [polling, setPolling] = useState(false);
+ const storedInterval = (localStorage.getItem('refresh_interval') as '5' | '10' | '30' | '60' | '0') || '10';
+ const [refreshInterval, setRefreshInterval] = useState<5 | 10 | 30 | 60 | 0>(
+ storedInterval === '0' ? 0 : (parseInt(storedInterval) as 5 | 10 | 30 | 60),
+ );
+ const intervalLabels = {
+ 5: '5s',
+ 10: '10s',
+ 30: '30s',
+ 60: '1m',
+ 0: 'OFF',
+ };
+
+ const refresh = () => {
+ router.reload({
+ onStart: () => {
+ setPolling(true);
+ },
+ onFinish: () => {
+ setPolling(false);
+ },
+ });
+ };
+
+ useEffect(() => {
+ poll?.stop();
+ if (refreshInterval > 0) {
+ setPoll(router.poll(refreshInterval * 1000));
+ } else {
+ poll?.stop();
+ setPoll(undefined);
+ }
+ localStorage.setItem('refresh_interval', refreshInterval.toString());
+ // eslint-disable-next-line react-hooks/exhaustive-deps
+ }, [refreshInterval]);
+
+ return (
+
+
+ {polling ? : }
+ Refresh
+
+
+
+
+ {intervalLabels[refreshInterval] || 'Unknown'}
+
+
+
+
+ setRefreshInterval(5)}>5s
+ setRefreshInterval(10)}>10s
+ setRefreshInterval(30)}>30s
+ setRefreshInterval(60)}>1m
+ setRefreshInterval(0)}>OFF
+
+
+
+ );
+}
diff --git a/resources/js/layouts/auth/layout.tsx b/resources/js/layouts/auth/layout.tsx
index d1f7a8ef..d19de12a 100644
--- a/resources/js/layouts/auth/layout.tsx
+++ b/resources/js/layouts/auth/layout.tsx
@@ -1,6 +1,7 @@
import AppLogoIcon from '@/components/app-logo-icon';
-import { Link } from '@inertiajs/react';
+import { Link, usePage } from '@inertiajs/react';
import { type PropsWithChildren } from 'react';
+import { SharedData } from '@/types';
interface AuthLayoutProps {
name?: string;
@@ -9,6 +10,7 @@ interface AuthLayoutProps {
}
export default function AuthLayout({ children, title, description }: PropsWithChildren) {
+ const page = usePage();
return (
@@ -27,6 +29,16 @@ export default function AuthLayout({ children, title, description }: PropsWithCh
{children}
+
diff --git a/resources/js/layouts/server/layout.tsx b/resources/js/layouts/server/layout.tsx
index 04aadfb4..f138918c 100644
--- a/resources/js/layouts/server/layout.tsx
+++ b/resources/js/layouts/server/layout.tsx
@@ -26,15 +26,13 @@ import { ReactNode } from 'react';
import { Server } from '@/types/server';
import ServerHeader from '@/pages/servers/components/header';
import Layout from '@/layouts/app/layout';
-import { usePage, usePoll } from '@inertiajs/react';
+import { usePage } from '@inertiajs/react';
import { Site } from '@/types/site';
import PHPIcon from '@/icons/php';
import NodeIcon from '@/icons/node';
import siteHelper from '@/lib/site-helper';
export default function ServerLayout({ children }: { children: ReactNode }) {
- usePoll(7000);
-
const page = usePage<{
server: Server;
site?: Site;
diff --git a/resources/js/types/index.d.ts b/resources/js/types/index.d.ts
index bc902c0f..672fd851 100644
--- a/resources/js/types/index.d.ts
+++ b/resources/js/types/index.d.ts
@@ -104,6 +104,7 @@ export interface Configs {
export interface SharedData {
name: string;
+ version: string;
quote: { message: string; author: string };
auth: Auth;
ziggy: Config & { location: string };