gitscope
← marzneshin / Diff · c778675 → c778675
Changes c778675..c778675
Patch commit
diff --git a/app/models/user.py b/app/models/user.py
index 77d65ae..af20174 100644
--- a/app/models/user.py
+++ b/app/models/user.py
@@ -127,6 +127,7 @@ class UserResponse(User):
service_ids: list[int]
subscription_url: str
owner_username: str
+ traffic_reset_at: datetime | None
model_config = ConfigDict(from_attributes=True)
diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json
index 9f560be..13efaf0 100644
--- a/dashboard/public/locales/en.json
+++ b/dashboard/public/locales/en.json
@@ -172,6 +172,10 @@
"created_at": "Created At",
"no_use": "No Use",
"data_limit_reached": "Data Limit Reached",
+ "sub_updated_at": "Last subscription update",
+ "sub_last_user_agent": "Last User Agent",
+ "traffic_reset_at": "Last traffic reset",
+ "lifetime_used_traffic": "Lifetime Used Traffic",
"note-placeholder": "Additional information about user",
"data_limit": "Data Limit",
"traffic": "Traffic",
diff --git a/dashboard/src/features/users/components/dialogs/settings/index.tsx b/dashboard/src/features/users/components/dialogs/settings/index.tsx
index 50f7aaf..2d5a13e 100644
--- a/dashboard/src/features/users/components/dialogs/settings/index.tsx
+++ b/dashboard/src/features/users/components/dialogs/settings/index.tsx
@@ -1,94 +1,81 @@
import {
- Button,
- ScrollArea,
- Tabs,
- TabsContent,
- TabsList,
- TabsTrigger,
- Awaiting,
- SettingsInfoSkeleton,
- SettingsDialogProps,
- SettingsDialog,
+ ScrollArea,
+ Tabs,
+ TabsContent,
+ TabsList,
+ TabsTrigger,
+ Awaiting,
+ SettingsInfoSkeleton,
+ SettingsDialogProps,
+ SettingsDialog,
} from "@marzneshin/components";
import {
- UserServicesTable,
- useUserUsageResetCmd,
- type UserType,
+ UserServicesTable,
+ type UserType,
} from "@marzneshin/features/users";
import { type FC } from "react";
import { useTranslation } from "react-i18next";
import {
- UserStatusEnableButton,
- SubscriptionActions,
- QRCodeSection,
- UserInfoTable,
+ SubscriptionActions,
+ QRCodeSection,
+ UserInfoTable,
} from "./user-info";
interface UsersSettingsDialogProps extends SettingsDialogProps {
- entity: UserType | null;
- onClose: () => void;
- isPending: boolean;
+ entity: UserType | null;
+ onClose: () => void;
+ isPending: boolean;
}
export const UsersSettingsDialog: FC<UsersSettingsDialogProps> = ({
- onOpenChange,
- open,
- entity,
- onClose,
- isPending,
+ onOpenChange,
+ open,
+ entity,
+ onClose,
+ isPending,
}) => {
- const { t } = useTranslation();
- const { mutate: resetUsage } = useUserUsageResetCmd();
+ const { t } = useTranslation();
- return (
- <SettingsDialog open={open} onClose={onClose} onOpenChange={onOpenChange}>
- <Awaiting
- Component={
- entity ? (
- <ScrollArea className="flex flex-col gap-4 h-full">
- <Tabs defaultValue="info" className="w-full h-full">
- <TabsList className="w-full bg-accent">
- <TabsTrigger className="w-full" value="info">
- {t("user_info")}
- </TabsTrigger>
- <TabsTrigger className="w-full" value="services">
- {t("services")}
- </TabsTrigger>
- <TabsTrigger className="w-full" value="subscription">
- {t("subscription")}
- </TabsTrigger>
- </TabsList>
- <TabsContent
- value="info"
- className="flex flex-col gap-2 w-full h-full"
- >
- <UserInfoTable user={entity} />
- <div className="hstack justify-center items-center gap-2">
- <Button
- className="w-1/2"
- onClick={() => resetUsage(entity)}
- >
- {t("page.users.reset_usage")}
- </Button>
- <UserStatusEnableButton user={entity} />
- </div>
- </TabsContent>
- <TabsContent value="services">
- <UserServicesTable user={entity} />
- </TabsContent>
- <TabsContent value="subscription">
- <QRCodeSection entity={entity} />
- <SubscriptionActions entity={entity} />
- </TabsContent>
- </Tabs>
- </ScrollArea>
- ) : (
- <div>Not Found</div>
- )
- }
- Skeleton={<SettingsInfoSkeleton />}
- isFetching={isPending}
- />
- </SettingsDialog>
- );
+ return (
+ <SettingsDialog open={open} onClose={onClose} onOpenChange={onOpenChange}>
+ <Awaiting
+ Component={
+ entity ? (
+ <ScrollArea className="flex flex-col gap-4 h-full">
+ <Tabs defaultValue="info" className="w-full h-full">
+ <TabsList className="w-full bg-accent">
+ <TabsTrigger className="w-full" value="info">
+ {t("user_info")}
+ </TabsTrigger>
+ <TabsTrigger className="w-full" value="services">
+ {t("services")}
+ </TabsTrigger>
+ <TabsTrigger className="w-full" value="subscription">
+ {t("subscription")}
+ </TabsTrigger>
+ </TabsList>
+ <TabsContent
+ value="info"
+ className="flex flex-col gap-2 w-full h-full"
+ >
+ <UserInfoTable user={entity} />
+ </TabsContent>
+ <TabsContent value="services">
+ <UserServicesTable user={entity} />
+ </TabsContent>
+ <TabsContent value="subscription">
+ <QRCodeSection entity={entity} />
+ <SubscriptionActions entity={entity} />
+ </TabsContent>
+ </Tabs>
+ </ScrollArea>
+ ) : (
+ <div>Not Found</div>
+ )
+ }
+ Skeleton={<SettingsInfoSkeleton />}
+ isFetching={isPending}
+ />
+ </SettingsDialog>
+ );
};
diff --git a/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx b/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx
index fbbc841..aafc29e 100644
--- a/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx
+++ b/dashboard/src/features/users/components/dialogs/settings/user-info/table.tsx
@@ -1,5 +1,6 @@
import {
Card,
+ Button,
CardContent,
CardHeader,
CardTitle,
@@ -10,24 +11,41 @@ import {
Badge
} from "@marzneshin/components";
import type { FC } from "react";
+import { UserStatusEnableButton } from "./user-status-enable-button";
import {
type UserProp,
UserEnabledPill,
UserUsedTraffic,
UserActivatedPill,
+ useUserUsageResetCmd,
UserDataLimitReachedPill,
UserExpiredPill,
} from "@marzneshin/features/users";
import { useTranslation } from "react-i18next";
+import { formatDistanceToNow } from "date-fns";
+import { format } from '@chbphone55/pretty-bytes';
+import { TimerReset } from "lucide-react";
export const UserInfoTable: FC<UserProp> = ({ user: entity }) => {
const { t } = useTranslation();
const expireDate = entity.expire_date ? new Date(entity.expire_date) : null;
+ const { mutate: resetUsage } = useUserUsageResetCmd();
+ const lifetimeUsedTrafficByte = format(entity.lifetime_used_traffic);
return (
<Card>
<CardHeader className="flex flex-row justify-between items-center w-full">
<CardTitle>{t("user_info")}</CardTitle>
+ <div className="hstack justify-center items-center gap-2">
+ <Button
+ className="rounded-2xl"
+ onClick={() => resetUsage(entity)}
+ >
+ <TimerReset className="mr-2" />
+ <span>{t("page.users.reset_usage")}</span>
+ </Button>
+ <UserStatusEnableButton user={entity} />
+ </div>
</CardHeader>
<CardContent>
<Table>
@@ -80,16 +98,23 @@ export const UserInfoTable: FC<UserProp> = ({ user: entity }) => {
),
}[entity.expire_strategy]
}
-
<TableRowWithCell
label={t("page.users.used_traffic")}
value={<UserUsedTraffic user={entity} />}
/>
+ <DateTableRow
+ label={t("page.users.traffic_reset_at")}
+ date={entity.traffic_reset_at}
+ withTime
+ />
+ <TableRowWithCell
+ label={t("page.users.lifetime_used_traffic")}
+ value={`${lifetimeUsedTrafficByte[0]} ${lifetimeUsedTrafficByte[1]}`}
+ />
{entity.online_at ? (
- <DateTableRow
+ <TableRowWithCell
label={t("page.users.online_at")}
- date={entity.online_at}
- withTime
+ value={formatDistanceToNow(new Date(entity.online_at + "Z")) + " ago"}
/>
) : (
<TableRowWithCell
@@ -97,9 +122,19 @@ export const UserInfoTable: FC<UserProp> = ({ user: entity }) => {
value={<Badge>{t("page.users.no_use")}</Badge>}
/>
)}
+ <DateTableRow
+ label={t("page.users.sub_updated_at")}
+ date={entity.sub_updated_at}
+ withTime
+ />
+ <TableRowWithCell
+ label={t("page.users.sub_last_user_agent")}
+ value={entity.sub_last_user_agent}
+ />
<DateTableRow
label={t("page.users.created_at")}
date={entity.created_at}
+ withTime
/>
<TableRowWithCell label={t("note")} value={entity.note} />
</TableBody>
diff --git a/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx b/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx
index 189f043..9d8c876 100644
--- a/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx
+++ b/dashboard/src/features/users/components/dialogs/settings/user-info/user-status-enable-button.tsx
@@ -1,7 +1,7 @@
import { Button } from "@marzneshin/components";
import { cn } from "@marzneshin/utils";
import { UserType, useUserStatusEnable } from "@marzneshin/features/users";
-import { LoaderIcon } from "lucide-react";
+import { LoaderIcon, UserCheck, UserX } from "lucide-react";
import { FC, useCallback, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -20,13 +20,14 @@ export const UserStatusEnableButton: FC<UserStatusEnableButtonProps> = ({ user }
setUserStatus(!tempUserStatus)
}, [user, userStatus, userStatusEnable]);
- const bgColor = isPending ? 'bg-muted-foreground' : (userStatus ? 'bg-red-400' : 'bg-green-400')
+ const bgColor = isPending ? 'bg-muted-foreground' : (userStatus ? 'bg-destructive' : 'bg-success')
return (
<Button
- className={cn(bgColor, "w-1/2")}
+ className={cn(bgColor, "rounded-2xl")}
onClick={handleUserStatusEnabledToggle}
>
+ {!userStatus ? <UserCheck className="mr-2" /> : <UserX className="mr-2" />}
{isPending ? <LoaderIcon className="animate-spin" /> : t(!userStatus ? 'enable' : 'disable')}
</Button>
)
diff --git a/dashboard/src/features/users/types/user.ts b/dashboard/src/features/users/types/user.ts
index a14644f..61848af 100644
--- a/dashboard/src/features/users/types/user.ts
+++ b/dashboard/src/features/users/types/user.ts
@@ -11,6 +11,7 @@ export interface UserType {
lifetime_used_traffic: number;
used_traffic: number;
sub_updated_at?: Date | string;
+ traffic_reset_at?: Date | string;
sub_last_user_agent?: string;
enabled: boolean;
activated: boolean;