Update
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -32,8 +32,8 @@
|
|||||||
<!-- Nepcha Analytics (nepcha.com) -->
|
<!-- Nepcha Analytics (nepcha.com) -->
|
||||||
<!-- Nepcha is a easy-to-use web analytics. No cookies and fully compliant with GDPR, CCPA and PECR. -->
|
<!-- Nepcha is a easy-to-use web analytics. No cookies and fully compliant with GDPR, CCPA and PECR. -->
|
||||||
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
|
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
|
||||||
<script type="module" crossorigin src="/assets/index-ad90a034.js"></script>
|
<script type="module" crossorigin src="/assets/index-5c7d29a2.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index-71775912.css">
|
<link rel="stylesheet" href="/assets/index-770f67cf.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ export function reducer(state, action) {
|
|||||||
export function MaterialTailwindControllerProvider({ children }) {
|
export function MaterialTailwindControllerProvider({ children }) {
|
||||||
const initialState = {
|
const initialState = {
|
||||||
openSidenav: false,
|
openSidenav: false,
|
||||||
sidenavColor: "dark",
|
sidenavColor: "gray",
|
||||||
sidenavType: "white",
|
sidenavType: "white",
|
||||||
transparentNavbar: true,
|
transparentNavbar: true,
|
||||||
fixedNavbar: false,
|
fixedNavbar: false,
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import App from "./App";
|
|||||||
import { BrowserRouter } from "react-router-dom";
|
import { BrowserRouter } from "react-router-dom";
|
||||||
import { ThemeProvider } from "@material-tailwind/react";
|
import { ThemeProvider } from "@material-tailwind/react";
|
||||||
import { MaterialTailwindControllerProvider } from "@/context";
|
import { MaterialTailwindControllerProvider } from "@/context";
|
||||||
import "../public/css/tailwind.css";
|
import "../dist/css/tailwind.css";
|
||||||
|
|
||||||
ReactDOM.createRoot(document.getElementById("root")).render(
|
ReactDOM.createRoot(document.getElementById("root")).render(
|
||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
|
|||||||
@@ -14,9 +14,62 @@ import {
|
|||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
Select,
|
Select,
|
||||||
Option,
|
Option,
|
||||||
Input
|
Input,
|
||||||
|
useSelect
|
||||||
} from "@material-tailwind/react";
|
} from "@material-tailwind/react";
|
||||||
|
|
||||||
|
const StickyOption = React.forwardRef(({ value, onValueSelect, onClick, onKeyDown, ...props }, ref) => {
|
||||||
|
const { setOpen } = useSelect();
|
||||||
|
|
||||||
|
const reopen = useCallback(() => {
|
||||||
|
const schedule = (typeof window !== "undefined" && typeof window.requestAnimationFrame === "function")
|
||||||
|
? window.requestAnimationFrame
|
||||||
|
: (callback) => setTimeout(callback, 0);
|
||||||
|
|
||||||
|
schedule(() => {
|
||||||
|
setOpen(true);
|
||||||
|
});
|
||||||
|
}, [setOpen]);
|
||||||
|
|
||||||
|
const commitSelection = useCallback(() => {
|
||||||
|
if (typeof onValueSelect === "function") {
|
||||||
|
onValueSelect(value);
|
||||||
|
}
|
||||||
|
}, [onValueSelect, value]);
|
||||||
|
|
||||||
|
const handleClick = useCallback((event) => {
|
||||||
|
if (typeof onClick === "function") {
|
||||||
|
onClick(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
commitSelection();
|
||||||
|
reopen();
|
||||||
|
}, [onClick, commitSelection, reopen]);
|
||||||
|
|
||||||
|
const handleKeyDown = useCallback((event) => {
|
||||||
|
if (typeof onKeyDown === "function") {
|
||||||
|
onKeyDown(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (event.key === "Enter" || event.key === " ") {
|
||||||
|
commitSelection();
|
||||||
|
reopen();
|
||||||
|
}
|
||||||
|
}, [onKeyDown, commitSelection, reopen]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Option
|
||||||
|
{...props}
|
||||||
|
value={value}
|
||||||
|
ref={ref}
|
||||||
|
onClick={handleClick}
|
||||||
|
onKeyDown={handleKeyDown}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
StickyOption.displayName = "StickyOption";
|
||||||
|
|
||||||
const STATUS_COLORS = {
|
const STATUS_COLORS = {
|
||||||
success: "text-green-400",
|
success: "text-green-400",
|
||||||
warning: "text-yellow-400",
|
warning: "text-yellow-400",
|
||||||
@@ -105,6 +158,8 @@ export function Logs() {
|
|||||||
const [perPage, setPerPage] = useState(PER_PAGE_DEFAULT);
|
const [perPage, setPerPage] = useState(PER_PAGE_DEFAULT);
|
||||||
const [page, setPage] = useState(1);
|
const [page, setPage] = useState(1);
|
||||||
|
|
||||||
|
const noop = useCallback(() => { }, []);
|
||||||
|
|
||||||
const updateFilterOptions = useCallback((payload) => {
|
const updateFilterOptions = useCallback((payload) => {
|
||||||
const logsPayload = Array.isArray(payload) ? payload : payload?.items ?? [];
|
const logsPayload = Array.isArray(payload) ? payload : payload?.items ?? [];
|
||||||
|
|
||||||
@@ -364,8 +419,34 @@ export function Logs() {
|
|||||||
}
|
}
|
||||||
}, [filtersReady, currentFilters, perPage, page]);
|
}, [filtersReady, currentFilters, perPage, page]);
|
||||||
|
|
||||||
const handleMultiSelectChange = (setter) => (event) => {
|
const handleMultiSelectChange = (setter) => (valueOrEvent) => {
|
||||||
const values = Array.from(event.target.selectedOptions).map((option) => option.value);
|
if (typeof valueOrEvent === "string") {
|
||||||
|
const value = valueOrEvent;
|
||||||
|
if (value === "") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setter((prev) => {
|
||||||
|
if (value === ALL_OPTION_VALUE) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prev.includes(value)) {
|
||||||
|
return prev.filter((entry) => entry !== value);
|
||||||
|
}
|
||||||
|
|
||||||
|
return [...prev, value];
|
||||||
|
});
|
||||||
|
setPage(1);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const selectedOptions = valueOrEvent?.target?.selectedOptions;
|
||||||
|
if (!selectedOptions) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = Array.from(selectedOptions).map((option) => option.value);
|
||||||
if (values.includes(ALL_OPTION_VALUE)) {
|
if (values.includes(ALL_OPTION_VALUE)) {
|
||||||
setter([]);
|
setter([]);
|
||||||
} else {
|
} else {
|
||||||
@@ -374,24 +455,8 @@ export function Logs() {
|
|||||||
setPage(1);
|
setPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleOptionMouseDown = (event, currentValues, setter) => {
|
const removeFilterValue = (setter, value) => {
|
||||||
event.preventDefault();
|
setter((prev) => prev.filter((entry) => entry !== value));
|
||||||
event.stopPropagation();
|
|
||||||
|
|
||||||
const { value } = event.target;
|
|
||||||
if (value === ALL_OPTION_VALUE) {
|
|
||||||
if (currentValues.length) {
|
|
||||||
setter([]);
|
|
||||||
setPage(1);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextValues = currentValues.includes(value)
|
|
||||||
? currentValues.filter((entry) => entry !== value)
|
|
||||||
: [...currentValues, value];
|
|
||||||
|
|
||||||
setter(nextValues);
|
|
||||||
setPage(1);
|
setPage(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -614,161 +679,174 @@ export function Logs() {
|
|||||||
<div className="grid gap-4 flex-1">
|
<div className="grid gap-4 flex-1">
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedStacks}
|
onChange={noop}
|
||||||
onChange={handleMultiSelectChange(setSelectedStacks)}
|
|
||||||
className="text-gray-500"
|
className="text-gray-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
|
dismiss={{ itemPress: false }}
|
||||||
label="Stacks"
|
label="Stacks"
|
||||||
>
|
>
|
||||||
{stackSelectOptions.map(({ value, label }) => (
|
{stackSelectOptions.map(({ value, label }) => (
|
||||||
<option
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onMouseDown={(event) => handleOptionMouseDown(event, selectedStacks, setSelectedStacks)}
|
onValueSelect={handleMultiSelectChange(setSelectedStacks)}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</StickyOption>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
||||||
{selectedStacks.length === 0 ? (
|
{selectedStacks.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300 ">
|
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300 ">
|
||||||
Alle Stacks
|
Alle Stacks
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-wrap gap-2">
|
<span>
|
||||||
{selectedStacks.map((stackId) => (
|
{selectedStacks.map((stackId) => (
|
||||||
<span
|
<button
|
||||||
key={stackId}
|
key={stackId}
|
||||||
className="rounded-full bg-purple-500/80 px-2 py-0.5 text-white"
|
type="button"
|
||||||
|
onClick={() => removeFilterValue(setSelectedStacks, stackId)}
|
||||||
|
className="rounded-full bg-purple-500/80 px-2 py-0.5 text-white transition hover:bg-purple-500/90 focus:outline-none focus:ring-2 focus:ring-purple-300 cursor-pointer"
|
||||||
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
{stackLabelMap.get(stackId) ?? `Stack ${stackId}`}
|
{stackLabelMap.get(stackId) ?? `Stack ${stackId}`}
|
||||||
</span>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 flex-1">
|
<div className="grid gap-4 flex-1">
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedStatuses}
|
onChange={noop}
|
||||||
onChange={handleMultiSelectChange(setSelectedStatuses)}
|
|
||||||
className="text-gray-500"
|
className="text-gray-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
|
dismiss={{ itemPress: false }}
|
||||||
label="Status"
|
label="Status"
|
||||||
>
|
>
|
||||||
{statusSelectOptions.map(({ value, label }) => (
|
{statusSelectOptions.map(({ value, label }) => (
|
||||||
<option
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onMouseDown={(event) => handleOptionMouseDown(event, selectedStatuses, setSelectedStatuses)}
|
onValueSelect={handleMultiSelectChange(setSelectedStatuses)}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</StickyOption>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
||||||
{selectedStatuses.length === 0 ? (
|
{selectedStatuses.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
||||||
Alle Status
|
Alle Status
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-wrap gap-2">
|
<span>
|
||||||
{selectedStatuses.map((status) => (
|
{selectedStatuses.map((status) => (
|
||||||
<span
|
<button
|
||||||
key={status}
|
key={status}
|
||||||
className="rounded-full bg-brown-500/80 px-2 py-0.5 text-white"
|
type="button"
|
||||||
|
onClick={() => removeFilterValue(setSelectedStatuses, status)}
|
||||||
|
className="rounded-full bg-brown-500/80 px-2 py-0.5 text-white transition hover:bg-brown-500/90 focus:outline-none focus:ring-2 focus:ring-brown-300 cursor-pointer"
|
||||||
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
{status}
|
{status}
|
||||||
</span>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 flex-1">
|
<div className="grid gap-4 flex-1">
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedRedeployTypes}
|
onChange={noop}
|
||||||
onChange={handleMultiSelectChange(setSelectedRedeployTypes)}
|
|
||||||
className="text-gray-500"
|
className="text-gray-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
|
dismiss={{ itemPress: false }}
|
||||||
label="Redeploy-Typ"
|
label="Redeploy-Typ"
|
||||||
>
|
>
|
||||||
{redeployTypeSelectOptions.map(({ value, label }) => (
|
{redeployTypeSelectOptions.map(({ value, label }) => (
|
||||||
<option
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onMouseDown={(event) => handleOptionMouseDown(event, selectedRedeployTypes, setSelectedRedeployTypes)}
|
onValueSelect={handleMultiSelectChange(setSelectedRedeployTypes)}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</StickyOption>
|
||||||
|
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
||||||
{selectedRedeployTypes.length === 0 ? (
|
{selectedRedeployTypes.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
||||||
Alle Typen
|
Alle Typen
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-wrap gap-2">
|
<span>
|
||||||
{selectedRedeployTypes.map((type) => (
|
{selectedRedeployTypes.map((type) => (
|
||||||
<span
|
<button
|
||||||
key={type}
|
key={type}
|
||||||
className="rounded-full bg-teal-500/80 px-2 py-0.5 text-white"
|
type="button"
|
||||||
|
onClick={() => removeFilterValue(setSelectedRedeployTypes, type)}
|
||||||
|
className="rounded-full bg-brown-500/80 px-2 py-0.5 text-white transition hover:bg-brown-500/90 focus:outline-none focus:ring-2 focus:ring-brown-300 cursor-pointer"
|
||||||
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
{REDEPLOY_TYPE_LABELS[type] ?? type}
|
{REDEPLOY_TYPE_LABELS[type] ?? type}
|
||||||
</span>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid gap-4 flex-1">
|
<div className="grid gap-4 flex-1">
|
||||||
<Select
|
<Select
|
||||||
multiple
|
multiple
|
||||||
value={selectedEndpoints}
|
onChange={noop}
|
||||||
onChange={handleMultiSelectChange(setSelectedRedeployTypes)}
|
|
||||||
className="text-gray-500"
|
className="text-gray-500"
|
||||||
variant="static"
|
variant="static"
|
||||||
|
dismiss={{ itemPress: false }}
|
||||||
label="Endpoints"
|
label="Endpoints"
|
||||||
>
|
>
|
||||||
{endpointSelectOptions.map(({ value, label }) => (
|
{endpointSelectOptions.map(({ value, label }) => (
|
||||||
<option
|
<StickyOption
|
||||||
key={value}
|
key={value}
|
||||||
value={value}
|
value={value}
|
||||||
onMouseDown={(event) => handleOptionMouseDown(event, selectedEndpoints, setSelectedEndpoints)}
|
onValueSelect={handleMultiSelectChange(setSelectedEndpoints)}
|
||||||
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
className={`text-black-600 ${value === ALL_OPTION_VALUE ? 'font-semibold text-black-800' : ''}`}
|
||||||
>
|
>
|
||||||
{label}
|
{label}
|
||||||
</option>
|
</StickyOption>
|
||||||
))}
|
))}
|
||||||
</Select>
|
</Select>
|
||||||
<div className="mt-2 min-h-[1.5rem] text-xs text-gray-400">
|
<div className="mt-2 mb-2 min-h-[1.5rem] text-xs text-gray-400">
|
||||||
{selectedEndpoints.length === 0 ? (
|
{selectedEndpoints.length === 0 ? (
|
||||||
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
<span className="rounded-full bg-gray-700/60 px-2 py-0.5 text-gray-300">
|
||||||
Alle Endpoints
|
Alle Endpoints
|
||||||
</span>
|
</span>
|
||||||
) : (
|
) : (
|
||||||
<div className="flex flex-wrap gap-2">
|
<span>
|
||||||
{selectedEndpoints.map((endpoint) => (
|
{selectedEndpoints.map((endpoint) => (
|
||||||
<span
|
<button
|
||||||
key={endpoint}
|
key={endpoint}
|
||||||
className="rounded-full bg-blue-500/80 px-2 py-0.5 text-white"
|
type="button"
|
||||||
|
onClick={() => removeFilterValue(setSelectedEndpoints, endpoint)}
|
||||||
|
className="rounded-full bg-teal-500/80 px-2 py-0.5 text-white transition hover:bg-teal-500/90 focus:outline-none focus:ring-2 focus:ring-brown-300 cursor-pointer"
|
||||||
|
title="Filter entfernen"
|
||||||
>
|
>
|
||||||
{endpoint}
|
Endpoint {endpoint}
|
||||||
</span>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-wrap gap-2 mt-10">
|
<div className="flex flex-wrap gap-2 mt-5">
|
||||||
<div className="grid gap-4 flex-1">
|
<div className="grid gap-4 flex-1">
|
||||||
<Input
|
<Input
|
||||||
value={messageQuery}
|
value={messageQuery}
|
||||||
@@ -781,18 +859,75 @@ export function Logs() {
|
|||||||
placeholder="Suche" />
|
placeholder="Suche" />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<div className="flex flex-col md:flex-row flex-wrap gap-4 mt-8">
|
||||||
|
<div className="flex-1">
|
||||||
|
<Input
|
||||||
|
type="datetime-local"
|
||||||
|
variant="static"
|
||||||
|
label="Von"
|
||||||
|
value={fromDate}
|
||||||
|
onChange={(event) => {
|
||||||
|
setFromDate(event.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Input
|
||||||
|
type="datetime-local"
|
||||||
|
variant="static"
|
||||||
|
label="Bis"
|
||||||
|
value={toDate}
|
||||||
|
onChange={(event) => {
|
||||||
|
setToDate(event.target.value);
|
||||||
|
setPage(1);
|
||||||
|
}}
|
||||||
|
className="w-full"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div className="flex-1">
|
||||||
|
<Button
|
||||||
|
onClick={handleResetFilters}
|
||||||
|
disabled={actionLoading || loading}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
Zurücksetzen
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
)}
|
)}
|
||||||
|
|
||||||
</CardBody>
|
</CardBody>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
|
||||||
<Card>
|
<Card>
|
||||||
<CardHeader variant="gradient" color="gray" className="mb-8 p-6">
|
<CardHeader variant="gradient" color="gray" className="mb-8 p-6">
|
||||||
<Typography variant="h6" color="white">
|
<Typography
|
||||||
Logs
|
variant="h6"
|
||||||
|
color="white"
|
||||||
|
className="flex items-center justify-between"
|
||||||
|
>
|
||||||
|
<span>Logs</span>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<span className="text-xs uppercase tracking-wide text-gray-400">
|
||||||
|
Einträge pro Seite:
|
||||||
|
</span>
|
||||||
|
<select
|
||||||
|
value={perPage}
|
||||||
|
onChange={handlePerPageChange}
|
||||||
|
className="rounded-md border border-gray-700 bg-gray-900 px-2 py-1 text-sm text-gray-100 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500"
|
||||||
|
>
|
||||||
|
{PER_PAGE_OPTIONS.map(({ value, label }) => (
|
||||||
|
<option key={value} value={value}>
|
||||||
|
{label}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</Typography>
|
</Typography>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardBody className="overflow-x-scroll px-0 pt-0 pb-2">
|
<CardBody className="overflow-x-scroll px-0 pt-0 pb-2">
|
||||||
|
|||||||
@@ -807,12 +807,12 @@ export default function Logs() {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-wrap items-center justify-end gap-3">
|
<div className="flex flex-wrap items-center justify-end gap-3">
|
||||||
<button
|
<Button
|
||||||
onClick={handleResetFilters}
|
onClick={handleResetFilters}
|
||||||
className="rounded-md border border-gray-600 px-4 py-2 text-gray-200 transition hover:bg-gray-700"
|
className="rounded-md border border-gray-600 px-4 py-2 text-gray-200 transition hover:bg-gray-700"
|
||||||
>
|
>
|
||||||
Zurücksetzen
|
Zurücksetzen
|
||||||
</button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|||||||
Reference in New Issue
Block a user