Skip to content

Commit 8a741e4

Browse files
authored
Merge pull request #2933 from AathilFelix/feat/server-time-clock
feat: add server time clock in the dashboard
2 parents 19244a2 + 1581def commit 8a741e4

File tree

4 files changed

+91
-4
lines changed

4 files changed

+91
-4
lines changed

apps/dokploy/components/dashboard/projects/show.tsx

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import { useEffect, useMemo, useState } from "react";
1414
import { toast } from "sonner";
1515
import { BreadcrumbSidebar } from "@/components/shared/breadcrumb-sidebar";
1616
import { DateTooltip } from "@/components/shared/date-tooltip";
17+
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
1718
import { StatusTooltip } from "@/components/shared/status-tooltip";
1819
import {
1920
AlertDialog,
@@ -44,20 +45,21 @@ import {
4445
DropdownMenuSeparator,
4546
DropdownMenuTrigger,
4647
} from "@/components/ui/dropdown-menu";
47-
import { FocusShortcutInput } from "@/components/shared/focus-shortcut-input";
4848
import {
4949
Select,
5050
SelectContent,
5151
SelectItem,
5252
SelectTrigger,
5353
SelectValue,
5454
} from "@/components/ui/select";
55+
import { TimeBadge } from "@/components/ui/time-badge";
5556
import { api } from "@/utils/api";
5657
import { HandleProject } from "./handle-project";
5758
import { ProjectEnvironment } from "./project-environment";
5859

5960
export const ShowProjects = () => {
6061
const utils = api.useUtils();
62+
const { data: isCloud } = api.settings.isCloud.useQuery();
6163
const { data, isLoading } = api.project.all.useQuery();
6264
const { data: auth } = api.user.get.useQuery();
6365
const { mutateAsync } = api.project.remove.useMutation();
@@ -135,6 +137,11 @@ export const ShowProjects = () => {
135137
<BreadcrumbSidebar
136138
list={[{ name: "Projects", href: "/dashboard/projects" }]}
137139
/>
140+
{!isCloud && (
141+
<div className="absolute top-5 right-5">
142+
<TimeBadge />
143+
</div>
144+
)}
138145
<div className="w-full">
139146
<Card className="h-full bg-sidebar p-2.5 rounded-xl ">
140147
<div className="rounded-xl bg-background shadow-md ">
@@ -148,7 +155,6 @@ export const ShowProjects = () => {
148155
Create and manage your projects
149156
</CardDescription>
150157
</CardHeader>
151-
152158
{(auth?.role === "owner" || auth?.canCreateProjects) && (
153159
<div className="">
154160
<HandleProject />
@@ -298,7 +304,13 @@ export const ShowProjects = () => {
298304
<Link
299305
className="space-x-4 text-xs cursor-pointer justify-between"
300306
target="_blank"
301-
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
307+
href={`${
308+
domain.https
309+
? "https"
310+
: "http"
311+
}://${domain.host}${
312+
domain.path
313+
}`}
302314
>
303315
<span className="truncate">
304316
{domain.host}
@@ -340,7 +352,13 @@ export const ShowProjects = () => {
340352
<Link
341353
className="space-x-4 text-xs cursor-pointer justify-between"
342354
target="_blank"
343-
href={`${domain.https ? "https" : "http"}://${domain.host}${domain.path}`}
355+
href={`${
356+
domain.https
357+
? "https"
358+
: "http"
359+
}://${domain.host}${
360+
domain.path
361+
}`}
344362
>
345363
<span className="truncate">
346364
{domain.host}

apps/dokploy/components/layouts/side.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ import { AddOrganization } from "../dashboard/organization/handle-organization";
8383
import { DialogAction } from "../shared/dialog-action";
8484
import { Logo } from "../shared/logo";
8585
import { Button } from "../ui/button";
86+
import { TimeBadge } from "../ui/time-badge";
8687
import { UpdateServerButton } from "./update-server";
8788
import { UserNav } from "./user-nav";
8889

@@ -1125,6 +1126,7 @@ export default function Page({ children }: Props) {
11251126
</BreadcrumbList>
11261127
</Breadcrumb>
11271128
</div>
1129+
{!isCloud && <TimeBadge />}
11281130
</div>
11291131
</header>
11301132
)}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { api } from "@/utils/api";
5+
6+
export function TimeBadge() {
7+
const { data: serverTime } = api.server.getServerTime.useQuery(undefined);
8+
const [time, setTime] = useState<Date | null>(null);
9+
10+
useEffect(() => {
11+
if (serverTime?.time) {
12+
setTime(new Date(serverTime.time));
13+
}
14+
}, [serverTime]);
15+
16+
useEffect(() => {
17+
const timer = setInterval(() => {
18+
setTime((prevTime) => {
19+
if (!prevTime) return null;
20+
const newTime = new Date(prevTime.getTime() + 1000);
21+
return newTime;
22+
});
23+
}, 1000);
24+
25+
return () => {
26+
clearInterval(timer);
27+
};
28+
}, []);
29+
30+
if (!time || !serverTime?.timezone) {
31+
return null;
32+
}
33+
34+
const getUtcOffset = (timeZone: string) => {
35+
const date = new Date();
36+
const utcDate = new Date(date.toLocaleString("en-US", { timeZone: "UTC" }));
37+
const tzDate = new Date(date.toLocaleString("en-US", { timeZone }));
38+
const offset = (tzDate.getTime() - utcDate.getTime()) / (1000 * 60 * 60);
39+
const sign = offset >= 0 ? "+" : "-";
40+
const hours = Math.floor(Math.abs(offset));
41+
const minutes = (Math.abs(offset) * 60) % 60;
42+
return `UTC${sign}${hours.toString().padStart(2, "0")}:${minutes
43+
.toString()
44+
.padStart(2, "0")}`;
45+
};
46+
47+
return (
48+
<div className="inline-flex items-center gap-2 rounded-md border px-2 py-1 text-xs sm:text-sm whitespace-nowrap max-w-full overflow-hidden">
49+
<span className="hidden sm:inline">Server Time:</span>
50+
<span className="font-medium tabular-nums">
51+
{time.toLocaleTimeString()}
52+
</span>
53+
<span className="hidden sm:inline text-muted-foreground">
54+
({serverTime.timezone} | {getUtcOffset(serverTime.timezone)})
55+
</span>
56+
</div>
57+
);
58+
}

apps/dokploy/server/api/routers/server.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,15 @@ export const serverRouter = createTRPCRouter({
383383
const ip = await getPublicIpWithFallback();
384384
return ip;
385385
}),
386+
getServerTime: protectedProcedure.query(() => {
387+
if (IS_CLOUD) {
388+
return null;
389+
}
390+
return {
391+
time: new Date(),
392+
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
393+
};
394+
}),
386395
getServerMetrics: protectedProcedure
387396
.input(
388397
z.object({

0 commit comments

Comments
 (0)