mirror of
https://github.com/Buriburizaem0n/admin-frontend-domain.git
synced 2026-09-19 09:40:13 +00:00
fix: migrate tables to TanStack React Table v9
This commit is contained in:
@@ -21,6 +21,8 @@ jobs:
|
|||||||
timeout-minutes: 25
|
timeout-minutes: 25
|
||||||
|
|
||||||
env:
|
env:
|
||||||
|
AGENT_REPO: nezhahq/agent
|
||||||
|
AGENT_REF: main
|
||||||
NEZHA_REPO: nezhahq/nezha
|
NEZHA_REPO: nezhahq/nezha
|
||||||
NEZHA_REF: master
|
NEZHA_REF: master
|
||||||
NZ_LISTENHOST: 127.0.0.1
|
NZ_LISTENHOST: 127.0.0.1
|
||||||
@@ -31,6 +33,7 @@ jobs:
|
|||||||
NZ_DEBUG: "true"
|
NZ_DEBUG: "true"
|
||||||
E2E_ADMIN_USER: admin
|
E2E_ADMIN_USER: admin
|
||||||
E2E_ADMIN_PASS: admin
|
E2E_ADMIN_PASS: admin
|
||||||
|
E2E_AGENT_UUID: 11111111-2222-4333-8444-555555555555
|
||||||
E2E_BASE_URL: http://127.0.0.1:5173
|
E2E_BASE_URL: http://127.0.0.1:5173
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -46,6 +49,13 @@ jobs:
|
|||||||
ref: ${{ env.NEZHA_REF }}
|
ref: ${{ env.NEZHA_REF }}
|
||||||
path: nezha
|
path: nezha
|
||||||
|
|
||||||
|
- name: Checkout Nezha Agent
|
||||||
|
uses: actions/checkout@v7
|
||||||
|
with:
|
||||||
|
repository: ${{ env.AGENT_REPO }}
|
||||||
|
ref: ${{ env.AGENT_REF }}
|
||||||
|
path: agent
|
||||||
|
|
||||||
- name: Setup Node
|
- name: Setup Node
|
||||||
uses: actions/setup-node@v7
|
uses: actions/setup-node@v7
|
||||||
with:
|
with:
|
||||||
@@ -57,7 +67,9 @@ jobs:
|
|||||||
uses: actions/setup-go@v7
|
uses: actions/setup-go@v7
|
||||||
with:
|
with:
|
||||||
go-version: "1.26.x"
|
go-version: "1.26.x"
|
||||||
cache-dependency-path: nezha/go.sum
|
cache-dependency-path: |
|
||||||
|
agent/go.sum
|
||||||
|
nezha/go.sum
|
||||||
|
|
||||||
- name: Install frontend dependencies
|
- name: Install frontend dependencies
|
||||||
working-directory: admin-frontend
|
working-directory: admin-frontend
|
||||||
@@ -77,6 +89,10 @@ jobs:
|
|||||||
"$(go env GOPATH)/bin/swag" init --pd -d cmd/dashboard -g main.go -o cmd/dashboard/docs
|
"$(go env GOPATH)/bin/swag" init --pd -d cmd/dashboard -g main.go -o cmd/dashboard/docs
|
||||||
go build -o /tmp/nezha-dashboard ./cmd/dashboard
|
go build -o /tmp/nezha-dashboard ./cmd/dashboard
|
||||||
|
|
||||||
|
- name: Build Agent
|
||||||
|
working-directory: agent
|
||||||
|
run: go build -o /tmp/nezha-agent ./cmd/agent
|
||||||
|
|
||||||
- name: Start backend
|
- name: Start backend
|
||||||
working-directory: nezha
|
working-directory: nezha
|
||||||
env:
|
env:
|
||||||
@@ -101,6 +117,19 @@ jobs:
|
|||||||
cat /tmp/dashboard.log || true
|
cat /tmp/dashboard.log || true
|
||||||
exit 1
|
exit 1
|
||||||
|
|
||||||
|
- name: Start Agent
|
||||||
|
working-directory: agent
|
||||||
|
env:
|
||||||
|
NZ_SERVER: 127.0.0.1:8008
|
||||||
|
NZ_CLIENT_SECRET: ${{ env.NZ_AGENTSECRETKEY }}
|
||||||
|
NZ_UUID: ${{ env.E2E_AGENT_UUID }}
|
||||||
|
NZ_TLS: "false"
|
||||||
|
NZ_DISABLE_AUTO_UPDATE: "true"
|
||||||
|
run: |
|
||||||
|
nohup /tmp/nezha-agent -c /tmp/nezha-agent-config.yml \
|
||||||
|
> /tmp/agent.log 2>&1 &
|
||||||
|
echo $! > /tmp/agent.pid
|
||||||
|
|
||||||
- name: Run Playwright tests
|
- name: Run Playwright tests
|
||||||
working-directory: admin-frontend
|
working-directory: admin-frontend
|
||||||
env:
|
env:
|
||||||
@@ -123,6 +152,21 @@ jobs:
|
|||||||
path: /tmp/dashboard.log
|
path: /tmp/dashboard.log
|
||||||
retention-days: 7
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Upload Agent log
|
||||||
|
if: always()
|
||||||
|
uses: actions/upload-artifact@v7
|
||||||
|
with:
|
||||||
|
name: agent-log
|
||||||
|
path: /tmp/agent.log
|
||||||
|
retention-days: 7
|
||||||
|
|
||||||
|
- name: Stop Agent
|
||||||
|
if: always()
|
||||||
|
run: |
|
||||||
|
if [ -f /tmp/agent.pid ]; then
|
||||||
|
kill "$(cat /tmp/agent.pid)" || true
|
||||||
|
fi
|
||||||
|
|
||||||
- name: Stop backend
|
- name: Stop backend
|
||||||
if: always()
|
if: always()
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ import {
|
|||||||
} from "@/components/ui/dropdown-menu"
|
} from "@/components/ui/dropdown-menu"
|
||||||
import { Input } from "@/components/ui/input"
|
import { Input } from "@/components/ui/input"
|
||||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||||
|
import { virtualizedTableFeatures } from "@/lib/table"
|
||||||
import { copyToClipboard, fm, formatPath, fmWorker as worker } from "@/lib/utils"
|
import { copyToClipboard, fm, formatPath, fmWorker as worker } from "@/lib/utils"
|
||||||
import {
|
import {
|
||||||
FMEntry,
|
FMEntry,
|
||||||
@@ -84,7 +85,7 @@ export const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({
|
|||||||
const [dOpen, setdOpen] = useState(false)
|
const [dOpen, setdOpen] = useState(false)
|
||||||
const [uOpen, setuOpen] = useState(false)
|
const [uOpen, setuOpen] = useState(false)
|
||||||
|
|
||||||
const columns: ColumnDef<FMEntry>[] = [
|
const columns: ColumnDef<typeof virtualizedTableFeatures, FMEntry>[] = [
|
||||||
{
|
{
|
||||||
id: "type",
|
id: "type",
|
||||||
header: () => <span>{t("Type")}</span>,
|
header: () => <span>{t("Type")}</span>,
|
||||||
@@ -122,7 +123,7 @@ export const FMComponent: React.FC<FMProps & JSX.IntrinsicElements["div"]> = ({
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const tableRowComponent = (rows: Row<FMEntry>[]) =>
|
const tableRowComponent = (rows: Row<typeof virtualizedTableFeatures, FMEntry>[]) =>
|
||||||
function getTableRow(props: VirtualizedTableRowProps) {
|
function getTableRow(props: VirtualizedTableRowProps) {
|
||||||
const index = Number(props["data-index"])
|
const index = Number(props["data-index"])
|
||||||
const row = rows[index]
|
const row = rows[index]
|
||||||
|
|||||||
@@ -3,16 +3,16 @@
|
|||||||
import { ScrollArea } from "@/components/ui/scroll-area"
|
import { ScrollArea } from "@/components/ui/scroll-area"
|
||||||
import { TableCell, TableHead, TableRow } from "@/components/ui/table"
|
import { TableCell, TableHead, TableRow } from "@/components/ui/table"
|
||||||
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
import { useMediaQuery } from "@/hooks/useMediaQuery"
|
||||||
|
import { virtualizedTableFeatures } from "@/lib/table"
|
||||||
import { cn } from "@/lib/utils"
|
import { cn } from "@/lib/utils"
|
||||||
import {
|
import {
|
||||||
ColumnDef,
|
ColumnDef,
|
||||||
Row,
|
Row,
|
||||||
|
RowData,
|
||||||
SortDirection,
|
SortDirection,
|
||||||
SortingState,
|
SortingState,
|
||||||
flexRender,
|
flexRender,
|
||||||
getCoreRowModel,
|
useTable,
|
||||||
getSortedRowModel,
|
|
||||||
useReactTable,
|
|
||||||
} from "@tanstack/react-table"
|
} from "@tanstack/react-table"
|
||||||
import { HTMLAttributes, JSX, forwardRef, useEffect, useRef, useState } from "react"
|
import { HTMLAttributes, JSX, forwardRef, useEffect, useRef, useState } from "react"
|
||||||
import { TableVirtuoso } from "react-virtuoso"
|
import { TableVirtuoso } from "react-virtuoso"
|
||||||
@@ -26,7 +26,9 @@ const TableComponent = forwardRef<HTMLTableElement, React.HTMLAttributes<HTMLTab
|
|||||||
)
|
)
|
||||||
TableComponent.displayName = "TableComponent"
|
TableComponent.displayName = "TableComponent"
|
||||||
|
|
||||||
const TableRowComponent = <TData,>(rows: Row<TData>[]) =>
|
const TableRowComponent = <TData extends RowData>(
|
||||||
|
rows: Row<typeof virtualizedTableFeatures, TData>[],
|
||||||
|
) =>
|
||||||
function getTableRow(props: HTMLAttributes<HTMLTableRowElement>) {
|
function getTableRow(props: HTMLAttributes<HTMLTableRowElement>) {
|
||||||
// @ts-expect-error data-index is a valid attribute
|
// @ts-expect-error data-index is a valid attribute
|
||||||
const index = props["data-index"]
|
const index = props["data-index"]
|
||||||
@@ -64,34 +66,33 @@ function SortingIndicator({ isSorted }: { isSorted: SortDirection | false }) {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
interface DataTableProps<TData, TValue> {
|
interface DataTableProps<TData extends RowData> {
|
||||||
columns: ColumnDef<TData, TValue>[]
|
columns: ColumnDef<typeof virtualizedTableFeatures, TData>[]
|
||||||
data: TData[]
|
data: TData[]
|
||||||
rowComponent?: (
|
rowComponent?: (
|
||||||
rows: Row<TData>[],
|
rows: Row<typeof virtualizedTableFeatures, TData>[],
|
||||||
) => (props: HTMLAttributes<HTMLTableRowElement>) => JSX.Element | null
|
) => (props: HTMLAttributes<HTMLTableRowElement>) => JSX.Element | null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function DataTable<TData, TValue>({
|
export function DataTable<TData extends RowData>({
|
||||||
columns,
|
columns,
|
||||||
data,
|
data,
|
||||||
rowComponent,
|
rowComponent,
|
||||||
}: DataTableProps<TData, TValue>) {
|
}: DataTableProps<TData>) {
|
||||||
const [sorting, setSorting] = useState<SortingState>([
|
const [sorting, setSorting] = useState<SortingState>([
|
||||||
{
|
{
|
||||||
id: "type",
|
id: "type",
|
||||||
desc: true,
|
desc: true,
|
||||||
},
|
},
|
||||||
])
|
])
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: virtualizedTableFeatures,
|
||||||
data,
|
data,
|
||||||
columns,
|
columns,
|
||||||
state: {
|
state: {
|
||||||
sorting,
|
sorting,
|
||||||
},
|
},
|
||||||
onSortingChange: setSorting,
|
onSortingChange: setSorting,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
getSortedRowModel: getSortedRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const { rows } = table.getRowModel()
|
const { rows } = table.getRowModel()
|
||||||
|
|||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import {
|
||||||
|
columnSizingFeature,
|
||||||
|
columnVisibilityFeature,
|
||||||
|
createSortedRowModel,
|
||||||
|
rowSelectionFeature,
|
||||||
|
rowSortingFeature,
|
||||||
|
sortFn_alphanumeric,
|
||||||
|
sortFn_datetime,
|
||||||
|
sortFn_text,
|
||||||
|
tableFeatures,
|
||||||
|
} from "@tanstack/react-table"
|
||||||
|
|
||||||
|
export const selectableTableFeatures = tableFeatures({
|
||||||
|
columnVisibilityFeature,
|
||||||
|
rowSelectionFeature,
|
||||||
|
})
|
||||||
|
|
||||||
|
export const virtualizedTableFeatures = tableFeatures({
|
||||||
|
columnSizingFeature,
|
||||||
|
columnVisibilityFeature,
|
||||||
|
rowSelectionFeature,
|
||||||
|
rowSortingFeature,
|
||||||
|
sortedRowModel: createSortedRowModel(),
|
||||||
|
sortFns: {
|
||||||
|
alphanumeric: sortFn_alphanumeric,
|
||||||
|
datetime: sortFn_datetime,
|
||||||
|
text: sortFn_text,
|
||||||
|
},
|
||||||
|
})
|
||||||
@@ -14,8 +14,9 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelAlertRule, triggerModes } from "@/types"
|
import { ModelAlertRule, triggerModes } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -37,7 +38,7 @@ export default function AlertRulePage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelAlertRule>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, ModelAlertRule>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -57,7 +58,6 @@ export default function AlertRulePage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -131,10 +131,10 @@ export default function AlertRulePage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
+5
-5
@@ -14,9 +14,10 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { IconButton } from "@/components/xui/icon-button"
|
import { IconButton } from "@/components/xui/icon-button"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelCron } from "@/types"
|
import { ModelCron } from "@/types"
|
||||||
import { cronTypes } from "@/types"
|
import { cronTypes } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -36,7 +37,7 @@ export default function CronPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelCron>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, ModelCron>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -56,7 +57,6 @@ export default function CronPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -199,10 +199,10 @@ export default function CronPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
+5
-5
@@ -12,8 +12,9 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelDDNSProfile } from "@/types"
|
import { ModelDDNSProfile } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo, useState } from "react"
|
import { useEffect, useMemo, useState } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -45,7 +46,7 @@ export default function DDNSPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelDDNSProfile>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, ModelDDNSProfile>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -65,7 +66,6 @@ export default function DDNSPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -136,10 +136,10 @@ export default function DDNSPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
+5
-5
@@ -12,8 +12,9 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelNAT } from "@/types"
|
import { ModelNAT } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -33,7 +34,7 @@ export default function NATPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelNAT>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, ModelNAT>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -53,7 +54,6 @@ export default function NATPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -119,10 +119,10 @@ export default function NATPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
@@ -13,13 +13,19 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelNotificationGroupResponseItem } from "@/types"
|
import { ModelNotificationGroupResponseItem } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
import useSWR from "swr"
|
import useSWR from "swr"
|
||||||
|
|
||||||
|
type NotificationGroupColumn = ColumnDef<
|
||||||
|
typeof selectableTableFeatures,
|
||||||
|
ModelNotificationGroupResponseItem
|
||||||
|
>
|
||||||
|
|
||||||
export default function NotificationGroupPage() {
|
export default function NotificationGroupPage() {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { data, mutate, error, isLoading } = useSWR<ModelNotificationGroupResponseItem[]>(
|
const { data, mutate, error, isLoading } = useSWR<ModelNotificationGroupResponseItem[]>(
|
||||||
@@ -37,7 +43,7 @@ export default function NotificationGroupPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelNotificationGroupResponseItem>[] = [
|
const columns: NotificationGroupColumn[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -57,7 +63,6 @@ export default function NotificationGroupPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -112,10 +117,10 @@ export default function NotificationGroupPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
@@ -15,8 +15,9 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { useNotification } from "@/hooks/useNotfication"
|
import { useNotification } from "@/hooks/useNotfication"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelNotification } from "@/types"
|
import { ModelNotification } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -40,7 +41,7 @@ export default function NotificationPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelNotification>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, ModelNotification>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -60,7 +61,6 @@ export default function NotificationPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -127,10 +127,10 @@ export default function NotificationPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
@@ -22,8 +22,9 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { useAuth } from "@/hooks/useAuth"
|
import { useAuth } from "@/hooks/useAuth"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelOnlineUser, ModelOnlineUserApi } from "@/types"
|
import { ModelOnlineUser, ModelOnlineUserApi } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { useSearchParams } from "react-router-dom"
|
import { useSearchParams } from "react-router-dom"
|
||||||
@@ -55,7 +56,7 @@ export default function OnlineUserPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
let columns: ColumnDef<ModelOnlineUser>[] = [
|
let columns: ColumnDef<typeof selectableTableFeatures, ModelOnlineUser>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -75,7 +76,6 @@ export default function OnlineUserPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -128,10 +128,10 @@ export default function OnlineUserPage() {
|
|||||||
return data?.value ?? []
|
return data?.value ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable<ModelOnlineUser>({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
@@ -13,8 +13,9 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelServerGroupResponseItem } from "@/types"
|
import { ModelServerGroupResponseItem } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -37,7 +38,7 @@ export default function ServerGroupPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelServerGroupResponseItem>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, ModelServerGroupResponseItem>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -57,7 +58,6 @@ export default function ServerGroupPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -112,10 +112,10 @@ export default function ServerGroupPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
@@ -27,9 +27,10 @@ import {
|
|||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { IconButton } from "@/components/xui/icon-button"
|
import { IconButton } from "@/components/xui/icon-button"
|
||||||
import { useServer } from "@/hooks/useServer"
|
import { useServer } from "@/hooks/useServer"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { joinIP } from "@/lib/utils"
|
import { joinIP } from "@/lib/utils"
|
||||||
import { ModelServerTaskResponse, ModelServer as Server } from "@/types"
|
import { ModelServerTaskResponse, ModelServer as Server } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -51,7 +52,7 @@ export default function ServerPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<Server>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, Server>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -71,7 +72,6 @@ export default function ServerPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -217,10 +217,10 @@ export default function ServerPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
@@ -12,9 +12,10 @@ import {
|
|||||||
TableHeader,
|
TableHeader,
|
||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelService as Service } from "@/types"
|
import { ModelService as Service } from "@/types"
|
||||||
import { serviceTypes } from "@/types"
|
import { serviceTypes } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -34,7 +35,7 @@ export default function ServicePage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<Service>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, Service>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -54,7 +55,6 @@ export default function ServicePage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -165,10 +165,10 @@ export default function ServicePage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
+5
-5
@@ -13,8 +13,9 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { UserCard } from "@/components/user"
|
import { UserCard } from "@/components/user"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import { ModelUser } from "@/types"
|
import { ModelUser } from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { toast } from "sonner"
|
import { toast } from "sonner"
|
||||||
@@ -34,7 +35,7 @@ export default function UserPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
const columns: ColumnDef<ModelUser>[] = [
|
const columns: ColumnDef<typeof selectableTableFeatures, ModelUser>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -54,7 +55,6 @@ export default function UserPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -105,10 +105,10 @@ export default function UserPage() {
|
|||||||
return data ?? []
|
return data ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
+5
-5
@@ -22,13 +22,14 @@ import {
|
|||||||
TableRow,
|
TableRow,
|
||||||
} from "@/components/ui/table"
|
} from "@/components/ui/table"
|
||||||
import { useAuth } from "@/hooks/useAuth"
|
import { useAuth } from "@/hooks/useAuth"
|
||||||
|
import { selectableTableFeatures } from "@/lib/table"
|
||||||
import {
|
import {
|
||||||
GithubComNezhahqNezhaModelValueArrayModelWAFApiMock,
|
GithubComNezhahqNezhaModelValueArrayModelWAFApiMock,
|
||||||
ModelWAFApiMock,
|
ModelWAFApiMock,
|
||||||
wafBlockIdentifiers,
|
wafBlockIdentifiers,
|
||||||
wafBlockReasons,
|
wafBlockReasons,
|
||||||
} from "@/types"
|
} from "@/types"
|
||||||
import { ColumnDef, flexRender, getCoreRowModel, useReactTable } from "@tanstack/react-table"
|
import { ColumnDef, flexRender, useTable } from "@tanstack/react-table"
|
||||||
import { useEffect, useMemo } from "react"
|
import { useEffect, useMemo } from "react"
|
||||||
import { useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { useSearchParams } from "react-router-dom"
|
import { useSearchParams } from "react-router-dom"
|
||||||
@@ -61,7 +62,7 @@ export default function WAFPage() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [error])
|
}, [error])
|
||||||
|
|
||||||
let columns: ColumnDef<ModelWAFApiMock>[] = [
|
let columns: ColumnDef<typeof selectableTableFeatures, ModelWAFApiMock>[] = [
|
||||||
{
|
{
|
||||||
id: "select",
|
id: "select",
|
||||||
header: ({ table }) => (
|
header: ({ table }) => (
|
||||||
@@ -81,7 +82,6 @@ export default function WAFPage() {
|
|||||||
aria-label="Select row"
|
aria-label="Select row"
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
enableSorting: false,
|
|
||||||
enableHiding: false,
|
enableHiding: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -147,10 +147,10 @@ export default function WAFPage() {
|
|||||||
return data?.value ?? []
|
return data?.value ?? []
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
const table = useReactTable({
|
const table = useTable({
|
||||||
|
features: selectableTableFeatures,
|
||||||
data: dataCache,
|
data: dataCache,
|
||||||
columns,
|
columns,
|
||||||
getCoreRowModel: getCoreRowModel(),
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const selectedRows = table.getSelectedRowModel().rows
|
const selectedRows = table.getSelectedRowModel().rows
|
||||||
|
|||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import { expect } from "@playwright/test"
|
||||||
|
|
||||||
|
import { test } from "./fixtures"
|
||||||
|
|
||||||
|
type AgentServer = {
|
||||||
|
host?: { platform?: string }
|
||||||
|
id: number
|
||||||
|
last_active?: string
|
||||||
|
name: string
|
||||||
|
uuid: string
|
||||||
|
}
|
||||||
|
|
||||||
|
test("a real agent reports to the dashboard and is selectable in the server table", async ({
|
||||||
|
adminPage: page,
|
||||||
|
}) => {
|
||||||
|
const agentUUID = process.env.E2E_AGENT_UUID
|
||||||
|
expect(agentUUID, "E2E_AGENT_UUID must identify the Agent started by CI").toBeTruthy()
|
||||||
|
|
||||||
|
let agentServer: AgentServer | undefined
|
||||||
|
await expect
|
||||||
|
.poll(
|
||||||
|
async () => {
|
||||||
|
const response = await page.request.get("/api/v1/server")
|
||||||
|
if (!response.ok()) return false
|
||||||
|
|
||||||
|
const body = (await response.json()) as { data?: AgentServer[] }
|
||||||
|
agentServer = body.data?.find((server) => server.uuid === agentUUID)
|
||||||
|
if (!agentServer?.last_active || !agentServer.host?.platform) return false
|
||||||
|
|
||||||
|
return new Date(agentServer.last_active).getTime() > 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "the real Agent must register and report live host state",
|
||||||
|
timeout: 30_000,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.toBe(true)
|
||||||
|
|
||||||
|
await page.goto("/dashboard")
|
||||||
|
|
||||||
|
const row = page.getByRole("row").filter({
|
||||||
|
has: page.getByText(agentServer!.name, { exact: true }),
|
||||||
|
})
|
||||||
|
await expect(row).toHaveCount(1)
|
||||||
|
|
||||||
|
const selection = row.getByRole("checkbox", { name: "Select row" })
|
||||||
|
await expect(selection).not.toBeChecked()
|
||||||
|
await selection.click()
|
||||||
|
await expect(selection).toBeChecked()
|
||||||
|
})
|
||||||
@@ -1,12 +1,11 @@
|
|||||||
import { expect } from "@playwright/test"
|
import { expect } from "@playwright/test"
|
||||||
|
|
||||||
import { csrfHeaders, test } from "./fixtures"
|
import { csrfRequest, test } from "./fixtures"
|
||||||
|
|
||||||
test("manual cron trigger goes through POST, not GET", async ({ adminPage: page }) => {
|
test("manual cron trigger goes through POST, not GET", async ({ adminPage: page }) => {
|
||||||
const created = await page.request.post("/api/v1/cron", {
|
const created = await csrfRequest(page, "post", "/api/v1/cron", {
|
||||||
headers: await csrfHeaders(page),
|
|
||||||
data: {
|
data: {
|
||||||
name: "e2e-cron-csrf",
|
name: `e2e-cron-csrf-${Date.now().toString(36)}`,
|
||||||
task_type: 0,
|
task_type: 0,
|
||||||
scheduler: "@every 1h",
|
scheduler: "@every 1h",
|
||||||
command: "true",
|
command: "true",
|
||||||
@@ -29,15 +28,12 @@ test("manual cron trigger goes through POST, not GET", async ({ adminPage: page
|
|||||||
`GET must no longer be routable (got ${getResp.status()})`,
|
`GET must no longer be routable (got ${getResp.status()})`,
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
|
|
||||||
const postResp = await page.request.post(`/api/v1/cron/${cronID}/manual`, {
|
const postResp = await csrfRequest(page, "post", `/api/v1/cron/${cronID}/manual`)
|
||||||
headers: await csrfHeaders(page),
|
|
||||||
})
|
|
||||||
expect(postResp.ok(), `POST must succeed (got ${postResp.status()})`).toBeTruthy()
|
expect(postResp.ok(), `POST must succeed (got ${postResp.status()})`).toBeTruthy()
|
||||||
const body = await postResp.json()
|
const body = await postResp.json()
|
||||||
expect(body.success).toBe(true)
|
expect(body.success).toBe(true)
|
||||||
} finally {
|
} finally {
|
||||||
await page.request.post("/api/v1/batch-delete/cron", {
|
await csrfRequest(page, "post", "/api/v1/batch-delete/cron", {
|
||||||
headers: await csrfHeaders(page),
|
|
||||||
data: [cronID],
|
data: [cronID],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+13
-10
@@ -1,8 +1,10 @@
|
|||||||
import { expect } from "@playwright/test"
|
import { expect } from "@playwright/test"
|
||||||
|
|
||||||
import { csrfHeaders, test } from "./fixtures"
|
import { csrfRequest, test } from "./fixtures"
|
||||||
|
|
||||||
test("file manager creation only accepts POST", async ({ adminPage: page }) => {
|
test("file manager creation only accepts POST and reaches the connected Agent", async ({
|
||||||
|
adminPage: page,
|
||||||
|
}) => {
|
||||||
const getResp = await page.request.get("/api/v1/file?id=1", {
|
const getResp = await page.request.get("/api/v1/file?id=1", {
|
||||||
failOnStatusCode: false,
|
failOnStatusCode: false,
|
||||||
})
|
})
|
||||||
@@ -11,15 +13,16 @@ test("file manager creation only accepts POST", async ({ adminPage: page }) => {
|
|||||||
`GET /api/v1/file must no longer be routable (got ${getResp.status()})`,
|
`GET /api/v1/file must no longer be routable (got ${getResp.status()})`,
|
||||||
).toBeTruthy()
|
).toBeTruthy()
|
||||||
|
|
||||||
const postResp = await page.request.post("/api/v1/file?id=1", {
|
const postResp = await csrfRequest(page, "post", "/api/v1/file?id=1", {
|
||||||
failOnStatusCode: false,
|
failOnStatusCode: false,
|
||||||
headers: await csrfHeaders(page),
|
|
||||||
})
|
})
|
||||||
expect(postResp.status()).toBe(200)
|
expect(postResp.status()).toBe(200)
|
||||||
const body = await postResp.json()
|
const body = (await postResp.json()) as {
|
||||||
expect(
|
data?: { session_id?: string }
|
||||||
body.success,
|
success?: boolean
|
||||||
"without a connected agent server the POST surfaces a Service error, but the route is reachable",
|
}
|
||||||
).not.toBe(true)
|
expect(body.success, "POST must create a file-manager session through the real Agent").toBe(
|
||||||
expect(body.error).toBeTruthy()
|
true,
|
||||||
|
)
|
||||||
|
expect(body.data?.session_id).toBeTruthy()
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user