diff --git a/app/db/crud.py b/app/db/crud.py index ce2876b..7f4d31f 100644 --- a/app/db/crud.py +++ b/app/db/crud.py @@ -14,7 +14,6 @@ from app.db.models import ( TLS, Admin, Node, - NodeUsage, NodeUserUsage, NotificationReminder, InboundHost, @@ -29,7 +28,6 @@ from app.models.node import ( NodeCreate, NodeModify, NodeStatus, - NodeUsageResponse, ) from app.models.proxy import InboundHost as InboundHostModify from app.models.service import Service as ServiceModify, ServiceCreate @@ -759,29 +757,44 @@ def get_nodes( return query.all() -def get_nodes_usage( - db: Session, start: datetime, end: datetime -) -> List[NodeUsageResponse]: - usages = dict() +def get_node_usage( + db: Session, start: datetime, end: datetime, node: Node +) -> TrafficUsageSeries: + usages = defaultdict(int) - usages[0] = NodeUsageResponse( # Main Core - node_id=None, node_name="Master", uplink=0, downlink=0 + query = ( + db.query( + NodeUserUsage.created_at, func.sum(NodeUserUsage.used_traffic) + ) + .group_by(NodeUserUsage.created_at) + .filter( + and_( + NodeUserUsage.node_id == node.id, + NodeUserUsage.created_at >= start, + NodeUserUsage.created_at <= end, + ) + ) ) - for node in db.query(Node).all(): - usages[node.id] = NodeUsageResponse( - node_id=node.id, node_name=node.name, uplink=0, downlink=0 + + for created_at, used_traffic in query.all(): + usages[created_at.replace(tzinfo=timezone.utc).timestamp()] += int( + used_traffic ) - cond = and_(NodeUsage.created_at >= start, NodeUsage.created_at <= end) + result = TrafficUsageSeries(usages=[], total=0) + current = start.astimezone(timezone.utc).replace( + minute=0, second=0, microsecond=0 + ) - for v in db.query(NodeUsage).filter(cond): - try: - usages[v.node_id or 0].uplink += v.uplink - usages[v.node_id or 0].downlink += v.downlink - except KeyError: - pass + while current <= end.replace(tzinfo=timezone.utc): + usage = usages.get(current.timestamp()) or 0 + result.usages.append( + (int(current.timestamp()), usage) + ) + result.total += usage + current += timedelta(hours=1) - return list(usages.values()) + return result def create_node(db: Session, node: NodeCreate): diff --git a/app/routes/node.py b/app/routes/node.py index ea1f9a9..4c4720d 100644 --- a/app/routes/node.py +++ b/app/routes/node.py @@ -24,10 +24,10 @@ from app.models.node import ( NodeResponse, NodeSettings, NodeStatus, - NodesUsageResponse, BackendConfig, BackendStats, ) +from app.models.system import TrafficUsageSeries logger = logging.getLogger(__name__) router = APIRouter(prefix="/nodes", tags=["Node"]) @@ -75,21 +75,6 @@ def get_node_settings(db: DBDep, admin: SudoAdminDep): return NodeSettings(certificate=tls.certificate) -@router.get("/usage", response_model=NodesUsageResponse) -def get_usage( - db: DBDep, - admin: SudoAdminDep, - start_date: StartDateDep, - end_date: EndDateDep, -): - """ - Get nodes usage - """ - usages = crud.get_nodes_usage(db, start_date, end_date) - - return {"usages": usages} - - @router.get("/{node_id}", response_model=NodeResponse) def get_node(node_id: int, db: DBDep, admin: SudoAdminDep): db_node = crud.get_node_by_id(db, node_id) @@ -172,6 +157,24 @@ async def reconnect_node(node_id: int, db: DBDep, admin: SudoAdminDep): return {} +@router.get("/{node_id}/usage", response_model=TrafficUsageSeries) +def get_usage( + node_id: int, + db: DBDep, + admin: SudoAdminDep, + start_date: StartDateDep, + end_date: EndDateDep, +): + """ + Get nodes usage + """ + node = crud.get_node_by_id(db, node_id) + if not node: + raise HTTPException(status_code=404, detail="Node not found") + + return crud.get_node_usage(db, start_date, end_date, node) + + @router.get("/{node_id}/{backend}/stats", response_model=BackendStats) async def get_backend_stats( node_id: int, backend: str, db: DBDep, admin: SudoAdminDep diff --git a/dashboard/public/locales/en.json b/dashboard/public/locales/en.json index b365d3b..5695d75 100644 --- a/dashboard/public/locales/en.json +++ b/dashboard/public/locales/en.json @@ -1,5 +1,6 @@ { "name": "Name", + "info": "Info", "status": "Status", "address": "Address", "users": "Users", @@ -130,6 +131,10 @@ } }, "nodes": { + "nodes-usage-widget": { + "title": "Node Traffic Usage", + "desc": "Total traffic usage on this node" + }, "settings": { "no-backend-alert": { "title": "No backend is configured for this node", diff --git a/dashboard/src/common/stats-charts/hooks/index.ts b/dashboard/src/common/stats-charts/hooks/index.ts index 2aac369..10cc07d 100644 --- a/dashboard/src/common/stats-charts/hooks/index.ts +++ b/dashboard/src/common/stats-charts/hooks/index.ts @@ -1 +1,2 @@ export * from "./use-from-now-interval" +export * from "./use-transform-date-usage-data" diff --git a/dashboard/src/features/total-traffic-widget/hooks/use-transform-data.ts b/dashboard/src/common/stats-charts/hooks/use-transform-date-usage-data.ts similarity index 50% rename from dashboard/src/features/total-traffic-widget/hooks/use-transform-data.ts rename to dashboard/src/common/stats-charts/hooks/use-transform-date-usage-data.ts index 59c4afb..f70a6b9 100644 --- a/dashboard/src/features/total-traffic-widget/hooks/use-transform-data.ts +++ b/dashboard/src/common/stats-charts/hooks/use-transform-date-usage-data.ts @@ -1,13 +1,6 @@ -import { UsageMetric } from "../api"; +import { UsageMetric, ChartData } from "./../types"; -export type ChartDataEntry = { - datetime: string; - [key: string]: number | string; -}; - -export type ChartData = ChartDataEntry[]; - -export function useTransformData(usages: Array): ChartData { +export function useTransformDateUsageData(usages: Array): ChartData { return usages.map(usageData => { const [timestamp, usage] = usageData; const date = new Date(timestamp * 1000); diff --git a/dashboard/src/common/stats-charts/types.ts b/dashboard/src/common/stats-charts/types.ts index 481900c..55253ed 100644 --- a/dashboard/src/common/stats-charts/types.ts +++ b/dashboard/src/common/stats-charts/types.ts @@ -1,2 +1,10 @@ +export type UsageMetric = number[]; + +export type ChartDataEntry = { + datetime: string; + [key: string]: number | string; +}; + +export type ChartData = ChartDataEntry[]; export type ChartDateInterval = '90d' | '30d' | '7d' | '1d'; diff --git a/dashboard/src/features/inbounds/types/index.ts b/dashboard/src/features/inbounds/types/index.ts index 919a537..ae7ab40 100644 --- a/dashboard/src/features/inbounds/types/index.ts +++ b/dashboard/src/features/inbounds/types/index.ts @@ -1,4 +1,4 @@ -import { NodeType } from "@marzneshin/features/nodes"; +import { NodeType } from "@marzneshin/modules/nodes"; export type ProtocolType = 'vmess' | 'vless' | 'trojan' | 'shadowsocks'; diff --git a/dashboard/src/features/nodes/dialogs/settings/index.tsx b/dashboard/src/features/nodes/dialogs/settings/index.tsx deleted file mode 100644 index 16424b6..0000000 --- a/dashboard/src/features/nodes/dialogs/settings/index.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { - type SettingsDialogProps, - SettingsDialog, - Tabs, - TabsContent, - TabsList, - TabsTrigger, - AlertCard, -} from "@marzneshin/components"; -import type { FC } from "react"; -import { useTranslation } from "react-i18next"; -import type { NodeBackendType, NodeType } from "../.."; -import { NodesDetailTable } from "../../tables/detail-table"; -import { NodeBackendSetting } from "./node-backend-setting"; - -interface NodesSettingsDialogProps extends SettingsDialogProps { - entity: NodeType; - onClose: () => void; -} - -export const NodesSettingsDialog: FC = ({ - onOpenChange, - open, - entity, - onClose, -}) => { - const { t } = useTranslation(); - - return ( - -
-

- {t("page.nodes.settings.detail")} -

- -
- {entity.backends.length === 0 ? ( - - ) : ( - - - {...entity.backends.map((backend: NodeBackendType) => ( - - {backend.name} - - ))} - - {...entity.backends.map((backend: NodeBackendType) => ( - - - - ))} - - )} -
- ); -}; diff --git a/dashboard/src/features/settings/services/certificate.query.ts b/dashboard/src/features/settings/services/certificate.query.ts index ffe462b..69d3e1f 100644 --- a/dashboard/src/features/settings/services/certificate.query.ts +++ b/dashboard/src/features/settings/services/certificate.query.ts @@ -1,6 +1,6 @@ import { useQuery } from "@tanstack/react-query"; import { fetch } from "@marzneshin/utils"; -import { NodesQueryFetchKey } from "@marzneshin/features/nodes"; +import { NodesQueryFetchKey } from "@marzneshin/modules/nodes"; export async function fetchCertificate(): Promise { return fetch('/nodes/settings').then((settings) => { diff --git a/dashboard/src/features/subscription-settings/services/subscription-settings.mutate.ts b/dashboard/src/features/subscription-settings/services/subscription-settings.mutate.ts index cd21848..9f0bfec 100644 --- a/dashboard/src/features/subscription-settings/services/subscription-settings.mutate.ts +++ b/dashboard/src/features/subscription-settings/services/subscription-settings.mutate.ts @@ -1,4 +1,4 @@ -import { NodeType } from "@marzneshin/features/nodes"; +import { NodeType } from "@marzneshin/modules/nodes"; import { useMutation } from "@tanstack/react-query"; import { fetch, queryClient } from "@marzneshin/utils"; import { toast } from "sonner"; diff --git a/dashboard/src/features/total-traffic-widget/hooks/index.ts b/dashboard/src/features/total-traffic-widget/hooks/index.ts deleted file mode 100644 index d8f6e9a..0000000 --- a/dashboard/src/features/total-traffic-widget/hooks/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./use-transform-data" diff --git a/dashboard/src/features/total-traffic-widget/index.tsx b/dashboard/src/features/total-traffic-widget/index.tsx index df4e297..c223382 100644 --- a/dashboard/src/features/total-traffic-widget/index.tsx +++ b/dashboard/src/features/total-traffic-widget/index.tsx @@ -9,13 +9,13 @@ import { ChartConfig, } from "@marzneshin/components"; import { XAxis, BarChart, YAxis, CartesianGrid, Bar } from "recharts" -import { useTransformData } from "./hooks"; import { format as formatByte } from '@chbphone55/pretty-bytes'; import { useTotalTrafficQuery } from "./api"; import { UsageGraphSkeleton } from "./components"; import { ChartDateInterval, SelectDateView, + useTransformDateUsageData, dateXAxisTicks, useFromNowInterval } from "@marzneshin/common/stats-charts"; @@ -32,7 +32,7 @@ export const TotalTrafficsWidget: FC = () => { const [timeRange, setTimeRange] = useState("1d") const { start, end } = useFromNowInterval(timeRange as ChartDateInterval); const { data, isPending } = useTotalTrafficQuery({ start, end }) - const chartData = useTransformData(data.usages); + const chartData = useTransformDateUsageData(data.usages); const [totalAmount, totalMetric] = formatByte(data.total); return ( diff --git a/dashboard/src/features/nodes/dialogs/delete-confirmation/index.tsx b/dashboard/src/modules/nodes/dialogs/delete-confirmation/index.tsx similarity index 97% rename from dashboard/src/features/nodes/dialogs/delete-confirmation/index.tsx rename to dashboard/src/modules/nodes/dialogs/delete-confirmation/index.tsx index 2e76558..53f8a68 100644 --- a/dashboard/src/features/nodes/dialogs/delete-confirmation/index.tsx +++ b/dashboard/src/modules/nodes/dialogs/delete-confirmation/index.tsx @@ -1,7 +1,7 @@ import { DeleteConfirmation } from '@marzneshin/components' import { FC, useEffect } from 'react' -import { NodeType, useNodesDeletionMutation } from '@marzneshin/features/nodes' +import { NodeType, useNodesDeletionMutation } from '@marzneshin/modules/nodes' interface NodesDeleteConfirmationDialogProps { onOpenChange: (state: boolean) => void diff --git a/dashboard/src/features/nodes/dialogs/index.ts b/dashboard/src/modules/nodes/dialogs/index.ts similarity index 100% rename from dashboard/src/features/nodes/dialogs/index.ts rename to dashboard/src/modules/nodes/dialogs/index.ts diff --git a/dashboard/src/features/nodes/dialogs/mutation/index.tsx b/dashboard/src/modules/nodes/dialogs/mutation/index.tsx similarity index 100% rename from dashboard/src/features/nodes/dialogs/mutation/index.tsx rename to dashboard/src/modules/nodes/dialogs/mutation/index.tsx diff --git a/dashboard/src/modules/nodes/dialogs/settings/index.tsx b/dashboard/src/modules/nodes/dialogs/settings/index.tsx new file mode 100644 index 0000000..babc502 --- /dev/null +++ b/dashboard/src/modules/nodes/dialogs/settings/index.tsx @@ -0,0 +1,87 @@ +import { + type SettingsDialogProps, + SettingsDialog, + Tabs, + TabsContent, + TabsList, + TabsTrigger, + AlertCard, +} from "@marzneshin/components"; +import type { FC } from "react"; +import { useTranslation } from "react-i18next"; +import { + NodeBackendType, + NodeType, + NodesDetailTable, + NodesUsageWidget +} from "@marzneshin/modules/nodes"; +import { NodeBackendSetting } from "./node-backend-setting"; + +interface NodesSettingsDialogProps extends SettingsDialogProps { + entity: NodeType; + onClose: () => void; +} + +export const NodesSettingsDialog: FC = ({ + onOpenChange, + open, + entity, + onClose, +}) => { + const { t } = useTranslation(); + + return ( + + + + {t("config")} + {t("info")} + + + {entity.backends.length === 0 ? ( + + ) : ( + + + {...entity.backends.map((backend: NodeBackendType) => ( + + {backend.name} + + ))} + + {...entity.backends.map((backend: NodeBackendType) => ( + + + + ))} + + )} + + +
+

+ {t("page.nodes.settings.detail")} +

+ +
+ +
+
+
+ ); +}; diff --git a/dashboard/src/features/nodes/dialogs/settings/node-backend-setting.tsx b/dashboard/src/modules/nodes/dialogs/settings/node-backend-setting.tsx similarity index 100% rename from dashboard/src/features/nodes/dialogs/settings/node-backend-setting.tsx rename to dashboard/src/modules/nodes/dialogs/settings/node-backend-setting.tsx diff --git a/dashboard/src/features/nodes/dialogs/settings/node-config-editor.tsx b/dashboard/src/modules/nodes/dialogs/settings/node-config-editor.tsx similarity index 100% rename from dashboard/src/features/nodes/dialogs/settings/node-config-editor.tsx rename to dashboard/src/modules/nodes/dialogs/settings/node-config-editor.tsx diff --git a/dashboard/src/features/nodes/dialogs/settings/use-nodes-settings.ts b/dashboard/src/modules/nodes/dialogs/settings/use-nodes-settings.ts similarity index 100% rename from dashboard/src/features/nodes/dialogs/settings/use-nodes-settings.ts rename to dashboard/src/modules/nodes/dialogs/settings/use-nodes-settings.ts diff --git a/dashboard/src/features/nodes/index.ts b/dashboard/src/modules/nodes/index.ts similarity index 78% rename from dashboard/src/features/nodes/index.ts rename to dashboard/src/modules/nodes/index.ts index bd7acfa..6955b0d 100644 --- a/dashboard/src/features/nodes/index.ts +++ b/dashboard/src/modules/nodes/index.ts @@ -1,5 +1,6 @@ export * from './types'; export * from './tables'; export * from './services'; +export * from './nodes-usage-widget'; export * from './dialogs'; export * from './router-context'; diff --git a/dashboard/src/features/nodes/log/container.tsx b/dashboard/src/modules/nodes/log/container.tsx similarity index 100% rename from dashboard/src/features/nodes/log/container.tsx rename to dashboard/src/modules/nodes/log/container.tsx diff --git a/dashboard/src/features/nodes/log/index.ts b/dashboard/src/modules/nodes/log/index.ts similarity index 100% rename from dashboard/src/features/nodes/log/index.ts rename to dashboard/src/modules/nodes/log/index.ts diff --git a/dashboard/src/modules/nodes/nodes-usage-widget/index.tsx b/dashboard/src/modules/nodes/nodes-usage-widget/index.tsx new file mode 100644 index 0000000..f513983 --- /dev/null +++ b/dashboard/src/modules/nodes/nodes-usage-widget/index.tsx @@ -0,0 +1,117 @@ +import { useState, FC } from 'react'; +import { useTranslation } from 'react-i18next'; +import { + SectionWidget, + ChartContainer, + Awaiting, + ChartTooltip, + ChartTooltipContent, + ChartConfig, +} from "@marzneshin/components"; +import { XAxis, BarChart, YAxis, CartesianGrid, Bar } from "recharts" +import { format as formatByte } from '@chbphone55/pretty-bytes'; +import { useNodesUsageQuery, NodeType } from "@marzneshin/modules/nodes"; +import { + useTransformDateUsageData, + ChartDateInterval, + SelectDateView, + dateXAxisTicks, + useFromNowInterval +} from "@marzneshin/common/stats-charts"; + +const chartConfig = { + traffic: { + label: "Traffic", + color: "hsl(var(--chart-1))", + }, +} satisfies ChartConfig + +export const NodesUsageWidget: FC<{ node: NodeType }> = ({ node }) => { + const { t } = useTranslation(); + const [timeRange, setTimeRange] = useState("1d") + const { start, end } = useFromNowInterval(timeRange as ChartDateInterval); + const { data } = useNodesUsageQuery({ nodeId: node.id, start, end }) + const chartData = useTransformDateUsageData(data.usages); + const [totalAmount, totalMetric] = formatByte(data.total); + + return ( + + {t("page.nodes.nodes-usage-widget.title")} + + } + description={t("page.nodes.nodes-usage-widget.desc")} + options={ +
+ + {totalAmount} {totalMetric} + + + Total + +
+ } + footer={ + + } + > + + + + { + const [amount, metric] = formatByte(value) + return `${amount} ${metric}` + }} + /> + dateXAxisTicks(value, timeRange as ChartDateInterval)} + /> + { + return new Date(value).toLocaleDateString("en-US", { + month: "short", + day: "numeric", + year: "numeric", + hour: "numeric" + }) + }} + valueFormatter={(value) => { + const [amount, metric] = formatByte(value as number) + return `${amount} ${metric}` + }} + /> + } + /> + + + + + } + /> + ); +}; diff --git a/dashboard/src/modules/nodes/nodes-usage-widget/types.ts b/dashboard/src/modules/nodes/nodes-usage-widget/types.ts new file mode 100644 index 0000000..a601179 --- /dev/null +++ b/dashboard/src/modules/nodes/nodes-usage-widget/types.ts @@ -0,0 +1,10 @@ +import { UserType } from "@marzneshin/modules/users"; + +export interface NodesUsage { + datetime: Date; + nodes: Record; +} + +export interface UserNodesUsageWidgetProps { + user: UserType; +} diff --git a/dashboard/src/features/nodes/router-context/index.ts b/dashboard/src/modules/nodes/router-context/index.ts similarity index 84% rename from dashboard/src/features/nodes/router-context/index.ts rename to dashboard/src/modules/nodes/router-context/index.ts index d5a3369..d7cce73 100644 --- a/dashboard/src/features/nodes/router-context/index.ts +++ b/dashboard/src/modules/nodes/router-context/index.ts @@ -1,5 +1,5 @@ import { createContext, useContext } from "react"; -import { NodeType } from "@marzneshin/features/nodes"; +import { NodeType } from "@marzneshin/modules/nodes"; interface RouterNodeContextProps { node: NodeType; diff --git a/dashboard/src/features/nodes/services/backend-stats.query.ts b/dashboard/src/modules/nodes/services/backend-stats.query.ts similarity index 93% rename from dashboard/src/features/nodes/services/backend-stats.query.ts rename to dashboard/src/modules/nodes/services/backend-stats.query.ts index bcd5ec8..d4e55d6 100644 --- a/dashboard/src/features/nodes/services/backend-stats.query.ts +++ b/dashboard/src/modules/nodes/services/backend-stats.query.ts @@ -1,4 +1,4 @@ -import type { NodeType } from "@marzneshin/features/nodes"; +import type { NodeType } from "@marzneshin/modules/nodes"; import { useQuery } from "@tanstack/react-query"; import { fetch } from "@marzneshin/utils"; diff --git a/dashboard/src/features/nodes/services/create-node.mutate.ts b/dashboard/src/modules/nodes/services/create-node.mutate.ts similarity index 93% rename from dashboard/src/features/nodes/services/create-node.mutate.ts rename to dashboard/src/modules/nodes/services/create-node.mutate.ts index 69095dd..f123dd2 100644 --- a/dashboard/src/features/nodes/services/create-node.mutate.ts +++ b/dashboard/src/modules/nodes/services/create-node.mutate.ts @@ -1,4 +1,4 @@ -import { NodeType, NodesQueryFetchKey } from "@marzneshin/features/nodes"; +import { NodeType, NodesQueryFetchKey } from "@marzneshin/modules/nodes"; import { useMutation } from "@tanstack/react-query"; import { fetch, queryClient } from "@marzneshin/utils"; import { toast } from "sonner"; diff --git a/dashboard/src/features/nodes/services/delete-node.mutate.ts b/dashboard/src/modules/nodes/services/delete-node.mutate.ts similarity index 93% rename from dashboard/src/features/nodes/services/delete-node.mutate.ts rename to dashboard/src/modules/nodes/services/delete-node.mutate.ts index de2a107..714b9cd 100644 --- a/dashboard/src/features/nodes/services/delete-node.mutate.ts +++ b/dashboard/src/modules/nodes/services/delete-node.mutate.ts @@ -1,5 +1,5 @@ -import { NodeType, NodesQueryFetchKey } from "@marzneshin/features/nodes"; +import { NodeType, NodesQueryFetchKey } from "@marzneshin/modules/nodes"; import { useMutation } from "@tanstack/react-query"; import { fetch, queryClient } from "@marzneshin/utils"; import { toast } from "sonner"; diff --git a/dashboard/src/features/nodes/services/index.ts b/dashboard/src/modules/nodes/services/index.ts similarity index 89% rename from dashboard/src/features/nodes/services/index.ts rename to dashboard/src/modules/nodes/services/index.ts index ad40cfd..a57298e 100644 --- a/dashboard/src/features/nodes/services/index.ts +++ b/dashboard/src/modules/nodes/services/index.ts @@ -2,6 +2,7 @@ export * from "./nodes.query"; export * from "./node.query"; export * from "./logs.socket"; export * from "./settings.query"; +export * from "./nodes-usage.query"; export * from "./backend-stats.query"; export * from "./settings.mutate"; export * from "./update-node.mutate"; diff --git a/dashboard/src/features/nodes/services/logs.socket.ts b/dashboard/src/modules/nodes/services/logs.socket.ts similarity index 97% rename from dashboard/src/features/nodes/services/logs.socket.ts rename to dashboard/src/modules/nodes/services/logs.socket.ts index 6412995..50a3a06 100644 --- a/dashboard/src/features/nodes/services/logs.socket.ts +++ b/dashboard/src/modules/nodes/services/logs.socket.ts @@ -1,4 +1,4 @@ -import { NodeType } from "@marzneshin/features/nodes"; +import { NodeType } from "@marzneshin/modules/nodes"; import useWebSocket, { ReadyState } from "react-use-websocket"; import { useAuth } from "@marzneshin/features/auth"; import { joinPaths } from "@tanstack/react-router"; diff --git a/dashboard/src/features/nodes/services/node.query.ts b/dashboard/src/modules/nodes/services/node.query.ts similarity index 100% rename from dashboard/src/features/nodes/services/node.query.ts rename to dashboard/src/modules/nodes/services/node.query.ts diff --git a/dashboard/src/modules/nodes/services/nodes-usage.query.ts b/dashboard/src/modules/nodes/services/nodes-usage.query.ts new file mode 100644 index 0000000..280feaf --- /dev/null +++ b/dashboard/src/modules/nodes/services/nodes-usage.query.ts @@ -0,0 +1,41 @@ +import { useQuery } from "@tanstack/react-query"; +import { fetch } from "@marzneshin/utils"; + +export type UsageMetric = number[]; + +export interface NodesUsageResponse { + usages: Array; + total: number; +} + +export interface NodesUsageQueryOptions { + nodeId: number; + start: string; + end: string; +} + +export type NodesUsageQueryKey = [string, number, string, { start?: string, end?: string }] + +export const NodesUsageDefault = { + total: 0, + usages: [] +} + +export async function fetchNodesUsage({ queryKey }: { queryKey: NodesUsageQueryKey }): Promise { + return await fetch(`/nodes/${queryKey[1]}/usage`, { + query: { + start: queryKey[3].start, + end: queryKey[3].end + } + }).then((result) => { + return result; + }); +} +export const useNodesUsageQuery = ({ nodeId, start, end }: NodesUsageQueryOptions) => { + return useQuery({ + queryKey: ["nodes", nodeId, "usage", { start, end }], + queryFn: fetchNodesUsage, + refetchInterval: 1000 * 60 * 5, // 60min refresh + initialData: NodesUsageDefault + }) +} diff --git a/dashboard/src/features/nodes/services/nodes.query.ts b/dashboard/src/modules/nodes/services/nodes.query.ts similarity index 95% rename from dashboard/src/features/nodes/services/nodes.query.ts rename to dashboard/src/modules/nodes/services/nodes.query.ts index ae6a8c7..8759c21 100644 --- a/dashboard/src/features/nodes/services/nodes.query.ts +++ b/dashboard/src/modules/nodes/services/nodes.query.ts @@ -1,4 +1,4 @@ -import type { NodeType } from "@marzneshin/features/nodes"; +import type { NodeType } from "@marzneshin/modules/nodes"; import { useQuery } from "@tanstack/react-query"; import { fetch } from "@marzneshin/utils"; import type { diff --git a/dashboard/src/features/nodes/services/settings.mutate.ts b/dashboard/src/modules/nodes/services/settings.mutate.ts similarity index 96% rename from dashboard/src/features/nodes/services/settings.mutate.ts rename to dashboard/src/modules/nodes/services/settings.mutate.ts index db60da3..afd136a 100644 --- a/dashboard/src/features/nodes/services/settings.mutate.ts +++ b/dashboard/src/modules/nodes/services/settings.mutate.ts @@ -1,4 +1,4 @@ -import type { NodeType } from "@marzneshin/features/nodes"; +import type { NodeType } from "@marzneshin/modules/nodes"; import { useMutation } from "@tanstack/react-query"; import { fetch, queryClient } from "@marzneshin/utils"; import { toast } from "sonner"; diff --git a/dashboard/src/features/nodes/services/settings.query.ts b/dashboard/src/modules/nodes/services/settings.query.ts similarity index 94% rename from dashboard/src/features/nodes/services/settings.query.ts rename to dashboard/src/modules/nodes/services/settings.query.ts index b1b8f70..b456362 100644 --- a/dashboard/src/features/nodes/services/settings.query.ts +++ b/dashboard/src/modules/nodes/services/settings.query.ts @@ -1,4 +1,4 @@ -import type { NodeType } from "@marzneshin/features/nodes"; +import type { NodeType } from "@marzneshin/modules/nodes"; import { useQuery } from "@tanstack/react-query"; import { fetch } from "@marzneshin/utils"; diff --git a/dashboard/src/features/nodes/services/update-node.mutate.ts b/dashboard/src/modules/nodes/services/update-node.mutate.ts similarity index 95% rename from dashboard/src/features/nodes/services/update-node.mutate.ts rename to dashboard/src/modules/nodes/services/update-node.mutate.ts index fc3b313..772022b 100644 --- a/dashboard/src/features/nodes/services/update-node.mutate.ts +++ b/dashboard/src/modules/nodes/services/update-node.mutate.ts @@ -1,4 +1,4 @@ -import { NodeType } from "@marzneshin/features/nodes"; +import { NodeType } from "@marzneshin/modules/nodes"; import { useMutation } from "@tanstack/react-query"; import { fetch, queryClient } from "@marzneshin/utils"; import { toast } from "sonner"; diff --git a/dashboard/src/features/nodes/tables/columns.tsx b/dashboard/src/modules/nodes/tables/columns.tsx similarity index 98% rename from dashboard/src/features/nodes/tables/columns.tsx rename to dashboard/src/modules/nodes/tables/columns.tsx index bd5fa9f..4391502 100644 --- a/dashboard/src/features/nodes/tables/columns.tsx +++ b/dashboard/src/modules/nodes/tables/columns.tsx @@ -1,5 +1,5 @@ import { ColumnDef } from "@tanstack/react-table" -import { NodesStatusBadge, NodeType, NodesStatus } from "@marzneshin/features/nodes" +import { NodesStatusBadge, NodeType, NodesStatus } from "@marzneshin/modules/nodes" import { DataTableActionsCell, DataTableColumnHeader diff --git a/dashboard/src/features/nodes/tables/detail-table/index.tsx b/dashboard/src/modules/nodes/tables/detail-table/index.tsx similarity index 100% rename from dashboard/src/features/nodes/tables/detail-table/index.tsx rename to dashboard/src/modules/nodes/tables/detail-table/index.tsx diff --git a/dashboard/src/features/nodes/tables/index.ts b/dashboard/src/modules/nodes/tables/index.ts similarity index 73% rename from dashboard/src/features/nodes/tables/index.ts rename to dashboard/src/modules/nodes/tables/index.ts index 1ba1b35..5cda0a6 100644 --- a/dashboard/src/features/nodes/tables/index.ts +++ b/dashboard/src/modules/nodes/tables/index.ts @@ -1,3 +1,4 @@ export * from "./columns" +export * from "./detail-table" export * from "./nodes-status-badge" export * from "./table" diff --git a/dashboard/src/features/nodes/tables/nodes-status-badge.tsx b/dashboard/src/modules/nodes/tables/nodes-status-badge.tsx similarity index 100% rename from dashboard/src/features/nodes/tables/nodes-status-badge.tsx rename to dashboard/src/modules/nodes/tables/nodes-status-badge.tsx diff --git a/dashboard/src/features/nodes/tables/table.tsx b/dashboard/src/modules/nodes/tables/table.tsx similarity index 96% rename from dashboard/src/features/nodes/tables/table.tsx rename to dashboard/src/modules/nodes/tables/table.tsx index 831a8f8..278daed 100644 --- a/dashboard/src/features/nodes/tables/table.tsx +++ b/dashboard/src/modules/nodes/tables/table.tsx @@ -3,7 +3,7 @@ import { columns as columnsFn, fetchNodes, NodeType -} from '@marzneshin/features/nodes'; +} from '@marzneshin/modules/nodes'; import { EntityTable } from "@marzneshin/features/entity-table"; import { useNavigate } from "@tanstack/react-router"; diff --git a/dashboard/src/features/nodes/types/index.ts b/dashboard/src/modules/nodes/types/index.ts similarity index 100% rename from dashboard/src/features/nodes/types/index.ts rename to dashboard/src/modules/nodes/types/index.ts diff --git a/dashboard/src/routes/_dashboard/nodes.lazy.tsx b/dashboard/src/routes/_dashboard/nodes.lazy.tsx index 154ed37..a8eb3cb 100644 --- a/dashboard/src/routes/_dashboard/nodes.lazy.tsx +++ b/dashboard/src/routes/_dashboard/nodes.lazy.tsx @@ -3,7 +3,7 @@ import { Page, Loading, } from '@marzneshin/components'; -import { NodesTable } from '@marzneshin/features/nodes'; +import { NodesTable } from '@marzneshin/modules/nodes'; import { Link, createFileRoute, Outlet } from '@tanstack/react-router' import { type FC, Suspense } from 'react'; import { useTranslation } from 'react-i18next'; diff --git a/dashboard/src/routes/_dashboard/nodes/$nodeId.tsx b/dashboard/src/routes/_dashboard/nodes/$nodeId.tsx index 2daaec4..eb60a20 100644 --- a/dashboard/src/routes/_dashboard/nodes/$nodeId.tsx +++ b/dashboard/src/routes/_dashboard/nodes/$nodeId.tsx @@ -7,7 +7,7 @@ import { import { fetchNode, RouterNodeContext -} from "@marzneshin/features/nodes"; +} from "@marzneshin/modules/nodes"; import { Suspense } from "react"; import { AlertDialog, diff --git a/dashboard/src/routes/_dashboard/nodes/$nodeId/delete.tsx b/dashboard/src/routes/_dashboard/nodes/$nodeId/delete.tsx index 586cee4..ef2046c 100644 --- a/dashboard/src/routes/_dashboard/nodes/$nodeId/delete.tsx +++ b/dashboard/src/routes/_dashboard/nodes/$nodeId/delete.tsx @@ -5,7 +5,7 @@ import { import { NodesDeleteConfirmationDialog, useRouterNodeContext, -} from "@marzneshin/features/nodes"; +} from "@marzneshin/modules/nodes"; import { useDialog } from "@marzneshin/hooks"; const NodeDelete = () => { diff --git a/dashboard/src/routes/_dashboard/nodes/$nodeId/edit.tsx b/dashboard/src/routes/_dashboard/nodes/$nodeId/edit.tsx index 6f874e2..d52e042 100644 --- a/dashboard/src/routes/_dashboard/nodes/$nodeId/edit.tsx +++ b/dashboard/src/routes/_dashboard/nodes/$nodeId/edit.tsx @@ -5,7 +5,7 @@ import { import { MutationDialog, useRouterNodeContext, -} from "@marzneshin/features/nodes"; +} from "@marzneshin/modules/nodes"; const NodeEdit = () => { const value = useRouterNodeContext() diff --git a/dashboard/src/routes/_dashboard/nodes/$nodeId/index.tsx b/dashboard/src/routes/_dashboard/nodes/$nodeId/index.tsx index a0505af..dc33800 100644 --- a/dashboard/src/routes/_dashboard/nodes/$nodeId/index.tsx +++ b/dashboard/src/routes/_dashboard/nodes/$nodeId/index.tsx @@ -5,7 +5,7 @@ import { import { useRouterNodeContext, NodesSettingsDialog, -} from "@marzneshin/features/nodes"; +} from "@marzneshin/modules/nodes"; import { useDialog } from "@marzneshin/hooks"; const NodeOpen = () => { diff --git a/dashboard/src/routes/_dashboard/nodes/create.tsx b/dashboard/src/routes/_dashboard/nodes/create.tsx index efc22de..9eb5636 100644 --- a/dashboard/src/routes/_dashboard/nodes/create.tsx +++ b/dashboard/src/routes/_dashboard/nodes/create.tsx @@ -1,5 +1,5 @@ import { createFileRoute, useNavigate } from "@tanstack/react-router"; -import { MutationDialog } from "@marzneshin/features/nodes"; +import { MutationDialog } from "@marzneshin/modules/nodes"; const NodeCreate = () => { const navigate = useNavigate({ from: "/nodes/create" });