This commit is contained in:
root
2025-10-21 16:16:25 +00:00
parent 5852fc6616
commit f45a2525e6
7 changed files with 174 additions and 168 deletions
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
File diff suppressed because one or more lines are too long
+2 -2
View File
@@ -32,8 +32,8 @@
<!-- Nepcha Analytics (nepcha.com) -->
<!-- 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 type="module" crossorigin src="/assets/index-186727a8.js"></script>
<link rel="stylesheet" href="/assets/index-caeb4473.css">
<script type="module" crossorigin src="/assets/index-24fe32b2.js"></script>
<link rel="stylesheet" href="/assets/index-06b3a129.css">
</head>
<body>
<div id="root"></div>
+1 -1
View File
@@ -6,7 +6,7 @@ function App() {
<Routes>
<Route path="/dashboard/*" element={<Dashboard />} />
<Route path="/auth/*" element={<Auth />} />
<Route path="*" element={<Navigate to="/dashboard/home" replace />} />
<Route path="*" element={<Navigate to="/dashboard/stacks" replace />} />
</Routes>
);
}
+62 -59
View File
@@ -9,6 +9,7 @@ import {
CardHeader,
CardBody,
Button,
ButtonGroup,
IconButton,
Menu,
MenuHandler,
@@ -17,6 +18,10 @@ import {
Avatar,
Tooltip,
Progress,
Spinner,
Input,
Select,
Option
} from "@material-tailwind/react";
const SELECTION_PROMPT_STORAGE_KEY = "stackSelectionPreference";
@@ -72,6 +77,7 @@ export function Stacks() {
const maintenanceMessage = maintenance?.message;
const maintenanceLocked = maintenanceActive || Boolean(update?.running);
const maintenanceBanner = maintenanceLocked ? (maintenanceMessage || (update?.running ? "Portainer-Update läuft" : "Wartungsmodus aktiv")) : "";
const noop = useCallback(() => { }, []);
const stacksByIdRef = useRef(new Map());
@@ -500,8 +506,8 @@ export function Stacks() {
);
};
const handlePerPageChange = (event) => {
const value = event.target.value;
const handlePerPageChange = (value) => {
if (!VALID_PER_PAGE_VALUES.has(value)) return;
setPerPage(value);
setPage(1);
@@ -772,60 +778,73 @@ export function Stacks() {
}
};
if (loading) return <p className="text-gray-400">Lade Stacks...</p>;
if (error) return <p className="text-red-400">{error}</p>;
return (
<div className="mt-12 mb-8 flex flex-col gap-12">
<Card>
<CardHeader variant="gradient" color="gray" className="p-4 pt-2 pb-2">
{!loading && !error ? (
<Typography variant="h6" color="white">
<span className="flex items-center gap-2">
<span>Filter und Optionen</span></span>
</Typography>
) :
(
<Typography variant="h6" color="white">
<span className="flex items-center gap-2">
<span>Lade Stacks</span></span>
</Typography>)
}
</CardHeader>
<CardBody>
<div className="mb-6 flex flex-col gap-4 md:flex-row md:items-end md:justify-between">
<div className="flex flex-col gap-4 md:flex-row md:items-end md:gap-6">
{loading ? (
<p className="flex py-6 text-center text-sm font-medium text-gray-400"> <Spinner className="mr-2 h-4 w-4 flex" />Lade Stacks...</p>
) : error ? (
<p className="py-6 text-sm font-medium text-red-400">{error}</p>
) : (
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<span className="mb-2 block text-sm font-medium text-gray-300">Status</span>
<div className="inline-flex overflow-hidden rounded-lg border border-gray-700 bg-gray-900">
<button
type="button"
<ButtonGroup
>
<Button
onClick={() => setStatusFilter('all')}
className={`px-4 py-2 text-sm font-medium transition ${statusFilter === 'all' ? 'bg-purple-600 text-white' : 'text-gray-300 hover:text-white'} border-r border-gray-700 last:border-r-0`}
>
Alle
</button>
<button
type="button"
>Alle</Button>
<Button
onClick={() => setStatusFilter('current')}
className={`px-4 py-2 text-sm font-medium transition ${statusFilter === 'current' ? 'bg-purple-600 text-white' : 'text-gray-300 hover:text-white'} border-r border-gray-700 last:border-r-0`}
>
Aktuell
</button>
<button
type="button"
>Aktuell</Button>
<Button
onClick={() => setStatusFilter('outdated')}
className={`px-4 py-2 text-sm font-medium transition ${statusFilter === 'outdated' ? 'bg-purple-600 text-white' : 'text-gray-300 hover:text-white'}`}
>
Veraltet
</button>
>Veraltet</Button>
</ButtonGroup>
</div>
</div>
<div className="md:w-64">
<label htmlFor="stack-search" className="mb-2 block text-sm font-medium text-gray-300">Suche</label>
<input
id="stack-search"
type="text"
<Input
value={searchQuery}
onChange={(event) => setSearchQuery(event.target.value)}
placeholder="Name oder ID"
className="w-full rounded-lg border border-gray-700 bg-gray-900 px-3 py-2 text-sm text-gray-100 placeholder-gray-500 focus:border-purple-500 focus:outline-none focus:ring-1 focus:ring-purple-500"
/>
</div>
variant="static"
placeholder="Name oder ID" />
<div className="flex items-center gap-3">
<Select
variant="static"
label="Einträge pro Seite"
onChange={noop}
value={perPage}
>
{PER_PAGE_OPTIONS.map(({ value, label }) => (
<Option
key={value}
value={value}
onClick={() => handlePerPageChange(value)}
>
{label}
</Option>
))}
</Select>
</div>
<div className="flex flex-col items-end gap-2">
{selectionPreferenceStored && (
<button
@@ -836,31 +855,16 @@ export function Stacks() {
Gespeicherte Entscheidung löschen
</button>
)}
<button
onClick={handleBulkRedeploy}
disabled={bulkActionDisabled}
className={`px-5 py-2 rounded-lg font-medium transition ${bulkActionDisabled ? 'bg-purple-900 cursor-not-allowed text-gray-400' : 'bg-purple-500 hover:bg-purple-600'}`}
>
{bulkButtonLabel}
</button>
<label className="flex items-center gap-2 text-sm text-gray-300">
<span>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((option) => (
<option key={option.value} value={option.value}>
{option.label}
</option>
))}
</select>
</label>
</div>
</div>
)}
</CardBody>
</Card>
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
{paginatedStacks.map(stack => {
@@ -872,7 +876,6 @@ export function Stacks() {
const isCurrent = stack.updateStatus === '✅';
const isSelfStack = Boolean(stack.redeployDisabled);
const isSelectable = !isBusy && !isCurrent && !isSelfStack && !maintenanceLocked;
return (
<Card key={stack.Id}>
<CardBody