This commit is contained in:
michael committed 2025-12-01 07:59:40 +00:00
1 parent fc3ed57a40
commit f109423f54
39 files changed
+2626 -2670

No files matched your search

+2 -3
View File
@@ -3,7 +3,7 @@
# 📦 StackPulse ![Release](https://img.shields.io/badge/release-v0.5-blue.svg)
**StackPulse** ist eine kleine Web-App, die über die Portainer-API deine Docker-Stacks verwaltet und aktualisiert.
Aktuell funktioniert StackPulse nur mit der Business-Edition von Portainer. Die Communitiy-Edition wird in einem späteren Release implementiert!
StackPulse funktioniert nur mit der Business-Edition von Portainer. Die Communitiy-Edition wird nicht unterstützt!
Sie besteht aus einem **Backend (Node.js/Express)** und einem **Frontend (React/Tailwind)**.
Ziel:
@@ -124,9 +124,8 @@ Ziel:
- Notifications (z. B. via Webhooks oder Mail)
- Monitoring (Status, CPU/RAM)
- Verbesserte UI/UX
- Erweiterte Filterungen udn Sortierungen
- Erweiterte Filterungen und Sortierungen
- Multi-Server Verwaltung
- Integration Community Edition
</details>
---
-12
View File
@@ -1,12 +0,0 @@
FROM node:22-alpine
WORKDIR /app
COPY package*.json ./
RUN npm install --omit=dev
COPY src ./src
EXPOSE 7070
CMD ["node", "src/index.js"]
-34
View File
@@ -1,34 +0,0 @@
# Stackpulse Agent
Der Stackpulse Agent stellt Docker- und Host-Metadaten über eine REST-API bereit. Er verbindet sich lokal über `/var/run/docker.sock`, validiert alle Anfragen per `X-Agent-Token` und läuft standardmäßig auf Port `7070`.
## Start lokal
1. Abhängigkeiten installieren: `npm install`
2. Starten: `AGENT_TOKEN=<token> npm start`
3. Optional: Port via `AGENT_PORT` anpassen.
## Als Docker-Container
```
docker build -t stackpulse-agent ./agent
docker run -d \
-p 7070:7070 \
-e AGENT_TOKEN=<token> \
-v /var/run/docker.sock:/var/run/docker.sock \
stackpulse-agent
```
Der Agent liest nur Docker-Daten und verändert keine Container oder Images.
## Stack-Image-Checks (CE-/BE-Parität)
Endpoint: `GET /stack-images?checkUpdates=true|false`
- Liefert pro Stack (Compose: `com.docker.compose.project`, Swarm: `com.docker.stack.namespace`) die verwendeten Images.
- Optional `checkUpdates=true` für lokalen/remote Digest-Vergleich (`updateAvailable`).
### Portainer-Version
- `GET /portainer/version` – liest den laufenden Portainer-Container (Image/Tag/Labels) aus, ermittelt lokale Digest und vergleicht mit Registry-Digest (Update-Indikator). Funktioniert ohne Portainer-API.
Optionale Registry-Creds (für private Registries):
- `REGISTRY_TOKEN` (Bearer)
- oder `REGISTRY_USERNAME` + `REGISTRY_PASSWORD` (Basic)
Hinweis: Für Swarm-Stacks muss der Agent Swarm-Services lesen können (`docker.listServices`). Ohne Swarm-Manager-Rolle bleibt die Swarm-Liste leer.
-13
View File
@@ -1,13 +0,0 @@
{
"name": "stackpulse-agent",
"version": "0.0.1",
"description": "Stackpulse Agent for Docker metadata and update checks",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js"
},
"dependencies": {
"dockerode": "^4.0.2",
"express": "^4.19.2"
}
}
-49
View File
@@ -1,49 +0,0 @@
const express = require('express');
const Docker = require('dockerode');
const infoRoutes = require('./routes/info');
const containerRoutes = require('./routes/containers');
const imageRoutes = require('./routes/images');
const stackRoutes = require('./routes/stacks');
const portainerRoutes = require('./routes/portainer');
const AGENT_PORT = parseInt(process.env.AGENT_PORT || '7070', 10);
const AGENT_TOKEN = process.env.AGENT_TOKEN;
const DOCKER_SOCKET = process.env.DOCKER_SOCKET || '/var/run/docker.sock';
const app = express();
const docker = new Docker({ socketPath: DOCKER_SOCKET });
app.use(express.json());
if (!AGENT_TOKEN) {
// Keep the server running to allow late injection, but warn loudly.
console.warn('AGENT_TOKEN is not set. All requests will be rejected until it is configured.');
}
app.use((req, res, next) => {
const token = req.header('X-Agent-Token');
if (!token || token !== AGENT_TOKEN) {
return res.status(403).json({ error: 'Forbidden' });
}
return next();
});
app.use(infoRoutes(docker));
app.use(containerRoutes(docker));
app.use(imageRoutes(docker));
app.use(stackRoutes(docker));
app.use(portainerRoutes(docker));
app.use((req, res) => res.status(404).json({ error: 'Not found' }));
app.use((err, req, res, next) => {
console.error('Unhandled error:', err);
res.status(500).json({ error: 'Internal server error' });
});
app.listen(AGENT_PORT, () => {
console.log(`Stackpulse Agent listening on port ${AGENT_PORT}`);
});
-76
View File
@@ -1,76 +0,0 @@
const express = require('express');
module.exports = function containerRoutes(docker) {
const router = express.Router();
router.get('/containers', async (req, res, next) => {
try {
const containers = await docker.listContainers({ all: true });
const payload = containers.map((container) => ({
id: container.Id,
name: Array.isArray(container.Names) && container.Names.length > 0
? container.Names[0].replace(/^\//, '')
: null,
status: container.Status,
image: container.Image,
ports: container.Ports,
labels: container.Labels,
}));
res.json(payload);
} catch (err) {
next(err);
}
});
router.get('/stacks', async (req, res, next) => {
try {
const containers = await docker.listContainers({ all: true });
const stacks = containers.reduce((acc, container) => {
const composeProject = container.Labels?.['com.docker.compose.project'];
const swarmStack = container.Labels?.['com.docker.stack.namespace'];
const stackName = composeProject || swarmStack || 'unassigned';
if (!acc[stackName]) {
acc[stackName] = [];
}
acc[stackName].push({
id: container.Id,
name: Array.isArray(container.Names) && container.Names.length > 0
? container.Names[0].replace(/^\//, '')
: null,
status: container.Status,
image: container.Image,
ports: container.Ports,
labels: container.Labels,
});
return acc;
}, {});
const payload = Object.entries(stacks).map(([name, stackContainers]) => ({
name,
containers: stackContainers,
}));
res.json(payload);
} catch (err) {
next(err);
}
});
router.get('/container/:id', async (req, res, next) => {
try {
const container = docker.getContainer(req.params.id);
const inspect = await container.inspect();
res.json(inspect);
} catch (err) {
next(err);
}
});
return router;
};
-41
View File
@@ -1,41 +0,0 @@
const express = require('express');
const { checkImageUpdate, getLocalImageInfo } = require('../services/registryService');
module.exports = function imageRoutes(docker) {
const router = express.Router();
router.get('/images', async (req, res, next) => {
try {
const images = await docker.listImages();
const payload = images.map((image) => ({
repoTags: image.RepoTags || [],
size: image.Size,
id: image.Id,
created: image.Created,
}));
res.json(payload);
} catch (err) {
next(err);
}
});
// Accept full image name with slashes/colons: /image/check/<image> (URL-encoded)
router.get('/image/check/*', async (req, res) => {
const raw = req.params[0] || req.params.name || '';
if (!raw) {
return res.status(400).json({ error: 'invalid_image' });
}
const imageName = decodeURIComponent(raw);
try {
const result = await checkImageUpdate(docker, imageName);
res.json(result);
} catch (err) {
const status = err.status || 500;
res.status(status).json({ error: err.code || err.message || 'Internal error' });
}
});
return router;
};
-33
View File
@@ -1,33 +0,0 @@
const express = require('express');
const os = require('os');
module.exports = function infoRoutes(docker) {
const router = express.Router();
router.get('/info', async (req, res, next) => {
try {
const [dockerVersion, containers, images] = await Promise.all([
docker.version(),
docker.listContainers({ all: true }),
docker.listImages(),
]);
res.json({
dockerVersion: dockerVersion.Version,
hostname: os.hostname(),
uptime: os.uptime(),
platform: os.platform(),
containerCount: containers.length,
imageCount: images.length,
});
} catch (err) {
next(err);
}
});
router.get('/events', (req, res) => {
res.json({ message: 'Events endpoint not implemented yet' });
});
return router;
};
-80
View File
@@ -1,80 +0,0 @@
const express = require('express');
const { checkImageUpdate } = require('../services/registryService');
function extractTag(imageName) {
const parts = imageName.split(':');
return parts.length > 1 ? parts[parts.length - 1] : 'latest';
}
function pickPortainerContainer(containers) {
return containers.find((container) => {
const labels = container.Labels || {};
if (labels['io.portainer.container']) return true;
const image = container.Image || '';
return /portainer/i.test(image);
});
}
async function getPortainerVersionInfo(docker) {
const containers = await docker.listContainers({ all: true });
const portainer = pickPortainerContainer(containers);
if (!portainer) {
return null;
}
const imageName = portainer.Image;
const tag = extractTag(imageName);
let version = null;
let localDigest = null;
let remoteDigest = null;
let updateAvailable = false;
try {
const inspect = await docker.getImage(imageName).inspect();
const labels = inspect?.Config?.Labels || {};
version = labels['org.opencontainers.image.version'] || labels.version || tag;
const repoDigests = inspect?.RepoDigests || [];
localDigest = repoDigests.length ? repoDigests[0].split('@')[1] || repoDigests[0] : null;
} catch {
version = tag;
localDigest = null;
}
try {
const info = await checkImageUpdate(docker, imageName);
remoteDigest = info.remoteDigest;
localDigest = info.localDigest || localDigest;
updateAvailable = info.updateAvailable;
} catch {
remoteDigest = null;
updateAvailable = false;
}
return {
containerId: portainer.Id,
image: imageName,
tag,
version,
localDigest,
remoteDigest,
updateAvailable
};
}
module.exports = function portainerVersionRoutes(docker) {
const router = express.Router();
router.get('/portainer/version', async (req, res, next) => {
try {
const info = await getPortainerVersionInfo(docker);
if (!info) {
return res.status(404).json({ error: 'Portainer container not found' });
}
res.json(info);
} catch (err) {
next(err);
}
});
return router;
};
-135
View File
@@ -1,135 +0,0 @@
const express = require('express');
const { checkImageUpdate, getLocalImageInfo } = require('../services/registryService');
async function getComposeStacks(docker) {
const containers = await docker.listContainers({ all: true });
const stacks = containers.reduce((acc, container) => {
const composeProject = container.Labels?.['com.docker.compose.project'];
if (!composeProject) return acc;
if (!acc[composeProject]) {
acc[composeProject] = {
name: composeProject,
type: 'compose',
images: new Map(),
};
}
acc[composeProject].images.set(container.Image, {
source: 'container',
name: container.Image
});
return acc;
}, {});
return Object.values(stacks);
}
async function getSwarmStacks(docker) {
try {
const services = await docker.listServices();
const stacks = services.reduce((acc, service) => {
const stackNs = service.Spec?.Labels?.['com.docker.stack.namespace'];
if (!stackNs) return acc;
if (!acc[stackNs]) {
acc[stackNs] = {
name: stackNs,
type: 'swarm',
images: new Map(),
};
}
const image = service.Spec?.TaskTemplate?.ContainerSpec?.Image;
if (image) {
acc[stackNs].images.set(image, {
source: service.Spec?.Name || 'service',
name: image
});
}
return acc;
}, {});
return Object.values(stacks);
} catch (err) {
// Not a swarm or no permissions; just return empty
if (err.statusCode === 503 || /This node is not a swarm manager/i.test(err.message || '')) {
return [];
}
throw err;
}
}
async function enrichImagesWithDigests(docker, imageEntries) {
return Promise.all(imageEntries.map(async (entry) => {
try {
return await checkImageUpdate(docker, entry.name);
} catch (err) {
const status = err.status || 500;
return {
...entry,
localDigest: null,
remoteDigest: null,
updateAvailable: false,
error: err.code || err.message || `update_check_failed_${status}`
};
}
}));
}
module.exports = function stackRoutes(docker) {
const router = express.Router();
router.get('/stack-images', async (req, res, next) => {
const withChecks = req.query.checkUpdates === 'true';
try {
const [composeStacks, swarmStacks] = await Promise.all([
getComposeStacks(docker),
getSwarmStacks(docker)
]);
const combined = [...composeStacks, ...swarmStacks];
const result = await Promise.all(combined.map(async (stack) => {
const images = Array.from(stack.images.values());
if (!withChecks) {
return { name: stack.name, type: stack.type, images };
}
const enriched = await enrichImagesWithDigests(docker, images);
return { name: stack.name, type: stack.type, images: enriched };
}));
res.json(result);
} catch (err) {
next(err);
}
});
// Always return local/remote digests with error details when resolution fails
router.get('/stack-images/status', async (req, res, next) => {
try {
const [composeStacks, swarmStacks] = await Promise.all([
getComposeStacks(docker),
getSwarmStacks(docker)
]);
const combined = [...composeStacks, ...swarmStacks];
const result = await Promise.all(combined.map(async (stack) => {
const images = Array.from(stack.images.values());
const enriched = await enrichImagesWithDigests(docker, images);
return { name: stack.name, type: stack.type, images: enriched };
}));
res.json(result);
} catch (err) {
next(err);
}
});
return router;
};
-372
View File
@@ -1,372 +0,0 @@
const DEFAULT_REGISTRY = 'dockerhub';
const GHCR_HOST = 'ghcr.io';
const DOCKERHUB_HOST = 'registry-1.docker.io';
const ACCEPT_HEADERS = [
'application/vnd.oci.image.manifest.v1+json',
'application/vnd.docker.distribution.manifest.v2+json'
];
function createError(status, code, message) {
const err = new Error(message || code);
err.status = status;
err.code = code;
return err;
}
function detectRegistry(imageName = '') {
if (imageName.startsWith(`${GHCR_HOST}/`)) return 'ghcr';
if (!imageName.includes('/')) return 'dockerhub';
return 'generic';
}
function splitImageIntoComponents(imageName) {
if (!imageName) throw createError(400, 'invalid_image', 'Image name required');
const registryType = detectRegistry(imageName);
let registryHost;
let remainder = imageName;
if (registryType === 'ghcr') {
registryHost = GHCR_HOST;
remainder = imageName.replace(`${GHCR_HOST}/`, '');
} else if (registryType === 'dockerhub') {
registryHost = DOCKERHUB_HOST;
if (!imageName.includes('/')) {
remainder = `library/${imageName}`;
} else if (imageName.startsWith('docker.io/')) {
remainder = imageName.replace('docker.io/', '');
} else if (imageName.startsWith(`${DOCKERHUB_HOST}/`)) {
remainder = imageName.replace(`${DOCKERHUB_HOST}/`, '');
}
} else {
const firstSlash = imageName.indexOf('/');
registryHost = imageName.slice(0, firstSlash);
remainder = imageName.slice(firstSlash + 1);
}
const digestIndex = remainder.indexOf('@');
let repoPart = remainder;
let digest = null;
if (digestIndex !== -1) {
repoPart = remainder.slice(0, digestIndex);
digest = remainder.slice(digestIndex + 1);
}
const lastColon = repoPart.lastIndexOf(':');
let tag = 'latest';
let repository = repoPart;
if (lastColon > -1 && repoPart.indexOf('/') < lastColon) {
tag = repoPart.slice(lastColon + 1);
repository = repoPart.slice(0, lastColon);
}
return {
registryType,
registryHost,
repository,
tag,
digest,
fullImage: imageName
};
}
async function getLocalImageInfo(docker, imageName, preferredTag = null) {
try {
const image = docker.getImage(imageName);
const inspect = await image.inspect();
const repoTags = inspect?.RepoTags || [];
const repoDigests = inspect?.RepoDigests || [];
let localTagFull = repoTags.length ? repoTags[0] : null;
let localDigestFull = repoDigests.length ? repoDigests[0] : null;
// Fallback: search listImages for matching repoTag when digest is missing
if (!localDigestFull) {
const images = await docker.listImages();
const matchByTag = images.find((img) => (img.RepoTags || []).includes(imageName));
const normalizedRepo = imageName.includes(':') ? imageName.split(':')[0] : imageName;
const matchByRepo = images.find((img) => (img.RepoTags || []).some((t) => t.startsWith(`${normalizedRepo}:`)));
const candidate = matchByTag || matchByRepo;
if (candidate) {
localDigestFull = (candidate.RepoDigests || [])[0] || null;
localTagFull = (candidate.RepoTags || [])[0] || localTagFull;
}
}
let localTag = preferredTag || null;
if (!localTag) {
localTag = localTagFull && localTagFull.includes(':')
? localTagFull.split(':').pop()
: null;
}
const localDigestHash = localDigestFull && localDigestFull.includes('@')
? localDigestFull.split('@')[1]
: localDigestFull;
return { localTag, localDigestFull, localDigestHash };
} catch (err) {
throw createError(404, 'image_not_found', `Local image not found: ${imageName}`);
}
}
function buildBasicAuthHeader() {
const username = process.env.REGISTRY_USERNAME || process.env.GITHUB_USERNAME;
const password = process.env.REGISTRY_PASSWORD || process.env.GITHUB_TOKEN || process.env.GITHUB_PAT;
if (!username || !password) return {};
const base = Buffer.from(`${username}:${password}`, 'utf8').toString('base64');
return { Authorization: `Basic ${base}` };
}
function buildBearerHeader() {
const token = process.env.REGISTRY_TOKEN;
if (!token) return {};
return { Authorization: `Bearer ${token}` };
}
async function fetchRemoteManifest(registryHost, repository, reference, headers) {
const url = `https://${registryHost}/v2/${repository}/manifests/${reference}`;
const res = await fetch(url, { headers });
const body = await res.text();
return { res, body };
}
async function fetchRegistryTokenDockerHub(repository) {
const headers = {
Accept: ACCEPT_HEADERS.join(', '),
...buildBearerHeader(),
...buildBasicAuthHeader(),
};
const url = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repository}:pull`;
const res = await fetch(url, { headers });
if (!res.ok) {
const body = await res.text();
throw createError(res.status === 401 ? 403 : res.status, 'token_error', `Failed to fetch Docker Hub token: ${res.status} ${body || ''}`.trim());
}
const data = await res.json();
if (!data.token) {
throw createError(403, 'token_error', 'Token missing in Docker Hub response');
}
return data.token;
}
async function fetchAnonymousTokenDockerHub(repository) {
const headers = {
Accept: ACCEPT_HEADERS.join(', ')
};
const url = `https://auth.docker.io/token?service=registry.docker.io&scope=repository:${repository}:pull`;
const res = await fetch(url, { headers });
if (!res.ok) {
const body = await res.text();
throw createError(res.status === 401 ? 403 : res.status, 'token_error', `Failed to fetch Docker Hub token anonymously: ${res.status} ${body || ''}`.trim());
}
const data = await res.json();
if (!data.token) {
throw createError(403, 'token_error', 'Token missing in Docker Hub anonymous response');
}
return data.token;
}
async function resolveRemoteDigestDockerHub(repository, tag, localDigestHash) {
const accept = { Accept: ACCEPT_HEADERS.join(', ') };
let token;
try {
token = await fetchRegistryTokenDockerHub(repository);
} catch {
token = null;
}
let headers = token ? { ...accept, Authorization: `Bearer ${token}` } : { ...accept };
let { res, body } = await fetchRemoteManifest(DOCKERHUB_HOST, repository, tag, headers);
if (res.status === 404 && localDigestHash) {
({ res, body } = await fetchRemoteManifest(DOCKERHUB_HOST, repository, localDigestHash, headers));
}
if ((res.status === 401 || res.status === 403) && !token) {
try {
const anonToken = await fetchAnonymousTokenDockerHub(repository);
headers = { ...accept, Authorization: `Bearer ${anonToken}` };
({ res, body } = await fetchRemoteManifest(DOCKERHUB_HOST, repository, tag, headers));
if (res.status === 404 && localDigestHash) {
({ res, body } = await fetchRemoteManifest(DOCKERHUB_HOST, repository, localDigestHash, headers));
}
} catch {
// fall through
}
}
if (!res.ok) {
if (res.status === 404) throw createError(404, 'tag_not_found', 'Tag not found on registry');
if (res.status === 401 || res.status === 403) throw createError(403, 'auth_required', body || 'Unauthorized');
throw createError(502, 'registry_error', `Registry responded with ${res.status} ${body || ''}`.trim());
}
const digest = extractDigestFromManifest(body);
if (!digest) throw createError(500, 'digest_missing', 'Could not extract digest from manifest');
return digest;
}
async function resolveRemoteDigestGeneric(registryHost, repository, tag, localDigestHash) {
const accept = { Accept: ACCEPT_HEADERS.join(', ') };
const headers = {
...accept,
...buildBearerHeader(),
...buildBasicAuthHeader(),
};
let { res, body } = await fetchRemoteManifest(registryHost, repository, tag, headers);
if (res.status === 404 && localDigestHash) {
({ res, body } = await fetchRemoteManifest(registryHost, repository, localDigestHash, headers));
}
if (!res.ok) {
if (res.status === 404) throw createError(404, 'tag_not_found', 'Tag not found on registry');
if (res.status === 401 || res.status === 403) throw createError(403, 'auth_required', body || 'Unauthorized');
throw createError(502, 'registry_error', `Registry responded with ${res.status} ${body || ''}`.trim());
}
const digest = extractDigestFromManifest(body);
if (!digest) throw createError(500, 'digest_missing', 'Could not extract digest from manifest');
return digest;
}
async function resolveRemoteDigest(registryType, registryHost, repository, tag, localDigestHash) {
const fetchDigestViaBuildx = async (ref) => {
const { execFile } = require('child_process');
return new Promise((resolve, reject) => {
execFile('docker', ['buildx', 'imagetools', 'inspect', ref], { maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
if (err) {
const msg = stderr || err.message || 'docker buildx imagetools inspect failed';
return reject(createError(502, 'registry_error', msg.trim()));
}
const match = stdout.match(/Digest:\s*(sha256:[a-fA-F0-9]+)/);
if (match && match[1]) {
return resolve(match[1]);
}
return reject(createError(500, 'digest_missing', 'Could not extract digest from imagetools inspect'));
});
});
};
const fetchRemoteDigestViaDocker = async (ref) => {
const { execFile } = require('child_process');
return new Promise((resolve, reject) => {
execFile('docker', ['manifest', 'inspect', ref], { maxBuffer: 4 * 1024 * 1024 }, (err, stdout, stderr) => {
if (err) {
const msg = stderr || err.message || 'docker manifest inspect failed';
return reject(createError(502, 'registry_error', msg.trim()));
}
try {
const json = JSON.parse(stdout);
if (Array.isArray(json.manifests) && json.manifests.length > 0) {
const candidate = json.manifests[0];
if (candidate?.digest) {
return resolve(candidate.digest);
}
}
if (json?.config?.digest) {
return resolve(json.config.digest);
}
if (Array.isArray(json?.layers) && json.layers.length > 0 && json.layers[0].digest) {
return resolve(json.layers[0].digest);
}
} catch {
// fall through to error
}
return reject(createError(500, 'digest_missing', 'Could not extract digest from docker manifest inspect'));
});
});
};
if (registryType === 'ghcr') {
// Use docker buildx imagetools inspect for GHCR: tag first, then digest fallback
const host = registryHost || GHCR_HOST;
try {
const digest = await fetchDigestViaBuildx(`${host}/${repository}:${tag}`);
return digest;
} catch (err) {
if (localDigestHash) {
return fetchDigestViaBuildx(`${host}/${repository}@${localDigestHash}`);
}
throw err.code ? err : createError(502, 'registry_error', err.message || 'GHCR lookup failed');
}
}
if (registryType === 'dockerhub') {
const host = 'docker.io';
try {
return await fetchDigestViaBuildx(`${host}/${repository}:${tag}`);
} catch (err) {
if (localDigestHash) {
try {
return await fetchDigestViaBuildx(`${host}/${repository}@${localDigestHash}`);
} catch {
// fall through
}
}
}
// Fallback to manifest inspect/HTTP if buildx fails
try {
return await fetchRemoteDigestViaDocker(`${host}/${repository}:${tag}`);
} catch (err) {
if (localDigestHash) {
try {
return await fetchRemoteDigestViaDocker(`${host}/${repository}@${localDigestHash}`);
} catch {
// fall through
}
}
}
return resolveRemoteDigestDockerHub(repository, tag, localDigestHash);
}
// generic
try {
return await fetchDigestViaBuildx(`${registryHost}/${repository}:${tag}`);
} catch (err) {
if (localDigestHash) {
try {
return await fetchDigestViaBuildx(`${registryHost}/${repository}@${localDigestHash}`);
} catch {
// fall through
}
}
}
// fallback manifest path
try {
return await fetchRemoteDigestViaDocker(`${registryHost}/${repository}:${tag}`);
} catch (err) {
if (localDigestHash) {
try {
return await fetchRemoteDigestViaDocker(`${registryHost}/${repository}@${localDigestHash}`);
} catch {
// fall through to HTTP
}
}
}
return resolveRemoteDigestGeneric(registryHost, repository, tag, localDigestHash);
}
async function checkImageUpdate(docker, imageName) {
const { registryType, registryHost, repository, tag } = splitImageIntoComponents(imageName);
const { localTag, localDigestHash } = await getLocalImageInfo(docker, imageName, tag);
const remoteDigest = await resolveRemoteDigest(registryType, registryHost, repository, tag, localDigestHash);
return {
image: imageName,
localTag,
localDigest: localDigestHash,
remoteDigest,
updateAvailable: Boolean(localDigestHash && remoteDigest && localDigestHash !== remoteDigest)
};
}
module.exports = {
detectRegistry,
splitImageIntoComponents,
getLocalImageInfo,
checkImageUpdate,
resolveRemoteDigestDockerHub,
resolveRemoteDigestGeneric
};
+19 -10
View File
@@ -69,6 +69,21 @@ CREATE TABLE user_group_memberships (
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE
);
CREATE TABLE user_server_assignments (
user_id INTEGER NOT NULL,
server_id INTEGER NOT NULL,
group_id INTEGER,
use_global_group INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (user_id, server_id),
FOREIGN KEY (user_id) REFERENCES users(id) ON DELETE CASCADE,
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE,
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE SET NULL
);
CREATE INDEX idx_user_server_assignments_user ON user_server_assignments (user_id);
CREATE INDEX idx_user_server_assignments_server ON user_server_assignments (server_id);
CREATE TABLE permissions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
key TEXT NOT NULL UNIQUE,
@@ -100,13 +115,15 @@ CREATE TABLE permission_sections (
CREATE TABLE permission_groups (
id INTEGER PRIMARY KEY AUTOINCREMENT,
section_id INTEGER NOT NULL,
parent_group_id INTEGER,
key TEXT NOT NULL,
title TEXT NOT NULL,
sort_order INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
UNIQUE(section_id, key),
FOREIGN KEY (section_id) REFERENCES permission_sections(id) ON DELETE CASCADE
FOREIGN KEY (section_id) REFERENCES permission_sections(id) ON DELETE CASCADE,
FOREIGN KEY (parent_group_id) REFERENCES permission_groups(id) ON DELETE CASCADE
);
CREATE TABLE permission_items (
@@ -118,6 +135,7 @@ CREATE TABLE permission_items (
sort_order INTEGER NOT NULL DEFAULT 0,
default_level TEXT NOT NULL,
available_levels TEXT NOT NULL,
is_global_scope INTEGER NOT NULL DEFAULT 1,
is_required INTEGER NOT NULL DEFAULT 0,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
@@ -210,15 +228,6 @@ CREATE TABLE server_update_scripts (
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
);
CREATE TABLE server_agents (
id INTEGER PRIMARY KEY AUTOINCREMENT,
server_id INTEGER NOT NULL UNIQUE,
agent_url TEXT,
agent_token TEXT NOT NULL,
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
FOREIGN KEY (server_id) REFERENCES servers(id) ON DELETE CASCADE
);
CREATE INDEX idx_server_update_scripts_server ON server_update_scripts (server_id);
CREATE TABLE user_settings (
+220 -219
View File
@@ -2,7 +2,6 @@ import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';
import { db } from './index.js';
import crypto from 'crypto';
const BLUEPRINT_FILENAME = 'dbs';
const PERMISSION_BLUEPRINT = [
@@ -16,6 +15,7 @@ const PERMISSION_BLUEPRINT = [
key: 'stacks-redeploy-single',
label: 'Redeploy einzeln',
sortOrder: 0,
global: false,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: []
@@ -24,6 +24,7 @@ const PERMISSION_BLUEPRINT = [
key: 'stacks-redeploy-selection',
label: 'Redeploy Auswahl',
sortOrder: 1,
global: false,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: []
@@ -32,6 +33,7 @@ const PERMISSION_BLUEPRINT = [
key: 'stacks-redeploy-all',
label: 'Redeploy Alle',
sortOrder: 2,
global: false,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: []
@@ -48,6 +50,7 @@ const PERMISSION_BLUEPRINT = [
key: 'logs-access',
label: 'Bereich & Navigation',
sortOrder: 0,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
isRequired: 1,
@@ -57,6 +60,7 @@ const PERMISSION_BLUEPRINT = [
key: 'logs-export',
label: 'Logs Exportieren',
sortOrder: 1,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
@@ -67,6 +71,7 @@ const PERMISSION_BLUEPRINT = [
key: 'logs-delete',
label: 'Logs löschen',
sortOrder: 2,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
@@ -85,6 +90,7 @@ const PERMISSION_BLUEPRINT = [
key: 'users-access',
label: 'Bereich & Navigation',
sortOrder: 0,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
isRequired: 1,
@@ -94,6 +100,7 @@ const PERMISSION_BLUEPRINT = [
key: 'users-edit',
label: 'Benutzer bearbeiten',
sortOrder: 1,
global: true,
defaultLevel: 'read',
levels: ['full', 'read'],
dependencies: [
@@ -104,6 +111,7 @@ const PERMISSION_BLUEPRINT = [
key: 'users-delete',
label: 'Benutzer löschen',
sortOrder: 2,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
@@ -114,6 +122,7 @@ const PERMISSION_BLUEPRINT = [
key: 'users-security-phrase',
label: 'Sicherheitsschlüssel',
sortOrder: 3,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
@@ -132,6 +141,7 @@ const PERMISSION_BLUEPRINT = [
key: 'user-groups-access',
label: 'Bereich & Navigation',
sortOrder: 0,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
isRequired: 1,
@@ -141,6 +151,7 @@ const PERMISSION_BLUEPRINT = [
key: 'user-groups-edit',
label: 'Benutzergruppen bearbeiten',
sortOrder: 1,
global: true,
defaultLevel: 'read',
levels: ['full', 'read'],
dependencies: [
@@ -151,6 +162,7 @@ const PERMISSION_BLUEPRINT = [
key: 'user-groups-delete',
label: 'Benutzergruppen löschen',
sortOrder: 2,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
@@ -169,6 +181,7 @@ const PERMISSION_BLUEPRINT = [
key: 'maintenance-access',
label: 'Bereich & Navigation',
sortOrder: 0,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
isRequired: 1,
@@ -185,8 +198,9 @@ const PERMISSION_BLUEPRINT = [
key: 'maintenance-server-manage',
label: 'Server-Sektion',
sortOrder: 0,
global: true,
defaultLevel: 'none',
levels: ['full', 'read', 'none'],
levels: ['full', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
]
@@ -195,8 +209,9 @@ const PERMISSION_BLUEPRINT = [
key: 'maintenance-server-edit',
label: 'Server bearbeiten',
sortOrder: 1,
global: false,
defaultLevel: 'none',
levels: ['full', 'none'],
levels: ['full', 'read', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
{ dependsOnKey: 'maintenance-server-manage', requiredLevel: '!=none' }
@@ -206,6 +221,7 @@ const PERMISSION_BLUEPRINT = [
key: 'maintenance-server-delete',
label: 'Server löschen',
sortOrder: 2,
global: false,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
@@ -214,6 +230,58 @@ const PERMISSION_BLUEPRINT = [
{ dependsOnKey: 'maintenance-server-edit', requiredLevel: '=full' }
]
}
],
groups: [
{
key: 'maintenance-portainer-group',
title: 'Portainer',
sortOrder: 1,
items: [
{
key: 'maintenance-ssh-update',
label: 'SSH/Update-Skript',
sortOrder: 0,
global: false,
defaultLevel: 'none',
levels: ['full', 'read', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
{ dependsOnKey: 'maintenance-server-edit', requiredLevel: '!=none' }
]
},
{
key: 'maintenance-update',
label: 'Update durchführen',
sortOrder: 1,
global: false,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
{ dependsOnKey: 'maintenance-server-edit', requiredLevel: '!=none' }
]
}
]
},
{
key: 'maintenance-duplicates-group',
title: 'Doppelte Stacks',
sortOrder: 2,
items: [
{
key: 'maintenance-duplicates',
label: 'Doppelte Stacks',
sortOrder: 0,
global: false,
defaultLevel: 'none',
levels: ['full', 'read', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
{ dependsOnKey: 'maintenance-server-edit', requiredLevel: '!=none' }
]
}
]
}
]
},
{
@@ -225,6 +293,7 @@ const PERMISSION_BLUEPRINT = [
key: 'maintenance-superuser-delete',
label: 'Superuser löschen',
sortOrder: 0,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
@@ -234,58 +303,20 @@ const PERMISSION_BLUEPRINT = [
]
},
{
key: 'maintenance-portainer-group',
title: 'Portainer',
key: 'maintenance-mtls-group',
title: 'mTLS',
sortOrder: 2,
items: [
{
key: 'maintenance-portainer',
label: 'Portainer-Sektion',
key: 'maintenance-mtls',
label: 'mTLS Sektion',
sortOrder: 0,
global: true,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
]
},
{
key: 'maintenance-ssh-update',
label: 'SSH/Update-Skript',
sortOrder: 1,
defaultLevel: 'none',
levels: ['full', 'read', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
{ dependsOnKey: 'maintenance-portainer', requiredLevel: '!=none' }
]
},
{
key: 'maintenance-update',
label: 'Update durchführen',
sortOrder: 2,
defaultLevel: 'none',
levels: ['full', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
{ dependsOnKey: 'maintenance-portainer', requiredLevel: '!=none' }
]
}
]
},
{
key: 'maintenance-duplicates-group',
title: 'Doppelte Stacks',
sortOrder: 3,
items: [
{
key: 'maintenance-duplicates',
label: 'Doppelte Stacks',
sortOrder: 0,
defaultLevel: 'none',
levels: ['full', 'read', 'none'],
dependencies: [
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
]
}
]
}
@@ -381,7 +412,13 @@ const tableExists = (tableName) => {
};
const dropDeprecatedArtifacts = () => {
const deprecatedTables = ['user_endpoint_permission_overrides', 'endpoints'];
const deprecatedTables = [
'user_endpoint_permission_overrides',
'endpoints',
'server_agents',
'agent_tls_material',
'agent_bootstrap_tokens'
];
deprecatedTables.forEach((table) => {
if (tableExists(table)) {
db.exec(`DROP TABLE IF EXISTS ${table}`);
@@ -453,20 +490,24 @@ const ensurePermissionSeeds = () => {
`);
const selectGroup = db.prepare(`
SELECT id, title, sort_order
SELECT id, title, sort_order, parent_group_id
FROM permission_groups
WHERE section_id = ? AND key = ?
LIMIT 1
`);
const insertGroup = db.prepare(`
INSERT INTO permission_groups (section_id, key, title, sort_order, created_at, updated_at)
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
INSERT INTO permission_groups (section_id, parent_group_id, key, title, sort_order, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`);
const updateGroup = db.prepare(`
UPDATE permission_groups
SET title = ?, sort_order = ?, updated_at = CURRENT_TIMESTAMP
SET title = ?, sort_order = ?, parent_group_id = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
const deleteUnusedGroups = db.prepare(`
DELETE FROM permission_groups
WHERE key NOT IN (SELECT value FROM json_each(?))
`);
const selectItem = db.prepare(`
SELECT
@@ -477,6 +518,7 @@ const ensurePermissionSeeds = () => {
sort_order,
default_level,
available_levels,
is_global_scope,
is_required
FROM permission_items
WHERE key = ?
@@ -491,11 +533,12 @@ const ensurePermissionSeeds = () => {
sort_order,
default_level,
available_levels,
is_global_scope,
is_required,
created_at,
updated_at
) VALUES (
?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
)
`);
const updateItem = db.prepare(`
@@ -507,10 +550,24 @@ const ensurePermissionSeeds = () => {
sort_order = ?,
default_level = ?,
available_levels = ?,
is_global_scope = ?,
is_required = ?,
updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
const deleteUnusedItems = db.prepare(`
DELETE FROM permission_items
WHERE key NOT IN (SELECT value FROM json_each(?))
`);
const deleteOrphanDependencies = db.prepare(`
DELETE FROM permission_dependencies
WHERE permission_id NOT IN (SELECT id FROM permission_items)
OR depends_on_permission_id NOT IN (SELECT id FROM permission_items)
`);
const deleteOrphanPermissionValues = db.prepare(`
DELETE FROM group_permission_values
WHERE permission_id NOT IN (SELECT id FROM permission_items)
`);
const selectDependency = db.prepare(`
SELECT id, required_level
@@ -536,6 +593,100 @@ const ensurePermissionSeeds = () => {
`);
const itemKeyToId = new Map();
const dependencyQueue = [];
const itemKeys = new Set();
const groupKeys = new Set();
const upsertItem = ({ item, sectionId, groupId = null, sortOrder }) => {
const existingItem = selectItem.get(item.key);
const itemSortOrder = typeof sortOrder === 'number' ? sortOrder : 0;
const levelOptions = Array.isArray(item.levels) && item.levels.length
? item.levels
: ['full', 'read', 'none'];
const availableLevels = JSON.stringify(levelOptions);
const isRequired = item.isRequired ? 1 : 0;
const isGlobal = item.global === false ? 0 : 1;
const label = item.label || item.key || '';
if (!existingItem) {
const result = insertItem.run(
sectionId,
groupId,
item.key,
label,
itemSortOrder,
item.defaultLevel || 'none',
availableLevels,
isGlobal,
isRequired
);
itemKeyToId.set(item.key, Number(result.lastInsertRowid));
console.log(`ℹ️ Berechtigung ${item.key} ${groupId ? `in Gruppe ${groupId}` : ''} angelegt`);
} else {
const hasChanges =
existingItem.section_id !== sectionId ||
existingItem.group_id !== groupId ||
existingItem.label !== label ||
(existingItem.sort_order ?? itemSortOrder) !== itemSortOrder ||
existingItem.default_level !== (item.defaultLevel || 'none') ||
existingItem.available_levels !== availableLevels ||
Number(existingItem.is_global_scope ?? 1) !== isGlobal ||
Number(existingItem.is_required) !== isRequired;
updateItem.run(
sectionId,
groupId,
label,
itemSortOrder,
item.defaultLevel || 'none',
availableLevels,
isGlobal,
isRequired,
existingItem.id
);
itemKeyToId.set(item.key, existingItem.id);
if (hasChanges) {
console.log(`ℹ️ Berechtigung ${item.key} aktualisiert`);
}
}
itemKeys.add(item.key);
dependencyQueue.push({ itemKey: item.key, dependencies: item.dependencies || [] });
};
const processGroups = (groups, sectionId, parentGroupId = null) => {
const list = Array.isArray(groups) ? groups : [];
list.forEach((group, groupIdx) => {
const sortOrder = typeof group.sortOrder === 'number' ? group.sortOrder : groupIdx;
const existingGroup = selectGroup.get(sectionId, group.key);
let groupId;
if (!existingGroup) {
const result = insertGroup.run(sectionId, parentGroupId, group.key, group.title, sortOrder);
groupId = Number(result.lastInsertRowid);
console.log(`ℹ️ Berechtigungs-Gruppe ${group.key} in Sektion ${sectionId} angelegt`);
} else {
const hasGroupChanges =
existingGroup.title !== group.title ||
(existingGroup.sort_order ?? sortOrder) !== sortOrder ||
existingGroup.parent_group_id !== parentGroupId;
updateGroup.run(group.title, sortOrder, parentGroupId, existingGroup.id);
groupId = existingGroup.id;
if (hasGroupChanges) {
console.log(`ℹ️ Berechtigungs-Gruppe ${group.key} in Sektion ${sectionId} aktualisiert`);
}
}
groupKeys.add(group.key);
const groupItems = Array.isArray(group.items) ? group.items : [];
groupItems.forEach((item, itemIdx) => {
upsertItem({ item, sectionId, groupId, sortOrder: typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx });
});
if (group.groups) {
processGroups(group.groups, sectionId, groupId);
}
});
};
PERMISSION_BLUEPRINT.forEach((section, sectionIndex) => {
const existingSection = selectSection.get(section.key);
@@ -568,190 +719,41 @@ const ensurePermissionSeeds = () => {
const sectionItems = Array.isArray(section.items) ? section.items : [];
sectionItems.forEach((item, itemIdx) => {
const existingItem = selectItem.get(item.key);
const sortOrder = typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx;
const levelOptions = Array.isArray(item.levels) && item.levels.length
? item.levels
: ['full', 'read', 'none'];
const availableLevels = JSON.stringify(levelOptions);
const isRequired = item.isRequired ? 1 : 0;
if (!existingItem) {
const result = insertItem.run(
sectionId,
null,
item.key,
item.label,
sortOrder,
item.defaultLevel || 'none',
availableLevels,
isRequired
);
itemKeyToId.set(item.key, Number(result.lastInsertRowid));
console.log(`ℹ️ Berechtigung ${item.key} angelegt`);
} else {
const hasChanges =
existingItem.section_id !== sectionId ||
existingItem.group_id !== null ||
existingItem.label !== item.label ||
(existingItem.sort_order ?? sortOrder) !== sortOrder ||
existingItem.default_level !== (item.defaultLevel || 'none') ||
existingItem.available_levels !== availableLevels ||
Number(existingItem.is_required) !== isRequired;
updateItem.run(
sectionId,
null,
item.label,
sortOrder,
item.defaultLevel || 'none',
availableLevels,
isRequired,
existingItem.id
);
itemKeyToId.set(item.key, existingItem.id);
if (hasChanges) {
console.log(`ℹ️ Berechtigung ${item.key} aktualisiert`);
}
}
upsertItem({ item, sectionId, groupId: null, sortOrder: typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx });
});
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
sectionGroups.forEach((group, groupIdx) => {
const existingGroup = selectGroup.get(sectionId, group.key);
const sortOrder = typeof group.sortOrder === 'number' ? group.sortOrder : groupIdx;
let groupId;
if (!existingGroup) {
const result = insertGroup.run(sectionId, group.key, group.title, sortOrder);
groupId = Number(result.lastInsertRowid);
console.log(`ℹ️ Berechtigungs-Gruppe ${group.key} in Sektion ${section.key} angelegt`);
} else {
const hasGroupChanges =
existingGroup.title !== group.title ||
(existingGroup.sort_order ?? sortOrder) !== sortOrder;
updateGroup.run(group.title, sortOrder, existingGroup.id);
groupId = existingGroup.id;
if (hasGroupChanges) {
console.log(`ℹ️ Berechtigungs-Gruppe ${group.key} in Sektion ${section.key} aktualisiert`);
}
}
const groupItems = Array.isArray(group.items) ? group.items : [];
groupItems.forEach((item, itemIdx) => {
const existingItem = selectItem.get(item.key);
const itemSortOrder = typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx;
const levelOptions = Array.isArray(item.levels) && item.levels.length
? item.levels
: ['full', 'read', 'none'];
const availableLevels = JSON.stringify(levelOptions);
const isRequired = item.isRequired ? 1 : 0;
if (!existingItem) {
const result = insertItem.run(
sectionId,
groupId,
item.key,
item.label,
itemSortOrder,
item.defaultLevel || 'none',
availableLevels,
isRequired
);
itemKeyToId.set(item.key, Number(result.lastInsertRowid));
console.log(`ℹ️ Berechtigung ${item.key} in Gruppe ${group.key} angelegt`);
} else {
const hasChanges =
existingItem.section_id !== sectionId ||
existingItem.group_id !== groupId ||
existingItem.label !== item.label ||
(existingItem.sort_order ?? itemSortOrder) !== itemSortOrder ||
existingItem.default_level !== (item.defaultLevel || 'none') ||
existingItem.available_levels !== availableLevels ||
Number(existingItem.is_required) !== isRequired;
updateItem.run(
sectionId,
groupId,
item.label,
itemSortOrder,
item.defaultLevel || 'none',
availableLevels,
isRequired,
existingItem.id
);
itemKeyToId.set(item.key, existingItem.id);
if (hasChanges) {
console.log(`ℹ️ Berechtigung ${item.key} in Gruppe ${group.key} aktualisiert`);
}
}
});
});
processGroups(section.groups, sectionId, null);
});
PERMISSION_BLUEPRINT.forEach((section) => {
const sectionItems = Array.isArray(section.items) ? section.items : [];
sectionItems.forEach((item) => {
const permissionId = itemKeyToId.get(item.key);
dependencyQueue.forEach(({ itemKey, dependencies }) => {
const permissionId = itemKeyToId.get(itemKey);
if (!permissionId) return;
(item.dependencies || []).forEach((dependency) => {
dependencies.forEach((dependency) => {
const dependsId = itemKeyToId.get(dependency.dependsOnKey);
if (!dependsId) return;
const existing = selectDependency.get(permissionId, dependsId);
const requiredLevel = dependency.requiredLevel ?? null;
if (!existing) {
insertDependency.run(permissionId, dependsId, requiredLevel);
console.log(
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} angelegt`
);
console.log(`ℹ️ Abhängigkeit ${itemKey} -> ${dependency.dependsOnKey} angelegt`);
} else if (existing.required_level !== requiredLevel) {
updateDependency.run(requiredLevel, existing.id);
console.log(
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} aktualisiert`
);
console.log(`ℹ️ Abhängigkeit ${itemKey} -> ${dependency.dependsOnKey} aktualisiert`);
}
});
});
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
sectionGroups.forEach((group) => {
const groupItems = Array.isArray(group.items) ? group.items : [];
groupItems.forEach((item) => {
const permissionId = itemKeyToId.get(item.key);
if (!permissionId) return;
(item.dependencies || []).forEach((dependency) => {
const dependsId = itemKeyToId.get(dependency.dependsOnKey);
if (!dependsId) return;
const requiredLevel = dependency.requiredLevel ?? null;
const existing = selectDependency.get(permissionId, dependsId);
if (!existing) {
insertDependency.run(permissionId, dependsId, requiredLevel);
console.log(
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} angelegt`
);
} else if (existing.required_level !== requiredLevel) {
updateDependency.run(requiredLevel, existing.id);
console.log(
`ℹ️ Abhängigkeit ${item.key} -> ${dependency.dependsOnKey} aktualisiert`
);
// Cleanup obsolete entries
if (itemKeys.size > 0) {
const itemKeyJson = JSON.stringify(Array.from(itemKeys));
deleteOrphanDependencies.run();
deleteOrphanPermissionValues.run();
deleteUnusedItems.run(itemKeyJson);
}
if (groupKeys.size > 0) {
const groupKeyJson = JSON.stringify(Array.from(groupKeys));
deleteUnusedGroups.run(groupKeyJson);
}
});
});
});
});
};
const ensureServerAgentEntries = () => {
if (!tableExists('server_agents')) return;
const servers = db.prepare('SELECT id FROM servers').all();
const insertAgent = db.prepare(`
INSERT OR IGNORE INTO server_agents (server_id, agent_url, agent_token, created_at, updated_at)
VALUES (?, NULL, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
`);
const updateMissingToken = db.prepare(`
UPDATE server_agents
SET agent_token = ?
WHERE server_id = ? AND (agent_token IS NULL OR agent_token = '')
`);
servers.forEach((server) => {
const token = `sp_${crypto.randomBytes(16).toString('hex')}`;
insertAgent.run(server.id, token);
updateMissingToken.run(token, server.id);
});
};
export const ensureDatabaseSchema = () => {
@@ -762,7 +764,6 @@ export const ensureDatabaseSchema = () => {
dropDeprecatedArtifacts();
ensureTablesAndColumns(tableDefinitions);
ensureServerAgentEntries();
ensureIndexes(indexDefinitions);
ensurePermissionSeeds();
} catch (error) {
+532 -169
View File
File diff suppressed because it is too large. Load diff
+95 -16
View File
@@ -13,7 +13,7 @@ const selectSections = db.prepare(`
`);
const selectGroups = db.prepare(`
SELECT id, section_id, key, title, sort_order
SELECT id, section_id, parent_group_id, key, title, sort_order
FROM permission_groups
ORDER BY section_id ASC, sort_order ASC, id ASC
`);
@@ -28,6 +28,7 @@ const selectItems = db.prepare(`
sort_order,
default_level,
available_levels,
is_global_scope,
is_required
FROM permission_items
ORDER BY section_id ASC, COALESCE(group_id, 0) ASC, sort_order ASC, id ASC
@@ -39,7 +40,7 @@ const selectDependencies = db.prepare(`
`);
const selectPermissionItemsForMap = db.prepare(`
SELECT id, key, available_levels, default_level
SELECT id, key, available_levels, default_level, is_global_scope
FROM permission_items
`);
@@ -81,6 +82,8 @@ const getLevelPriority = (level) => {
};
let cachedPermissionItems = null;
let cachedServerScopedKeys = null;
let cachedScopeByKey = null;
const getPermissionItems = () => {
if (!cachedPermissionItems) {
@@ -91,6 +94,24 @@ const getPermissionItems = () => {
const resetPermissionItemsCache = () => {
cachedPermissionItems = null;
cachedServerScopedKeys = null;
cachedScopeByKey = null;
};
const getScopeCache = () => {
if (!cachedScopeByKey || !cachedServerScopedKeys) {
const items = getPermissionItems();
cachedScopeByKey = new Map();
cachedServerScopedKeys = new Set();
items.forEach((item) => {
const isGlobal = item.is_global_scope !== 0;
cachedScopeByKey.set(item.key, { isGlobal });
if (!isGlobal) {
cachedServerScopedKeys.add(item.key);
}
});
}
return { scopeByKey: cachedScopeByKey, serverScopedKeys: cachedServerScopedKeys };
};
export function getPermissionStructure() {
@@ -109,10 +130,23 @@ export function getPermissionStructure() {
});
const groupsBySectionId = new Map();
groups.forEach((group) => {
const groupsByParentId = new Map();
const sortedGroups = [...groups].sort((a, b) => {
const sortDiff = (a.sort_order ?? 0) - (b.sort_order ?? 0);
if (sortDiff !== 0) return sortDiff;
return a.id - b.id;
});
sortedGroups.forEach((group) => {
if (group.parent_group_id) {
const list = groupsByParentId.get(group.parent_group_id) || [];
list.push(group);
groupsByParentId.set(group.parent_group_id, list);
} else {
const list = groupsBySectionId.get(group.section_id) || [];
list.push(group);
groupsBySectionId.set(group.section_id, list);
}
});
const itemsBySectionId = new Map();
@@ -154,18 +188,26 @@ export function getPermissionStructure() {
defaultLevel: item.default_level || 'none',
availableLevels,
isRequired: Boolean(item.is_required),
isGlobal: item.is_global_scope !== 0,
dependencies: formattedDependencies
};
};
const buildGroupTree = (group) => {
const children = groupsByParentId.get(group.id) || [];
return {
key: group.key,
title: group.title,
sortOrder: group.sort_order ?? 0,
items: (itemsByGroupId.get(group.id) || []).map(formatItem),
groups: children.map(buildGroupTree)
};
};
const formattedSections = sections.map((section) => {
const sectionItems = (itemsBySectionId.get(section.id) || []).map(formatItem);
const sectionGroups = (groupsBySectionId.get(section.id) || []).map((group) => ({
key: group.key,
title: group.title,
sortOrder: group.sort_order ?? 0,
items: (itemsByGroupId.get(group.id) || []).map(formatItem)
}));
const rootGroups = groupsBySectionId.get(section.id) || [];
const sectionGroups = rootGroups.map(buildGroupTree);
return {
key: section.key,
@@ -270,13 +312,24 @@ export function saveGroupPermissionValues(groupId, values = {}) {
const serverEditLevel = getSubmittedLevel('maintenance-server-edit');
const serverDeleteLevel = getSubmittedLevel('maintenance-server-delete');
if (getLevelPriority(serverDeleteLevel) >= getLevelPriority('full') &&
(getLevelPriority(serverManageLevel) < getLevelPriority('full') ||
getLevelPriority(serverEditLevel) < getLevelPriority('full'))) {
const error = new Error('PERMISSION_DEPENDENCY_LEVEL');
error.code = 'PERMISSION_DEPENDENCY_LEVEL';
error.permissionKey = 'maintenance-server-delete';
throw error;
// Enforce dependency: "server löschen" darf maximal so hoch sein wie manage/edit.
const deletePriority = getLevelPriority(serverDeleteLevel);
const allowedDeletePriority = Math.min(
getLevelPriority(serverManageLevel),
getLevelPriority(serverEditLevel)
);
if (deletePriority > allowedDeletePriority) {
const allowedLevelEntry = Object.entries(LEVEL_PRIORITY).find(([, priority]) => priority === allowedDeletePriority);
const allowedLevel = allowedLevelEntry ? allowedLevelEntry[0] : 'none';
const deleteEntry = normalizedEntries.find((entry) => entry.item.key === 'maintenance-server-delete');
if (deleteEntry) {
deleteEntry.level = allowedLevel;
} else {
normalizedEntries.push({
item: itemMap.get('maintenance-server-delete'),
level: allowedLevel
});
}
}
const runSave = db.transaction(() => {
@@ -324,6 +377,32 @@ export function getSuperuserPermissionMap() {
return result;
}
export function getServerScopedPermissionKeys() {
const { serverScopedKeys } = getScopeCache();
return Array.from(serverScopedKeys);
}
export function isServerScopedPermission(permissionKey) {
if (!permissionKey) return false;
const { serverScopedKeys, scopeByKey } = getScopeCache();
if (serverScopedKeys.has(permissionKey)) {
return true;
}
const scope = scopeByKey.get(permissionKey);
return scope ? scope.isGlobal === false : false;
}
export function applyServerScopedOverride(basePermissions = {}, overridePermissions = {}) {
const merged = { ...basePermissions };
const { serverScopedKeys } = getScopeCache();
serverScopedKeys.forEach((key) => {
if (Object.prototype.hasOwnProperty.call(overridePermissions, key)) {
merged[key] = overridePermissions[key];
}
});
return merged;
}
export function getEffectivePermissionsForGroup(groupId) {
const defaults = getDefaultPermissionMap();
const numericId = Number(groupId);
-94
View File
@@ -1,94 +0,0 @@
function $j(e,t){for(var r=0;r<t.length;r++){const n=t[r];if(typeof n!="string"&&!Array.isArray(n)){for(const o in n)if(o!=="default"&&!(o in e)){const a=Object.getOwnPropertyDescriptor(n,o);a&&Object.defineProperty(e,o,a.get?a:{enumerable:!0,get:()=>n[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const a of o)if(a.type==="childList")for(const i of a.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&n(i)}).observe(document,{childList:!0,subtree:!0});function r(o){const a={};return o.integrity&&(a.integrity=o.integrity),o.referrerPolicy&&(a.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?a.credentials="include":o.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function n(o){if(o.ep)return;o.ep=!0;const a=r(o);fetch(o.href,a)}})();function r_(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function cu(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var r=function n(){return this instanceof n?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};r.prototype=t.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(e).forEach(function(n){var o=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(r,n,o.get?o:{enumerable:!0,get:function(){return e[n]}})}),r}var n_={exports:{}},Zd={},o_={exports:{}},kt={};/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var du=Symbol.for("react.element"),Hj=Symbol.for("react.portal"),qj=Symbol.for("react.fragment"),Gj=Symbol.for("react.strict_mode"),Kj=Symbol.for("react.profiler"),Yj=Symbol.for("react.provider"),Qj=Symbol.for("react.context"),Xj=Symbol.for("react.forward_ref"),Zj=Symbol.for("react.suspense"),Jj=Symbol.for("react.memo"),e2=Symbol.for("react.lazy"),fy=Symbol.iterator;function t2(e){return e===null||typeof e!="object"?null:(e=fy&&e[fy]||e["@@iterator"],typeof e=="function"?e:null)}var a_={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},i_=Object.assign,s_={};function Ss(e,t,r){this.props=e,this.context=t,this.refs=s_,this.updater=r||a_}Ss.prototype.isReactComponent={};Ss.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};Ss.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function l_(){}l_.prototype=Ss.prototype;function y0(e,t,r){this.props=e,this.context=t,this.refs=s_,this.updater=r||a_}var x0=y0.prototype=new l_;x0.constructor=y0;i_(x0,Ss.prototype);x0.isPureReactComponent=!0;var py=Array.isArray,u_=Object.prototype.hasOwnProperty,w0={current:null},c_={key:!0,ref:!0,__self:!0,__source:!0};function d_(e,t,r){var n,o={},a=null,i=null;if(t!=null)for(n in t.ref!==void 0&&(i=t.ref),t.key!==void 0&&(a=""+t.key),t)u_.call(t,n)&&!c_.hasOwnProperty(n)&&(o[n]=t[n]);var u=arguments.length-2;if(u===1)o.children=r;else if(1<u){for(var f=Array(u),g=0;g<u;g++)f[g]=arguments[g+2];o.children=f}if(e&&e.defaultProps)for(n in u=e.defaultProps,u)o[n]===void 0&&(o[n]=u[n]);return{$$typeof:du,type:e,key:a,ref:i,props:o,_owner:w0.current}}function r2(e,t){return{$$typeof:du,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}function _0(e){return typeof e=="object"&&e!==null&&e.$$typeof===du}function n2(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(r){return t[r]})}var hy=/\/+/g;function Dp(e,t){return typeof e=="object"&&e!==null&&e.key!=null?n2(""+e.key):t.toString(36)}function Tc(e,t,r,n,o){var a=typeof e;(a==="undefined"||a==="boolean")&&(e=null);var i=!1;if(e===null)i=!0;else switch(a){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case du:case Hj:i=!0}}if(i)return i=e,o=o(i),e=n===""?"."+Dp(i,0):n,py(o)?(r="",e!=null&&(r=e.replace(hy,"$&/")+"/"),Tc(o,t,r,"",function(g){return g})):o!=null&&(_0(o)&&(o=r2(o,r+(!o.key||i&&i.key===o.key?"":(""+o.key).replace(hy,"$&/")+"/")+e)),t.push(o)),1;if(i=0,n=n===""?".":n+":",py(e))for(var u=0;u<e.length;u++){a=e[u];var f=n+Dp(a,u);i+=Tc(a,t,r,f,o)}else if(f=t2(e),typeof f=="function")for(e=f.call(e),u=0;!(a=e.next()).done;)a=a.value,f=n+Dp(a,u++),i+=Tc(a,t,r,f,o);else if(a==="object")throw t=String(e),Error("Objects are not valid as a React child (found: "+(t==="[object Object]"?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return i}function Ku(e,t,r){if(e==null)return e;var n=[],o=0;return Tc(e,n,"","",function(a){return t.call(r,a,o++)}),n}function o2(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(r){(e._status===0||e._status===-1)&&(e._status=1,e._result=r)},function(r){(e._status===0||e._status===-1)&&(e._status=2,e._result=r)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var Yr={current:null},Pc={transition:null},a2={ReactCurrentDispatcher:Yr,ReactCurrentBatchConfig:Pc,ReactCurrentOwner:w0};kt.Children={map:Ku,forEach:function(e,t,r){Ku(e,function(){t.apply(this,arguments)},r)},count:function(e){var t=0;return Ku(e,function(){t++}),t},toArray:function(e){return Ku(e,function(t){return t})||[]},only:function(e){if(!_0(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};kt.Component=Ss;kt.Fragment=qj;kt.Profiler=Kj;kt.PureComponent=y0;kt.StrictMode=Gj;kt.Suspense=Zj;kt.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=a2;kt.cloneElement=function(e,t,r){if(e==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var n=i_({},e.props),o=e.key,a=e.ref,i=e._owner;if(t!=null){if(t.ref!==void 0&&(a=t.ref,i=w0.current),t.key!==void 0&&(o=""+t.key),e.type&&e.type.defaultProps)var u=e.type.defaultProps;for(f in t)u_.call(t,f)&&!c_.hasOwnProperty(f)&&(n[f]=t[f]===void 0&&u!==void 0?u[f]:t[f])}var f=arguments.length-2;if(f===1)n.children=r;else if(1<f){u=Array(f);for(var g=0;g<f;g++)u[g]=arguments[g+2];n.children=u}return{$$typeof:du,type:e.type,key:o,ref:a,props:n,_owner:i}};kt.createContext=function(e){return e={$$typeof:Qj,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null},e.Provider={$$typeof:Yj,_conLine truncated
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var i2=P,s2=Symbol.for("react.element"),l2=Symbol.for("react.fragment"),u2=Object.prototype.hasOwnProperty,c2=i2.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,d2={key:!0,ref:!0,__self:!0,__source:!0};function f_(e,t,r){var n,o={},a=null,i=null;r!==void 0&&(a=""+r),t.key!==void 0&&(a=""+t.key),t.ref!==void 0&&(i=t.ref);for(n in t)u2.call(t,n)&&!d2.hasOwnProperty(n)&&(o[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps,t)o[n]===void 0&&(o[n]=t[n]);return{$$typeof:s2,type:e,key:a,ref:i,props:o,_owner:c2.current}}Zd.Fragment=l2;Zd.jsx=f_;Zd.jsxs=f_;n_.exports=Zd;var v=n_.exports;function p_(e,t){return function(){return e.apply(t,arguments)}}const{toString:f2}=Object.prototype,{getPrototypeOf:C0}=Object,Jd=(e=>t=>{const r=f2.call(t);return e[r]||(e[r]=r.slice(8,-1).toLowerCase())})(Object.create(null)),mo=e=>(e=e.toLowerCase(),t=>Jd(t)===e),ef=e=>t=>typeof t===e,{isArray:Cs}=Array,Dl=ef("undefined");function p2(e){return e!==null&&!Dl(e)&&e.constructor!==null&&!Dl(e.constructor)&&yn(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const h_=mo("ArrayBuffer");function h2(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&h_(e.buffer),t}const g2=ef("string"),yn=ef("function"),g_=ef("number"),tf=e=>e!==null&&typeof e=="object",m2=e=>e===!0||e===!1,Oc=e=>{if(Jd(e)!=="object")return!1;const t=C0(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)},b2=mo("Date"),v2=mo("File"),y2=mo("Blob"),x2=mo("FileList"),w2=e=>tf(e)&&yn(e.pipe),_2=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||yn(e.append)&&((t=Jd(e))==="formdata"||t==="object"&&yn(e.toString)&&e.toString()==="[object FormData]"))},S2=mo("URLSearchParams"),[C2,T2,P2,O2]=["ReadableStream","Request","Response","Headers"].map(mo),k2=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function fu(e,t,{allOwnKeys:r=!1}={}){if(e===null||typeof e>"u")return;let n,o;if(typeof e!="object"&&(e=[e]),Cs(e))for(n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else{const a=r?Object.getOwnPropertyNames(e):Object.keys(e),i=a.length;let u;for(n=0;n<i;n++)u=a[n],t.call(null,e[u],u,e)}}function m_(e,t){t=t.toLowerCase();const r=Object.keys(e);let n=r.length,o;for(;n-- >0;)if(o=r[n],t===o.toLowerCase())return o;return null}const Xa=(()=>typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global)(),b_=e=>!Dl(e)&&e!==Xa;function zh(){const{caseless:e}=b_(this)&&this||{},t={},r=(n,o)=>{const a=e&&m_(t,o)||o;Oc(t[a])&&Oc(n)?t[a]=zh(t[a],n):Oc(n)?t[a]=zh({},n):Cs(n)?t[a]=n.slice():t[a]=n};for(let n=0,o=arguments.length;n<o;n++)arguments[n]&&fu(arguments[n],r);return t}const N2=(e,t,r,{allOwnKeys:n}={})=>(fu(t,(o,a)=>{r&&yn(o)?e[a]=p_(o,r):e[a]=o},{allOwnKeys:n}),e),E2=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),j2=(e,t,r,n)=>{e.prototype=Object.create(t.prototype,n),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),r&&Object.assign(e.prototype,r)},A2=(e,t,r,n)=>{let o,a,i;const u={};if(t=t||{},e==null)return t;do{for(o=Object.getOwnPropertyNames(e),a=o.length;a-- >0;)i=o[a],(!n||n(i,e,t))&&!u[i]&&(t[i]=e[i],u[i]=!0);e=r!==!1&&C0(e)}while(e&&(!r||r(e,t))&&e!==Object.prototype);return t},R2=(e,t,r)=>{e=String(e),(r===void 0||r>e.length)&&(r=e.length),r-=t.length;const n=e.indexOf(t,r);return n!==-1&&n===r},M2=e=>{if(!e)return null;if(Cs(e))return e;let t=e.length;if(!g_(t))return null;const r=new Array(t);for(;t-- >0;)r[t]=e[t];return r},I2=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&C0(Uint8Array)),D2=(e,t)=>{const n=(e&&e[Symbol.iterator]).call(e);let o;for(;(o=n.next())&&!o.done;){const a=o.value;t.call(e,a[0],a[1])}},L2=(e,t)=>{let r;const n=[];for(;(r=e.exec(t))!==null;)n.push(r);return n},F2=mo("HTMLFormElement"),z2=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(r,n,o){return n.toUpperCase()+o}),gy=(({hasOwnProperty:e})=>(t,r)=>e.call(t,r))(Object.prototype),B2=mo("RegExp"),v_=(e,t)=>{const r=Object.getOwnPropertyDescriptors(e),n={};fu(r,(o,a)=>{let i;(i=t(o,a,e))!==!1&&(n[a]=i||o)}),Object.defineProperties(e,n)},V2=e=>{v_(e,(t,r)=>{if(yn(e)&&["arguments","caller","callee"].indexOf(r)!==-1)return!1;const n=e[r];if(yn(n)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+r+"'")})}})},U2=(e,t)=>{const r={},n=o=>{o.forEach(a=>{r[a]=!0})};return Cs(e)?n(e):n(String(e).split(t)),r},W2=()=>{},$2=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t,Lp="abcdefghijklmnopqrstuvwxyz",my="0123456789",y_={DIGIT:my,ALPHA:Lp,ALPHA_DIGIT:Lp+Lp.toUpperCase()+my},H2=(e=16,t=y_.ALPHA_DIGIT)=>{let r="";const{length:n}=t;for(;e--;)r+=t[Math.random()*n|0];return r};function q2(e){return!!(e&&yn(e.append)&&e[Symbol.toStringTag]==="FormData"&&e[Symbol.iterator])}const G2=e=>{const t=new Array(10),r=(n,o)=>{if(tf(n)){if(t.indexOf(n)>=0)return;if(!("toJSON"in n)){t[o]=n;const a=CLine truncated
`).forEach(function(i){o=i.indexOf(":"),r=i.substring(0,o).trim().toLowerCase(),n=i.substring(o+1).trim(),!(!r||t[r]&&hA[r])&&(r==="set-cookie"?t[r]?t[r].push(n):t[r]=[n]:t[r]=t[r]?t[r]+", "+n:n)}),t},xy=Symbol("internals");function rl(e){return e&&String(e).trim().toLowerCase()}function kc(e){return e===!1||e==null?e:Se.isArray(e)?e.map(kc):String(e)}function mA(e){const t=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let n;for(;n=r.exec(e);)t[n[1]]=n[2];return t}const bA=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function Fp(e,t,r,n,o){if(Se.isFunction(n))return n.call(this,t,r);if(o&&(t=r),!!Se.isString(t)){if(Se.isString(n))return t.indexOf(n)!==-1;if(Se.isRegExp(n))return n.test(t)}}function vA(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,r,n)=>r.toUpperCase()+n)}function yA(e,t){const r=Se.toCamelCase(" "+t);["get","set","has"].forEach(n=>{Object.defineProperty(e,n+r,{value:function(o,a,i){return this[n].call(this,t,o,a,i)},configurable:!0})})}class nf{constructor(t){t&&this.set(t)}set(t,r,n){const o=this;function a(u,f,g){const m=rl(f);if(!m)throw new Error("header name must be a non-empty string");const S=Se.findKey(o,m);(!S||o[S]===void 0||g===!0||g===void 0&&o[S]!==!1)&&(o[S||f]=kc(u))}const i=(u,f)=>Se.forEach(u,(g,m)=>a(g,m,f));if(Se.isPlainObject(t)||t instanceof this.constructor)i(t,r);else if(Se.isString(t)&&(t=t.trim())&&!bA(t))i(gA(t),r);else if(Se.isHeaders(t))for(const[u,f]of t.entries())a(f,u,n);else t!=null&&a(r,t,n);return this}get(t,r){if(t=rl(t),t){const n=Se.findKey(this,t);if(n){const o=this[n];if(!r)return o;if(r===!0)return mA(o);if(Se.isFunction(r))return r.call(this,o,n);if(Se.isRegExp(r))return r.exec(o);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,r){if(t=rl(t),t){const n=Se.findKey(this,t);return!!(n&&this[n]!==void 0&&(!r||Fp(this,this[n],n,r)))}return!1}delete(t,r){const n=this;let o=!1;function a(i){if(i=rl(i),i){const u=Se.findKey(n,i);u&&(!r||Fp(n,n[u],u,r))&&(delete n[u],o=!0)}}return Se.isArray(t)?t.forEach(a):a(t),o}clear(t){const r=Object.keys(this);let n=r.length,o=!1;for(;n--;){const a=r[n];(!t||Fp(this,this[a],a,t,!0))&&(delete this[a],o=!0)}return o}normalize(t){const r=this,n={};return Se.forEach(this,(o,a)=>{const i=Se.findKey(n,a);if(i){r[i]=kc(o),delete r[a];return}const u=t?vA(a):String(a).trim();u!==a&&delete r[a],r[u]=kc(o),n[u]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const r=Object.create(null);return Se.forEach(this,(n,o)=>{n!=null&&n!==!1&&(r[o]=t&&Se.isArray(n)?n.join(", "):n)}),r}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,r])=>t+": "+r).join(`
`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...r){const n=new this(t);return r.forEach(o=>n.set(o)),n}static accessor(t){const n=(this[xy]=this[xy]={accessors:{}}).accessors,o=this.prototype;function a(i){const u=rl(i);n[u]||(yA(o,i),n[u]=!0)}return Se.isArray(t)?t.forEach(a):a(t),this}}nf.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);Se.reduceDescriptors(nf.prototype,({value:e},t)=>{let r=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(n){this[r]=n}}});Se.freezeMethods(nf);const io=nf;function zp(e,t){const r=this||k0,n=t||r,o=io.from(n.headers);let a=n.data;return Se.forEach(e,function(u){a=u.call(r,a,o.normalize(),t?t.status:void 0)}),o.normalize(),a}function k_(e){return!!(e&&e.__CANCEL__)}function Ts(e,t,r){St.call(this,e??"canceled",St.ERR_CANCELED,t,r),this.name="CanceledError"}Se.inherits(Ts,St,{__CANCEL__:!0});function N_(e,t,r){const n=r.config.validateStatus;!r.status||!n||n(r.status)?e(r):t(new St("Request failed with status code "+r.status,[St.ERR_BAD_REQUEST,St.ERR_BAD_RESPONSE][Math.floor(r.status/100)-4],r.config,r.request,r))}function xA(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function wA(e,t){e=e||10;const r=new Array(e),n=new Array(e);let o=0,a=0,i;return t=t!==void 0?t:1e3,function(f){const g=Date.now(),m=n[a];i||(i=g),r[o]=f,n[o]=g;let S=a,O=0;for(;S!==o;)O+=r[S++],S=S%e;if(o=(o+1)%e,o===a&&(a=(a+1)%e),g-i<t)return;const T=m&&g-m;return T?Math.round(O*1e3/T):void 0}}function _A(e,t){let r=0,n=1e3/t,o,a;const i=(g,m=Date.now())=>{r=m,o=null,a&&(clearTimeout(a),a=null),e.apply(null,g)};return[(...g)=>{const m=Date.now(),S=m-r;S>=n?i(g,m):(o=g,a||(a=setTimeout(()=>{a=null,i(o)},n-S)))},()=>o&&i(o)]}const rd=(e,t,r=3)=>{let n=0;const o=wA(50,250);return _A(a=>{const i=a.loaded,u=a.lengthComputable?a.total:void 0,f=i-n,g=o(f),m=i<=u;n=i;const S={loaded:i,total:u,progress:u?i/u:void 0,bytes:f,rate:g||void 0,estimated:g&&u&&m?(u-i)/g:void 0,event:a,lengthComputable:u!=null,[t?"download":"upload"]:!0};e(S)},r)},wy=(e,t)=>{const r=e!=null;return[n=>t[0]({lengthComputable:r,total:e,loaded:n}),t[1]]},_y=e=>(...t)=>Se.asap(()=>e(...t)),SA=xn.hasStandardBrowserEnv?function(){const t=xn.navigator&&/(msie|trident)/i.test(xn.navigator.userAgent),r=document.createElement("a");let n;function o(a){let i=a;return t&&(r.setAttribute("href",i),i=r.href),r.setAttribute("href",i),{href:r.href,protocol:r.protocol?r.protocol.replace(/:$/,""):"",host:r.host,search:r.search?r.search.replace(/^\?/,""):"",hash:r.hash?r.hash.replace(/^#/,""):"",hostname:r.hostname,port:r.port,pathname:r.pathname.charAt(0)==="/"?r.pathname:"/"+r.pathname}}return n=o(window.location.href),function(i){const u=Se.isString(i)?o(i):i;return u.protocol===n.protocol&&u.host===n.host}}():function(){return function(){return!0}}(),CA=xn.hasStandardBrowserEnv?{write(e,t,r,n,o,a){const i=[e+"="+encodeURIComponent(t)];Se.isNumber(r)&&i.push("expires="+new Date(r).toGMTString()),Se.isString(n)&&i.push("path="+n),Se.isString(o)&&i.push("domain="+o),a===!0&&i.push("secure"),document.cookie=i.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function TA(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function PA(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function E_(e,t){return e&&!TA(t)?PA(e,t):t}const Sy=e=>e instanceof io?{...e}:e;function ai(e,t){t=t||{};const r={};function n(g,m,S){return Se.isPlainObject(g)&&Se.isPlainObject(m)?Se.merge.call({caseless:S},g,m):Se.isPlainObject(m)?Se.merge({},m):Se.isArray(m)?m.slice():m}function o(g,m,S){if(Se.isUndefined(m)){if(!Se.isUndefined(g))return n(void 0,g,S)}else return n(g,m,S)}function a(g,m){if(!Se.isUndefined(m))return n(void 0,m)}function i(g,m){if(Se.isUndefined(m)){if(!Se.isUndefined(g))return n(void 0,g)}else return n(void 0,m)}function u(g,m,S){if(S in t)return n(g,m);if(S in e)return n(void 0,g)}const f={url:a,method:a,data:a,baseURL:i,transformRequest:i,transformResponse:i,paramsSerializer:i,timeout:i,timeoutMessage:i,withCredentials:i,withXSRFToken:i,adapter:i,responseType:i,xsrfCookieName:i,xsrfHeaderName:i,onUploadProgress:i,onDownloadProgress:i,decompress:i,maxContentLength:i,maxBodyLength:i,beforeRedirect:i,transport:i,httpAgent:i,httpsAgent:i,cancelToken:i,socketPath:i,responseEncoding:i,validateStatus:u,headers:(g,m)=>o(Sy(g),Sy(m),!0)};return Se.forEach(Object.keys(Object.assign({},e,t)),function(m){const S=f[m]||o,O=S(e[m],t[m],m);Se.isUndefined(O)&&S!==u||(r[m]=O)}),r}const j_=e=>{const t=ai({},e);let{data:r,withXSRFToken:n,xsrfHeaderName:o,xsrfCookieName:a,headers:i,auth:u}=t;t.headers=i=io.from(i),t.url=T_(E_(t.baseURL,t.url),e.params,e.paramsSerializer),u&&i.set("Authorization","Basic "+btoa((u.username||"")+":"+(u.password?unescape(enco Line truncated
`+a.map(Py).join(`
`):" "+Py(a[0]):"as no adapter specified";throw new St("There is no suitable adapter to dispatch the request "+i,"ERR_NOT_SUPPORT")}return n},adapters:$h};function Bp(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new Ts(null,e)}function Oy(e){return Bp(e),e.headers=io.from(e.headers),e.data=zp.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),M_.getAdapter(e.adapter||k0.adapter)(e).then(function(n){return Bp(e),n.data=zp.call(e,e.transformResponse,n),n.headers=io.from(n.headers),n},function(n){return k_(n)||(Bp(e),n&&n.response&&(n.response.data=zp.call(e,e.transformResponse,n.response),n.response.headers=io.from(n.response.headers))),Promise.reject(n)})}const I_="1.7.5",N0={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{N0[e]=function(n){return typeof n===e||"a"+(t<1?"n ":" ")+e}});const ky={};N0.transitional=function(t,r,n){function o(a,i){return"[Axios v"+I_+"] Transitional option '"+a+"'"+i+(n?". "+n:"")}return(a,i,u)=>{if(t===!1)throw new St(o(i," has been removed"+(r?" in "+r:"")),St.ERR_DEPRECATED);return r&&!ky[i]&&(ky[i]=!0,console.warn(o(i," has been deprecated since v"+r+" and will be removed in the near future"))),t?t(a,i,u):!0}};function FA(e,t,r){if(typeof e!="object")throw new St("options must be an object",St.ERR_BAD_OPTION_VALUE);const n=Object.keys(e);let o=n.length;for(;o-- >0;){const a=n[o],i=t[a];if(i){const u=e[a],f=u===void 0||i(u,a,e);if(f!==!0)throw new St("option "+a+" must be "+f,St.ERR_BAD_OPTION_VALUE);continue}if(r!==!0)throw new St("Unknown option "+a,St.ERR_BAD_OPTION)}}const Hh={assertOptions:FA,validators:N0},ua=Hh.validators;class od{constructor(t){this.defaults=t,this.interceptors={request:new yy,response:new yy}}async request(t,r){try{return await this._request(t,r)}catch(n){if(n instanceof Error){let o;Error.captureStackTrace?Error.captureStackTrace(o={}):o=new Error;const a=o.stack?o.stack.replace(/^.+\n/,""):"";try{n.stack?a&&!String(n.stack).endsWith(a.replace(/^.+\n.+\n/,""))&&(n.stack+=`
`+a):n.stack=a}catch{}}throw n}}_request(t,r){typeof t=="string"?(r=r||{},r.url=t):r=t||{},r=ai(this.defaults,r);const{transitional:n,paramsSerializer:o,headers:a}=r;n!==void 0&&Hh.assertOptions(n,{silentJSONParsing:ua.transitional(ua.boolean),forcedJSONParsing:ua.transitional(ua.boolean),clarifyTimeoutError:ua.transitional(ua.boolean)},!1),o!=null&&(Se.isFunction(o)?r.paramsSerializer={serialize:o}:Hh.assertOptions(o,{encode:ua.function,serialize:ua.function},!0)),r.method=(r.method||this.defaults.method||"get").toLowerCase();let i=a&&Se.merge(a.common,a[r.method]);a&&Se.forEach(["delete","get","head","post","put","patch","common"],C=>{delete a[C]}),r.headers=io.concat(i,a);const u=[];let f=!0;this.interceptors.request.forEach(function(w){typeof w.runWhen=="function"&&w.runWhen(r)===!1||(f=f&&w.synchronous,u.unshift(w.fulfilled,w.rejected))});const g=[];this.interceptors.response.forEach(function(w){g.push(w.fulfilled,w.rejected)});let m,S=0,O;if(!f){const C=[Oy.bind(this),void 0];for(C.unshift.apply(C,u),C.push.apply(C,g),O=C.length,m=Promise.resolve(r);S<O;)m=m.then(C[S++],C[S++]);return m}O=u.length;let T=r;for(S=0;S<O;){const C=u[S++],w=u[S++];try{T=C(T)}catch(b){w.call(this,b);break}}try{m=Oy.call(this,T)}catch(C){return Promise.reject(C)}for(S=0,O=g.length;S<O;)m=m.then(g[S++],g[S++]);return m}getUri(t){t=ai(this.defaults,t);const r=E_(t.baseURL,t.url);return T_(r,t.params,t.paramsSerializer)}}Se.forEach(["delete","get","head","options"],function(t){od.prototype[t]=function(r,n){return this.request(ai(n||{},{method:t,url:r,data:(n||{}).data}))}});Se.forEach(["post","put","patch"],function(t){function r(n){return function(a,i,u){return this.request(ai(u||{},{method:t,headers:n?{"Content-Type":"multipart/form-data"}:{},url:a,data:i}))}}od.prototype[t]=r(),od.prototype[t+"Form"]=r(!0)});const Nc=od;class E0{constructor(t){if(typeof t!="function")throw new TypeError("executor must be a function.");let r;this.promise=new Promise(function(a){r=a});const n=this;this.promise.then(o=>{if(!n._listeners)return;let a=n._listeners.length;for(;a-- >0;)n._listeners[a](o);n._listeners=null}),this.promise.then=o=>{let a;const i=new Promise(u=>{n.subscribe(u),a=u}).then(o);return i.cancel=function(){n.unsubscribe(a)},i},t(function(a,i,u){n.reason||(n.reason=new Ts(a,i,u),r(n.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const r=this._listeners.indexOf(t);r!==-1&&this._listeners.splice(r,1)}static source(){let t;return{token:new E0(function(o){t=o}),cancel:t}}}const zA=E0;function BA(e){return function(r){return e.apply(null,r)}}function VA(e){return Se.isObject(e)&&e.isAxiosError===!0}const qh={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(qh).forEach(([e,t])=>{qh[t]=e});const UA=qh;function D_(e){const t=new Nc(e),r=p_(Nc.prototype.request,t);return Se.extend(r,Nc.prototype,t,{allOwnKeys:!0}),Se.extend(r,t,null,{allOwnKeys:!0}),r.create=function(o){return D_(ai(e,o))},r}const gr=D_(k0);gr.Axios=Nc;gr.CanceledError=Ts;gr.CancelToken=zA;gr.isCancel=k_;gr.VERSION=I_;gr.toFormData=rf;gr.AxiosError=St;gr.Cancel=gr.CanceledError;gr.all=function(t){return Promise.all(t)};gr.spread=BA;gr.isAxiosError=VA;gr.mergeConfig=ai;gr.AxiosHeaders=io;gr.formToJSON=e=>O_(Se.isHTMLForm(e)?new FormData(e):e);gr.getAdapter=M_.getAdapter;gr.HttpStatusCode=UA;gr.default=gr;const Je=gr;var Gh={},L_={exports:{}},Tn={},F_={exports:{}},z_={};/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/(function(e){function t(q,B){var H=q.length;q.push(B);e:for(;0<H;){var K=H-1>>>1,X=q[K];if(0<o(X,B))q[K]=B,q[H]=X,H=K;else break e}}function r(q){return q.length===0?null:q[0]}function n(q){if(q.length===0)return null;var B=q[0],H=q.pop();if(H!==B){q[0]=H;e:for(var K=0,X=q.length,re=X>>>1;K<re;){var me=2*(K+1)-1,de=q[me],ee=me+1,_e=q[ee];if(0>o(de,H))ee<X&&0>o(_e,de)?(q[K]=_e,q[ee]=H,K=ee):(q[K]=de,q[me]=H,K=me);else if(ee<X&&0>o(_e,H))q[K]=_e,q[ee]=H,K=ee;else break e}}return B}function o(q,B){var H=q.sortIndex-B.sortIndex;return H!==0?H:q.id-B.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var i=Date,u=i.now();e.unstable_now=function(){return i.now()-u}}var f=[],g=[],m=1,S=null,O=3,T=!1,C=!1,w=!1,b=typeof setTimeout=="function"?setTimeout:null,d=typeof clearTimeout=="function"?clearTimeout:null,s=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function c(q){for(var B=r(g);B!==null;){if(B.callback===null)n(g);else if(B.startTime<=q)n(g),B.sortIndex=B.expirationTime,t(f,B);else break;B=r(g)}}function l(q){if(w=!1,c(q),!C)if(r(f)!==null)C=!0,V(h);else{var B=r(g);B!==null&&Y(l,B.startTime-q)}}function h(q,B){C=!1,w&&(w=!1,d(j),j=-1),T=!0;var H=O;try{for(c(B),S=r(f);S!==null&&(!(S.expirationTime>B)||q&&!A());){var K=S.callback;if(typeof K=="function"){S.callback=null,O=S.priorityLevel;var X=K(S.expirationTime<=B);B=e.unstable_now(),typeof X=="function"?S.callback=X:S===r(f)&&n(f),c(B)}else n(f);S=r(f)}if(S!==null)var re=!0;else{var me=r(g);me!==null&&Y(l,me.startTime-B),re=!1}return re}finally{S=null,O=H,T=!1}}var y=!1,x=null,j=-1,k=5,N=-1;function A(){return!(e.unstable_now()-N<k)}function M(){if(x!==null){var q=e.unstable_now();N=q;var B=!0;try{B=x(!0,q)}finally{B?D():(y=!1,x=null)}}else y=!1}var D;if(typeof s=="function")D=function(){s(M)};else if(typeof MessageChannel<"u"){var L=new MessageChannel,z=L.port2;L.port1.onmessage=M,D=function(){z.postMessage(null)}}else D=function(){b(M,0)};function V(q){x=q,y||(y=!0,D())}function Y(q,B){j=b(function(){q(e.unstable_now())},B)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(q){q.callback=null},e.unstable_continueExecution=function(){C||T||(C=!0,V(h))},e.unstable_forceFrameRate=function(q){0>q||125<q?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):k=0<q?Math.floor(1e3/q):5},e.unstable_getCurrentPriorityLevel=function(){return O},e.unstable_getFirstCallbackNode=function(){return r(f)},e.unstable_next=function(q){switch(O){case 1:case 2:case 3:var B=3;break;default:B=O}var H=O;O=B;try{return q()}finally{O=H}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(q,B){switch(q){case 1:case 2:case 3:case 4:case 5:break;default:q=3}var H=O;O=q;try{return B()}finally{O=H}},e.unstable_scheduleCallback=function(q,B,H){var K=e.unstable_now();switch(typeof H=="object"&&H!==null?(H=H.delay,H=typeof H=="number"&&0<H?K+H:K):H=K,q){case 1:var X=-1;break;case 2:X=250;break;case 5:X=1073741823;break;case 4:X=1e4;break;default:X=5e3}return X=H+X,q={id:m++,callback:B,priorityLevel:q,startTime:H,expirationTime:X,sortIndex:-1},H>K?(q.sortIndex=H,t(g,q),r(f)===null&&q===r(g)&&(w?(d(j),j=-1):w=!0,Y(l,H-K))):(q.sortIndex=X,t(f,q),C||T||(C=!0,V(h))),q},e.unstable_shouldYield=A,e.unstable_wrapCallback=function(q){var B=O;return function(){var H=O;O=B;try{return q.apply(this,arguments)}finally{O=H}}}})(z_);F_.exports=z_;var WA=F_.exports;/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var B_=P,Sn=WA;function Me(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=1;r<arguments.length;r++)t+="&args[]="+encodeURIComponent(arguments[r]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var V_=new Set,Ll={};function gi(e,t){ss(e,t),ss(e+"Capture",t)}function ss(e,t){for(Ll[e]=t,e=0;e<t.length;e++)V_.add(t[e])}var Ho=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Kh=Object.prototype.hasOwnProperty,$A=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,Ny={},Ey={};function HA(e){return Kh.call(Ey,e)?!0:Kh.call(Ny,e)?!1:$A.test(e)?Ey[e]=!0:(Ny[e]=!0,!1)}function qA(e,t,r,n){if(r!==null&&r.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return n?!1:r!==null?!r.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function GA(e,t,r,n){if(t===null||typeof t>"u"||qA(e,t,r,n))return!0;if(n)return!1;if(r!==null)switch(r.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function Qr(e,t,r,n,o,a,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=n,this.attributeNamespace=o,this.mustUseProperty=r,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=i}var Rr={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){Rr[e]=new Qr(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];Rr[t]=new Qr(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){Rr[e]=new Qr(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){Rr[e]=new Qr(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){Rr[e]=new Qr(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){Rr[e]=new Qr(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){Rr[e]=new Qr(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){Rr[e]=new Qr(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){Rr[e]=new Qr(e,5,!1,e.toLowerCase(),null,!1,!1)});var j0=/[\-:]([a-z])/g;function A0(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(j0,A0);Rr[t]=new Qr(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(j0,A0);Rr[t]=new Qr(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(j0,A0);Rr[t]=new Qr(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){Rr[e]=new Qr(e,1,!1,e.toLowerCase(),null,!1,!1)});Rr.xlinkHref=new Qr("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){Rr[e]=new Qr(e,1,!1,e.toLowerCase(),null,!0,!0)});function R0(e,t,r,n){var o=Rr.hasOwnProperty(t)?Rr[t]:null;(o!==null?o.type!==0:n||!(2<t.length)||t[0]!=="o"&&t[0]!== Line truncated
`+Vp+e}var Up=!1;function Wp(e,t){if(!e||Up)return"";Up=!0;var r=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(t,[])}catch(g){var n=g}Reflect.construct(e,[],t)}else{try{t.call()}catch(g){n=g}e.call(t.prototype)}else{try{throw Error()}catch(g){n=g}e()}}catch(g){if(g&&n&&typeof g.stack=="string"){for(var o=g.stack.split(`
`),a=n.stack.split(`
`),i=o.length-1,u=a.length-1;1<=i&&0<=u&&o[i]!==a[u];)u--;for(;1<=i&&0<=u;i--,u--)if(o[i]!==a[u]){if(i!==1||u!==1)do if(i--,u--,0>u||o[i]!==a[u]){var f=`
`+o[i].replace(" at new "," at ");return e.displayName&&f.includes("<anonymous>")&&(f=f.replace("<anonymous>",e.displayName)),f}while(1<=i&&0<=u);break}}}finally{Up=!1,Error.prepareStackTrace=r}return(e=e?e.displayName||e.name:"")?gl(e):""}function KA(e){switch(e.tag){case 5:return gl(e.type);case 16:return gl("Lazy");case 13:return gl("Suspense");case 19:return gl("SuspenseList");case 0:case 2:case 15:return e=Wp(e.type,!1),e;case 11:return e=Wp(e.type.render,!1),e;case 1:return e=Wp(e.type,!0),e;default:return""}}function Zh(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Li:return"Fragment";case Di:return"Portal";case Yh:return"Profiler";case M0:return"StrictMode";case Qh:return"Suspense";case Xh:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case W_:return(e.displayName||"Context")+".Consumer";case U_:return(e._context.displayName||"Context")+".Provider";case I0:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case D0:return t=e.displayName||null,t!==null?t:Zh(e.type)||"Memo";case ga:t=e._payload,e=e._init;try{return Zh(e(t))}catch{}}return null}function YA(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Zh(t);case 8:return t===M0?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function Ia(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function H_(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function QA(e){var t=H_(e)?"checked":"value",r=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),n=""+e[t];if(!e.hasOwnProperty(t)&&typeof r<"u"&&typeof r.get=="function"&&typeof r.set=="function"){var o=r.get,a=r.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(i){n=""+i,a.call(this,i)}}),Object.defineProperty(e,t,{enumerable:r.enumerable}),{getValue:function(){return n},setValue:function(i){n=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Qu(e){e._valueTracker||(e._valueTracker=QA(e))}function q_(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var r=t.getValue(),n="";return e&&(n=H_(e)?e.checked?"true":"false":e.value),e=n,e!==r?(t.setValue(e),!0):!1}function ad(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function Jh(e,t){var r=t.checked;return lr({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:r??e._wrapperState.initialChecked})}function Ay(e,t){var r=t.defaultValue==null?"":t.defaultValue,n=t.checked!=null?t.checked:t.defaultChecked;r=Ia(t.value!=null?t.value:r),e._wrapperState={initialChecked:n,initialValue:r,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function G_(e,t){t=t.checked,t!=null&&R0(e,"checked",t,!1)}function eg(e,t){G_(e,t);var r=Ia(t.value),n=t.type;if(r!=null)n==="number"?(r===0&&e.value===""||e.value!=r)&&(e.value=""+r):e.value!==""+r&&(e.value=""+r);else if(n==="submit"||n==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?tg(e,t.type,r):t.hasOwnProperty("defaultValue")&&tg(e,t.type,Ia(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Ry(e,t,r){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var n=t.type;if(!(n!=="submit"&&n!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,r||t===e.value||(e.value=t),e.defaultValue=t}r=e.name,r!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,r!==""&&(e.name=r)}function tg(e,t,r){(t!=="number"||ad(e.ownerDocument)!==e)&&(r==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+r&&(e.defaultValue=""+r))}var ml=Array.isArray;function Qi(e,t,r,n){if(e=e.options,t){t={};for(var o=0;o<r.length;o++)t["$"+r[o]]=!0;for(r=0;r<e.length;r++)o=t.hasOwnProperty("$"+e[r].value),e[r].selected!==o&&(e[r].selected=o),o&&n&&(e[r].defaultSelected=!0)}else{for(r=""+Ia(r),t=null,o=0;o<e.length;o++){if(e[o].value===r){e[o].selected=!0,n&&(e[o].defaultSelected=!0);return}t!==null||e[o].disabled||(t=e[o])}t!==null&&(t.selected=!0)}}function rg(e,t){if(t.dan Line truncated
`).replace(iM,"")}function oc(e,t,r){if(t=rx(t),rx(e)!==t&&r)throw Error(Me(425))}function pd(){}var gg=null,mg=null;function bg(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var vg=typeof setTimeout=="function"?setTimeout:void 0,sM=typeof clearTimeout=="function"?clearTimeout:void 0,nx=typeof Promise=="function"?Promise:void 0,lM=typeof queueMicrotask=="function"?queueMicrotask:typeof nx<"u"?function(e){return nx.resolve(null).then(e).catch(uM)}:vg;function uM(e){setTimeout(function(){throw e})}function rh(e,t){var r=t,n=0;do{var o=r.nextSibling;if(e.removeChild(r),o&&o.nodeType===8)if(r=o.data,r==="/$"){if(n===0){e.removeChild(o),Ul(t);return}n--}else r!=="$"&&r!=="$?"&&r!=="$!"||n++;r=o}while(r);Ul(t)}function Oa(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function ox(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var r=e.data;if(r==="$"||r==="$!"||r==="$?"){if(t===0)return e;t--}else r==="/$"&&t++}e=e.previousSibling}return null}var Os=Math.random().toString(36).slice(2),Po="__reactFiber$"+Os,Gl="__reactProps$"+Os,qo="__reactContainer$"+Os,yg="__reactEvents$"+Os,cM="__reactListeners$"+Os,dM="__reactHandles$"+Os;function Za(e){var t=e[Po];if(t)return t;for(var r=e.parentNode;r;){if(t=r[qo]||r[Po]){if(r=t.alternate,t.child!==null||r!==null&&r.child!==null)for(e=ox(e);e!==null;){if(r=e[Po])return r;e=ox(e)}return t}e=r,r=e.parentNode}return null}function gu(e){return e=e[Po]||e[qo],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function Vi(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(Me(33))}function cf(e){return e[Gl]||null}var xg=[],Ui=-1;function za(e){return{current:e}}function Xt(e){0>Ui||(e.current=xg[Ui],xg[Ui]=null,Ui--)}function Kt(e,t){Ui++,xg[Ui]=e.current,e.current=t}var Da={},Ur=za(Da),sn=za(!1),ii=Da;function ls(e,t){var r=e.type.contextTypes;if(!r)return Da;var n=e.stateNode;if(n&&n.__reactInternalMemoizedUnmaskedChildContext===t)return n.__reactInternalMemoizedMaskedChildContext;var o={},a;for(a in r)o[a]=t[a];return n&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function ln(e){return e=e.childContextTypes,e!=null}function hd(){Xt(sn),Xt(Ur)}function ax(e,t,r){if(Ur.current!==Da)throw Error(Me(168));Kt(Ur,t),Kt(sn,r)}function IS(e,t,r){var n=e.stateNode;if(t=t.childContextTypes,typeof n.getChildContext!="function")return r;n=n.getChildContext();for(var o in n)if(!(o in t))throw Error(Me(108,YA(e)||"Unknown",o));return lr({},r,n)}function gd(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Da,ii=Ur.current,Kt(Ur,e),Kt(sn,sn.current),!0}function ix(e,t,r){var n=e.stateNode;if(!n)throw Error(Me(169));r?(e=IS(e,t,ii),n.__reactInternalMemoizedMergedChildContext=e,Xt(sn),Xt(Ur),Kt(Ur,e)):Xt(sn),Kt(sn,r)}var zo=null,df=!1,nh=!1;function DS(e){zo===null?zo=[e]:zo.push(e)}function fM(e){df=!0,DS(e)}function Ba(){if(!nh&&zo!==null){nh=!0;var e=0,t=Wt;try{var r=zo;for(Wt=1;e<r.length;e++){var n=r[e];do n=n(!0);while(n!==null)}zo=null,df=!1}catch(o){throw zo!==null&&(zo=zo.slice(e+1)),sS(F0,Ba),o}finally{Wt=t,nh=!1}}return null}var Wi=[],$i=0,md=null,bd=0,In=[],Dn=0,si=null,Bo=1,Vo="";function Ka(e,t){Wi[$i++]=bd,Wi[$i++]=md,md=e,bd=t}function LS(e,t,r){In[Dn++]=Bo,In[Dn++]=Vo,In[Dn++]=si,si=e;var n=Bo;e=Vo;var o=32-so(n)-1;n&=~(1<<o),r+=1;var a=32-so(t)+o;if(30<a){var i=o-o%5;a=(n&(1<<i)-1).toString(32),n>>=i,o-=i,Bo=1<<32-so(t)+o|r<<o|n,Vo=a+e}else Bo=1<<a|r<<o|n,Vo=e}function G0(e){e.return!==null&&(Ka(e,1),LS(e,1,0))}function K0(e){for(;e===md;)md=Wi[--$i],Wi[$i]=null,bd=Wi[--$i],Wi[$i]=null;for(;e===si;)si=In[--Dn],In[Dn]=null,Vo=In[--Dn],In[Dn]=null,Bo=In[--Dn],In[Dn]=null}var wn=null,bn=null,er=!1,ro=null;function FS(e,t){var r=Fn(5,null,null,0);r.elementType="DELETED",r.stateNode=t,r.return=e,t=e.deletions,t===null?(e.deletions=[r],e.flags|=16):t.push(r)}function sx(e,t){switch(e.tag){case 5:var r=e.type;return t=t.nodeType!==1||r.toLowerCase()!==t.nodeName.toLowerCase()?null:t,t!==null?(e.stateNode=t,wn=e,bn=Oa(t.firstChild),!0):!1;case 6:return t=e.pendingProps===""||t.nodeType!==3?null:t,t!==null?(e.stateNode=t,wn=e,bn=null,!0):!1;case 13:return t=t.nodeType!==8?null:t,t!==null?(r=si!==null?{id:Bo,overflow:Vo}:null,e.memoizedState={dehydrated:t,treeContext:r,retryLane:1073741824},r=Fn(18,null,null,0),r.stateNode=t,r.return=e,e.child=r,wn=e,bn=null,!0):!1;default:return!1}}function wg(e){return(e.mode&1)!==0&&(e.flags&128)===0}function _g(e){if(er){var t=bn;if(t){var r=t;if(!sx(e,t)){if(wg(e))throw Error(Me(418));t=Oa(r.nextSibling);var n=wn;t&&sx(e,t)?FS(n,r):(e.flags=e.flags&-4097|2,er=!1,wn=e)}}else{if(wg(e))throw Error(Me(418));e.flags=e.flags&-4097|2,er=!1,wn=e}}}function lx(e){ Line truncated
Error generating stack: `+a.message+`
`+a.stack}return{value:e,source:t,stack:o,digest:null}}function lh(e,t,r){return{value:e,source:null,stack:r??null,digest:t??null}}function Pg(e,t){try{console.error(t.value)}catch(r){setTimeout(function(){throw r})}}var wM=typeof WeakMap=="function"?WeakMap:Map;function fC(e,t,r){r=Uo(-1,r),r.tag=3,r.payload={element:null};var n=t.value;return r.callback=function(){Td||(Td=!0,Dg=n),Pg(e,t)},r}function pC(e,t,r){r=Uo(-1,r),r.tag=3;var n=e.type.getDerivedStateFromError;if(typeof n=="function"){var o=t.value;r.payload=function(){return n(o)},r.callback=function(){Pg(e,t)}}var a=e.stateNode;return a!==null&&typeof a.componentDidCatch=="function"&&(r.callback=function(){Pg(e,t),typeof n!="function"&&(Na===null?Na=new Set([this]):Na.add(this));var i=t.stack;this.componentDidCatch(t.value,{componentStack:i!==null?i:""})}),r}function mx(e,t,r){var n=e.pingCache;if(n===null){n=e.pingCache=new wM;var o=new Set;n.set(t,o)}else o=n.get(t),o===void 0&&(o=new Set,n.set(t,o));o.has(r)||(o.add(r),e=IM.bind(null,e,t,r),t.then(e,e))}function bx(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function vx(e,t,r,n,o){return e.mode&1?(e.flags|=65536,e.lanes=o,e):(e===t?e.flags|=65536:(e.flags|=128,r.flags|=131072,r.flags&=-52805,r.tag===1&&(r.alternate===null?r.tag=17:(t=Uo(-1,1),t.tag=2,ka(r,t,1))),r.lanes|=1),e)}var _M=Xo.ReactCurrentOwner,on=!1;function Gr(e,t,r,n){t.child=e===null?HS(t,null,r,n):cs(t,e.child,r,n)}function yx(e,t,r,n,o){r=r.render;var a=t.ref;return es(t,o),n=am(e,t,r,n,a,o),r=im(),e!==null&&!on?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Ko(e,t,o)):(er&&r&&G0(t),t.flags|=1,Gr(e,t,n,o),t.child)}function xx(e,t,r,n,o){if(e===null){var a=r.type;return typeof a=="function"&&!mm(a)&&a.defaultProps===void 0&&r.compare===null&&r.defaultProps===void 0?(t.tag=15,t.type=a,hC(e,t,a,n,o)):(e=zc(r.type,null,n,t,t.mode,o),e.ref=t.ref,e.return=t,t.child=e)}if(a=e.child,!(e.lanes&o)){var i=a.memoizedProps;if(r=r.compare,r=r!==null?r:$l,r(i,n)&&e.ref===t.ref)return Ko(e,t,o)}return t.flags|=1,e=ja(a,n),e.ref=t.ref,e.return=t,t.child=e}function hC(e,t,r,n,o){if(e!==null){var a=e.memoizedProps;if($l(a,n)&&e.ref===t.ref)if(on=!1,t.pendingProps=n=a,(e.lanes&o)!==0)e.flags&131072&&(on=!0);else return t.lanes=e.lanes,Ko(e,t,o)}return Og(e,t,r,n,o)}function gC(e,t,r){var n=t.pendingProps,o=n.children,a=e!==null?e.memoizedState:null;if(n.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},Kt(Gi,mn),mn|=r;else{if(!(r&1073741824))return e=a!==null?a.baseLanes|r:r,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,Kt(Gi,mn),mn|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},n=a!==null?a.baseLanes:r,Kt(Gi,mn),mn|=n}else a!==null?(n=a.baseLanes|r,t.memoizedState=null):n=r,Kt(Gi,mn),mn|=n;return Gr(e,t,o,r),t.child}function mC(e,t){var r=t.ref;(e===null&&r!==null||e!==null&&e.ref!==r)&&(t.flags|=512,t.flags|=2097152)}function Og(e,t,r,n,o){var a=ln(r)?ii:Ur.current;return a=ls(t,a),es(t,o),r=am(e,t,r,n,a,o),n=im(),e!==null&&!on?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Ko(e,t,o)):(er&&n&&G0(t),t.flags|=1,Gr(e,t,r,o),t.child)}function wx(e,t,r,n,o){if(ln(r)){var a=!0;gd(t)}else a=!1;if(es(t,o),t.stateNode===null)Dc(e,t),WS(t,r,n),Tg(t,r,n,o),n=!0;else if(e===null){var i=t.stateNode,u=t.memoizedProps;i.props=u;var f=i.context,g=r.contextType;typeof g=="object"&&g!==null?g=Un(g):(g=ln(r)?ii:Ur.current,g=ls(t,g));var m=r.getDerivedStateFromProps,S=typeof m=="function"||typeof i.getSnapshotBeforeUpdate=="function";S||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(u!==n||f!==g)&&fx(t,i,n,g),ma=!1;var O=t.memoizedState;i.state=O,xd(t,n,i,o),f=t.memoizedState,u!==n||O!==f||sn.current||ma?(typeof m=="function"&&(Cg(t,r,m,n),f=t.memoizedState),(u=ma||dx(t,r,u,n,O,f,g))?(S||typeof i.UNSAFE_componentWillMount!="function"&&typeof i.componentWillMount!="function"||(typeof i.componentWillMount=="function"&&i.componentWillMount(),typeof i.UNSAFE_componentWillMount=="function"&&i.UNSAFE_componentWillMount()),typeof i.componentDidMount=="function"&&(t.flags|=4194308)):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=n,t.memoizedState=f),i.props=n,i.state=f,i.context=g,n=u):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),n=!1)}else{i=t.stateNode,VS(e,t),u=t.memoizedProps,g=t.type===t.elementType?u:Jn(t.type,u),i.props=g,S=t.pendingProps,O=i.context,f=r.contextType,typeof f=="object"&&f!==null?f=Un(f):(f=ln(r)?ii:Ur.current,f=ls(t,f));var T=r.getDerivedStateFromProps;(m=typeof T=="function"||typeof i.getSnapshotBeforeUpdate=="function")||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(u!==S||O!==f)&&fx(t,i,n,f),ma=!1,O=t.memoizedState,i.state=O,xd(t,n,i,o);var C=t.memoizedS Line truncated
* @remix-run/router v1.10.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/function eu(){return eu=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},eu.apply(this,arguments)}var _a;(function(e){e.Pop="POP",e.Push="PUSH",e.Replace="REPLACE"})(_a||(_a={}));const Ix="popstate";function qM(e){e===void 0&&(e={});function t(n,o){let{pathname:a,search:i,hash:u}=n.location;return Bg("",{pathname:a,search:i,hash:u},o.state&&o.state.usr||null,o.state&&o.state.key||"default")}function r(n,o){return typeof o=="string"?o:Nd(o)}return KM(t,r,null,e)}function sr(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function wm(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function GM(){return Math.random().toString(36).substr(2,8)}function Dx(e,t){return{usr:e.state,key:e.key,idx:t}}function Bg(e,t,r,n){return r===void 0&&(r=null),eu({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?ks(t):t,{state:r,key:t&&t.key||n||GM()})}function Nd(e){let{pathname:t="/",search:r="",hash:n=""}=e;return r&&r!=="?"&&(t+=r.charAt(0)==="?"?r:"?"+r),n&&n!=="#"&&(t+=n.charAt(0)==="#"?n:"#"+n),t}function ks(e){let t={};if(e){let r=e.indexOf("#");r>=0&&(t.hash=e.substr(r),e=e.substr(0,r));let n=e.indexOf("?");n>=0&&(t.search=e.substr(n),e=e.substr(0,n)),e&&(t.pathname=e)}return t}function KM(e,t,r,n){n===void 0&&(n={});let{window:o=document.defaultView,v5Compat:a=!1}=n,i=o.history,u=_a.Pop,f=null,g=m();g==null&&(g=0,i.replaceState(eu({},i.state,{idx:g}),""));function m(){return(i.state||{idx:null}).idx}function S(){u=_a.Pop;let b=m(),d=b==null?null:b-g;g=b,f&&f({action:u,location:w.location,delta:d})}function O(b,d){u=_a.Push;let s=Bg(w.location,b,d);r&&r(s,b),g=m()+1;let c=Dx(s,g),l=w.createHref(s);try{i.pushState(c,"",l)}catch(h){if(h instanceof DOMException&&h.name==="DataCloneError")throw h;o.location.assign(l)}a&&f&&f({action:u,location:w.location,delta:1})}function T(b,d){u=_a.Replace;let s=Bg(w.location,b,d);r&&r(s,b),g=m();let c=Dx(s,g),l=w.createHref(s);i.replaceState(c,"",l),a&&f&&f({action:u,location:w.location,delta:0})}function C(b){let d=o.location.origin!=="null"?o.location.origin:o.location.href,s=typeof b=="string"?b:Nd(b);return sr(d,"No window.location.(origin|href) available to create URL for href: "+s),new URL(s,d)}let w={get action(){return u},get location(){return e(o,i)},listen(b){if(f)throw new Error("A history only accepts one active listener");return o.addEventListener(Ix,S),f=b,()=>{o.removeEventListener(Ix,S),f=null}},createHref(b){return t(o,b)},createURL:C,encodeLocation(b){let d=C(b);return{pathname:d.pathname,search:d.search,hash:d.hash}},push:O,replace:T,go(b){return i.go(b)}};return w}var Lx;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(Lx||(Lx={}));function YM(e,t,r){r===void 0&&(r="/");let n=typeof t=="string"?ks(t):t,o=tu(n.pathname||"/",r);if(o==null)return null;let a=BC(e);QM(a);let i=null;for(let u=0;i==null&&u<a.length;++u)i=aI(a[u],sI(o));return i}function BC(e,t,r,n){t===void 0&&(t=[]),r===void 0&&(r=[]),n===void 0&&(n="");let o=(a,i,u)=>{let f={relativePath:u===void 0?a.path||"":u,caseSensitive:a.caseSensitive===!0,childrenIndex:i,route:a};f.relativePath.startsWith("/")&&(sr(f.relativePath.startsWith(n),'Absolute route path "'+f.relativePath+'" nested under path '+('"'+n+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),f.relativePath=f.relativePath.slice(n.length));let g=Aa([n,f.relativePath]),m=r.concat(f);a.children&&a.children.length>0&&(sr(a.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+g+'".')),BC(a.children,t,m,g)),!(a.path==null&&!a.index)&&t.push({path:g,score:nI(g,a.index),routesMeta:m})};return e.forEach((a,i)=>{var u;if(a.path===""||!((u=a.path)!=null&&u.includes("?")))o(a,i);else for(let f of VC(a.path))o(a,i,f)}),t}function VC(e){let t=e.split("/");if(t.length===0)return[];let[r,...n]=t,o=r.endsWith("?"),a=r.replace(/\?$/,"");if(n.length===0)return o?[a,""]:[a];let i=VC(n.join("/")),u=[];return u.push(...i.map(f=>f===""?a:[a,f].join("/"))),o&&u.push(...i),u.map(f=>e.startsWith("/")&&f===""?"/":f)}function QM(e){e.sort((t,r)=>t.score!==r.score?r.score-t.score:oI(t.routesMeta.map(n=>n.childrenIndex),r.routesMeta.map(n=>n.childrenIndex)))}const XM=/^:\w+$/,ZM=3,JM=2,eI=1,tI=10,rI=-2,Fx=e=>e==="*";function nI(e,t){let r=e.split("/"),n=r.length;return r.some(Fx)&&(n+=rI),t&&(n+=JM),r.filter(o=>!Fx(o)).reduce((o,a)=>o+(XM.test(a)?ZM:a===""?eI:tI),n)}function oI(e,t){return e.length===t.length&&e.slice(0,-1).every((n,o)=>n===t[o])?e[e.length-1]-t[t.length-1]:0}function aI(e,t){let{routesMeta:r}=e,n={},o="/",a=[];for(let i=0;i<r.length;++i){let u=r[i],f=i===r.length-1,g=o==="/"?t:t.slice(o.length)||"/",m=Vg({path:u.relativePath,caseSensitive:u.caseSensitive,end:f},g);if(!m)return null;Object.assign(n,m.paLine truncated
* React Router v6.17.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/function Ed(){return Ed=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Ed.apply(this,arguments)}const wf=P.createContext(null),WC=P.createContext(null),bi=P.createContext(null),_f=P.createContext(null),Zo=P.createContext({outlet:null,matches:[],isDataRoute:!1}),$C=P.createContext(null);function mI(e,t){let{relative:r}=t===void 0?{}:t;Ns()||sr(!1);let{basename:n,navigator:o}=P.useContext(bi),{hash:a,pathname:i,search:u}=Sf(e,{relative:r}),f=i;return n!=="/"&&(f=i==="/"?n:Aa([n,i])),o.createHref({pathname:f,search:u,hash:a})}function Ns(){return P.useContext(_f)!=null}function Va(){return Ns()||sr(!1),P.useContext(_f).location}function HC(e){P.useContext(bi).static||P.useLayoutEffect(e)}function $n(){let{isDataRoute:e}=P.useContext(Zo);return e?NI():bI()}function bI(){Ns()||sr(!1);let e=P.useContext(wf),{basename:t,navigator:r}=P.useContext(bi),{matches:n}=P.useContext(Zo),{pathname:o}=Va(),a=JSON.stringify(_m(n).map(f=>f.pathnameBase)),i=P.useRef(!1);return HC(()=>{i.current=!0}),P.useCallback(function(f,g){if(g===void 0&&(g={}),!i.current)return;if(typeof f=="number"){r.go(f);return}let m=Sm(f,JSON.parse(a),o,g.relative==="path");e==null&&t!=="/"&&(m.pathname=m.pathname==="/"?t:Aa([t,m.pathname])),(g.replace?r.replace:r.push)(m,g.state,g)},[t,r,a,o,e])}function Cm(){let{matches:e}=P.useContext(Zo),t=e[e.length-1];return t?t.params:{}}function Sf(e,t){let{relative:r}=t===void 0?{}:t,{matches:n}=P.useContext(Zo),{pathname:o}=Va(),a=JSON.stringify(_m(n).map(i=>i.pathnameBase));return P.useMemo(()=>Sm(e,JSON.parse(a),o,r==="path"),[e,a,o,r])}function vI(e,t){return yI(e,t)}function yI(e,t,r){Ns()||sr(!1);let{navigator:n}=P.useContext(bi),{matches:o}=P.useContext(Zo),a=o[o.length-1],i=a?a.params:{};a&&a.pathname;let u=a?a.pathnameBase:"/";a&&a.route;let f=Va(),g;if(t){var m;let w=typeof t=="string"?ks(t):t;u==="/"||(m=w.pathname)!=null&&m.startsWith(u)||sr(!1),g=w}else g=f;let S=g.pathname||"/",O=u==="/"?S:S.slice(u.length)||"/",T=YM(e,{pathname:O}),C=CI(T&&T.map(w=>Object.assign({},w,{params:Object.assign({},i,w.params),pathname:Aa([u,n.encodeLocation?n.encodeLocation(w.pathname).pathname:w.pathname]),pathnameBase:w.pathnameBase==="/"?u:Aa([u,n.encodeLocation?n.encodeLocation(w.pathnameBase).pathname:w.pathnameBase])})),o,r);return t&&C?P.createElement(_f.Provider,{value:{location:Ed({pathname:"/",search:"",hash:"",state:null,key:"default"},g),navigationType:_a.Pop}},C):C}function xI(){let e=kI(),t=hI(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),r=e instanceof Error?e.stack:null,o={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"},a=null;return P.createElement(P.Fragment,null,P.createElement("h2",null,"Unexpected Application Error!"),P.createElement("h3",{style:{fontStyle:"italic"}},t),r?P.createElement("pre",{style:o},r):null,a)}const wI=P.createElement(xI,null);class _I extends P.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,r){return r.location!==t.location||r.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error||r.error,location:r.location,revalidation:t.revalidation||r.revalidation}}componentDidCatch(t,r){console.error("React Router caught the following error during render",t,r)}render(){return this.state.error?P.createElement(Zo.Provider,{value:this.props.routeContext},P.createElement($C.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function SI(e){let{routeContext:t,match:r,children:n}=e,o=P.useContext(wf);return o&&o.static&&o.staticContext&&(r.route.errorElement||r.route.ErrorBoundary)&&(o.staticContext._deepestRenderedBoundaryId=r.route.id),P.createElement(Zo.Provider,{value:t},n)}function CI(e,t,r){var n;if(t===void 0&&(t=[]),r===void 0&&(r=null),e==null){var o;if((o=r)!=null&&o.errors)e=r.matches;else return null}let a=e,i=(n=r)==null?void 0:n.errors;if(i!=null){let u=a.findIndex(f=>f.route.id&&(i==null?void 0:i[f.route.id]));u>=0||sr(!1),a=a.slice(0,Math.min(a.length,u+1))}return a.reduceRight((u,f,g)=>{let m=f.route.id?i==null?void 0:i[f.route.id]:null,S=null;r&&(S=f.route.errorElement||wI);let O=t.concat(a.slice(0,g+1)),T=()=>{let C;return m?C=S:f.route.Component?C=P.createElement(f.route.Component,null):f.route.element?C=f.route.element:C=u,P.createElement(SI,{match:f,routeContext:{outlet:u,matches:O,isDataRoute:r!=null},children:C})};return r&&(f.route.ErrorBoundary||f.route.errorElement||g===0)?P.createElement(_I,{location:r.location,revalidation:r.revalidation,component:S,error:m,children:T(),routeContext:{outlet:null,matches:O,isDataRoute:!0}}):T()},null)}var qC=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable=Line truncated
* React Router DOM v6.17.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/function Ad(){return Ad=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Ad.apply(this,arguments)}function KC(e,t){if(e==null)return{};var r={},n=Object.keys(e),o,a;for(a=0;a<n.length;a++)o=n[a],!(t.indexOf(o)>=0)&&(r[o]=e[o]);return r}function AI(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function RI(e,t){return e.button===0&&(!t||t==="_self")&&!AI(e)}const MI=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","unstable_viewTransition"],II=["aria-current","caseSensitive","className","end","style","to","unstable_viewTransition","children"],DI=P.createContext({isTransitioning:!1}),LI="startTransition",zx=S0[LI];function FI(e){let{basename:t,children:r,future:n,window:o}=e,a=P.useRef();a.current==null&&(a.current=qM({window:o,v5Compat:!0}));let i=a.current,[u,f]=P.useState({action:i.action,location:i.location}),{v7_startTransition:g}=n||{},m=P.useCallback(S=>{g&&zx?zx(()=>f(S)):f(S)},[f,g]);return P.useLayoutEffect(()=>i.listen(m),[i,m]),P.createElement(jI,{basename:t,children:r,location:u.location,navigationType:u.action,navigator:i})}const zI=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",BI=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,YC=P.forwardRef(function(t,r){let{onClick:n,relative:o,reloadDocument:a,replace:i,state:u,target:f,to:g,preventScrollReset:m,unstable_viewTransition:S}=t,O=KC(t,MI),{basename:T}=P.useContext(bi),C,w=!1;if(typeof g=="string"&&BI.test(g)&&(C=g,zI))try{let c=new URL(window.location.href),l=g.startsWith("//")?new URL(c.protocol+g):new URL(g),h=tu(l.pathname,T);l.origin===c.origin&&h!=null?g=h+l.search+l.hash:w=!0}catch{}let b=mI(g,{relative:o}),d=WI(g,{replace:i,state:u,target:f,preventScrollReset:m,relative:o,unstable_viewTransition:S});function s(c){n&&n(c),c.defaultPrevented||d(c)}return P.createElement("a",Ad({},O,{href:C||b,onClick:w||a?n:s,ref:r,target:f}))}),VI=P.forwardRef(function(t,r){let{"aria-current":n="page",caseSensitive:o=!1,className:a="",end:i=!1,style:u,to:f,unstable_viewTransition:g,children:m}=t,S=KC(t,II),O=Sf(f,{relative:S.relative}),T=Va(),C=P.useContext(WC),{navigator:w}=P.useContext(bi),b=C!=null&&$I(O)&&g===!0,d=w.encodeLocation?w.encodeLocation(O).pathname:O.pathname,s=T.pathname,c=C&&C.navigation&&C.navigation.location?C.navigation.location.pathname:null;o||(s=s.toLowerCase(),c=c?c.toLowerCase():null,d=d.toLowerCase());let l=s===d||!i&&s.startsWith(d)&&s.charAt(d.length)==="/",h=c!=null&&(c===d||!i&&c.startsWith(d)&&c.charAt(d.length)==="/"),y={isActive:l,isPending:h,isTransitioning:b},x=l?n:void 0,j;typeof a=="function"?j=a(y):j=[a,l?"active":null,h?"pending":null,b?"transitioning":null].filter(Boolean).join(" ");let k=typeof u=="function"?u(y):u;return P.createElement(YC,Ad({},S,{"aria-current":x,className:j,ref:r,style:k,to:f,unstable_viewTransition:g}),typeof m=="function"?m(y):m)});var Wg;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Wg||(Wg={}));var Bx;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Bx||(Bx={}));function UI(e){let t=P.useContext(wf);return t||sr(!1),t}function WI(e,t){let{target:r,replace:n,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:u}=t===void 0?{}:t,f=$n(),g=Va(),m=Sf(e,{relative:i});return P.useCallback(S=>{if(RI(S,r)){S.preventDefault();let O=n!==void 0?n:Nd(g)===Nd(m);f(e,{replace:O,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:u})}},[g,f,m,n,o,r,e,a,i,u])}function $I(e,t){t===void 0&&(t={});let r=P.useContext(DI);r==null&&sr(!1);let{basename:n}=UI(Wg.useViewTransitionState),o=Sf(e,{relative:t.relative});if(!r.isTransitioning)return!1;let a=tu(r.currentLocation.pathname,n)||r.currentLocation.pathname,i=tu(r.nextLocation.pathname,n)||r.nextLocation.pathname;return Vg(o.pathname,i)!=null||Vg(o.pathname,a)!=null}function HI({title:e,titleId:t,...r},n){return P.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",ref:n,"aria-labelledby":t},r),e?P.createElement("title",{id:t},e):null,P.createElement("path",{fillRule:"evenodd",d:"M7.5 3.75A1.5 1.5 0 006 5.25v13.5a1.5 1.5 0 001.5 1.5h6a1.5 1.5 0 001.5-1.5V15a.75.75 0 011.5 0v3.75a3 3 0 01-3 3h-6a3 3 0 01-3-3V5.25a3 3 0 013-3h6a3 3 0 013 3V9A.75.75 0 0115 9V5.25a1.5 1.5 0 00-1.5-1.5h-6zm5.03 4.72a.75.75 0 010 1.06l-1.72 1.72h10.94a.75.75 0 010 1.5H10.81l1.72 1.72a.75.75 0 11-1.06 1.06l-3-3a.75.75 0 010-1.06l3-3a.75.75 0 011.06 0z",clipRule:"evenodd"}))}const qI=P.forwardRef(HI),GI=qI;function KI({title:e,titleId:t,...r},n){return P.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",rLine truncated
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/(function(e){(function(){var t={}.hasOwnProperty;function r(){for(var n=[],o=0;o<arguments.length;o++){var a=arguments[o];if(a){var i=typeof a;if(i==="string"||i==="number")n.push(a);else if(Array.isArray(a)){if(a.length){var u=r.apply(null,a);u&&n.push(u)}}else if(i==="object"){if(a.toString!==Object.prototype.toString&&!a.toString.toString().includes("[native code]")){n.push(a.toString());continue}for(var f in a)t.call(a,f)&&a[f]&&n.push(f)}}}return n.join(" ")}e.exports?(r.default=r,e.exports=r):window.classNames=r})()})(ZC);var gt=ZC.exports,JC={exports:{}},Zn={};function Pm(){for(var e,t,r=0,n="";r<arguments.length;)(e=arguments[r++])&&(t=e5(e))&&(n&&(n+=" "),n+=t);return n}function e5(e){if(typeof e=="string")return e;for(var t,r="",n=0;n<e.length;n++)e[n]&&(t=e5(e[n]))&&(r&&(r+=" "),r+=t);return r}function $g(){return $g=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},$g.apply(this,arguments)}function S4(e){var t=function(r){var n=r.theme,o=r.prefix,a={nextPart:new Map,validators:[]};return function(i,u){return u?i.map(function(f){return[f[0],f[1].map(function(g){return typeof g=="string"?u+g:typeof g=="object"?Object.fromEntries(Object.entries(g).map(function(m){return[u+m[0],m[1]]})):g})]}):i}(Object.entries(r.classGroups),o).forEach(function(i){Hg(i[1],a,i[0],n)}),a}(e);return{getClassGroupId:function(r){var n=r.split("-");return n[0]===""&&n.length!==1&&n.shift(),t5(n,t)||function(o){if(Vx.test(o)){var a=Vx.exec(o)[1],i=a==null?void 0:a.substring(0,a.indexOf(":"));if(i)return"arbitrary.."+i}}(r)},getConflictingClassGroupIds:function(r){return e.conflictingClassGroups[r]||[]}}}function t5(e,t){var r;if(e.length===0)return t.classGroupId;var n=t.nextPart.get(e[0]),o=n?t5(e.slice(1),n):void 0;if(o)return o;if(t.validators.length!==0){var a=e.join("-");return(r=t.validators.find(function(i){return(0,i.validator)(a)}))==null?void 0:r.classGroupId}}Object.defineProperty(Zn,"__esModule",{value:!0});var Vx=/^\[(.+)\]$/;function Hg(e,t,r,n){e.forEach(function(o){if(typeof o!="string"){if(typeof o=="function")return o.isThemeGetter?void Hg(o(n),t,r,n):void t.validators.push({validator:o,classGroupId:r});Object.entries(o).forEach(function(a){Hg(a[1],Ux(t,a[0]),r,n)})}else(o===""?t:Ux(t,o)).classGroupId=r})}function Ux(e,t){var r=e;return t.split("-").forEach(function(n){r.nextPart.has(n)||r.nextPart.set(n,{nextPart:new Map,validators:[]}),r=r.nextPart.get(n)}),r}function C4(e){if(e<1)return{get:function(){},set:function(){}};var t=0,r=new Map,n=new Map;function o(a,i){r.set(a,i),++t>e&&(t=0,n=r,r=new Map)}return{get:function(a){var i=r.get(a);return i!==void 0?i:(i=n.get(a))!==void 0?(o(a,i),i):void 0},set:function(a,i){r.has(a)?r.set(a,i):o(a,i)}}}function T4(e){var t=e.separator||":";return function(r){for(var n=0,o=[],a=0,i=0;i<r.length;i++){var u=r[i];n===0&&u===t[0]&&(t.length!==1&&r.slice(i,i+t.length)!==t||(o.push(r.slice(a,i)),a=i+t.length)),u==="["?n++:u==="]"&&n--}var f=o.length===0?r:r.substring(a),g=f.startsWith("!");return{modifiers:o,hasImportantModifier:g,baseClassName:g?f.substring(1):f}}}function P4(e){return $g({cache:C4(e.cacheSize),splitModifiers:T4(e)},S4(e))}var O4=/\s+/;function k4(e,t){var r=t.splitModifiers,n=t.getClassGroupId,o=t.getConflictingClassGroupIds,a=new Set;return e.trim().split(O4).map(function(i){var u=r(i),f=u.modifiers,g=u.hasImportantModifier,m=n(u.baseClassName);if(!m)return{isTailwindClass:!1,originalClassName:i};var S=function(O){if(O.length<=1)return O;var T=[],C=[];return O.forEach(function(w){w[0]==="["?(T.push.apply(T,C.sort().concat([w])),C=[]):C.push(w)}),T.push.apply(T,C.sort()),T}(f).join(":");return{isTailwindClass:!0,modifierId:g?S+"!":S,classGroupId:m,originalClassName:i}}).reverse().filter(function(i){if(!i.isTailwindClass)return!0;var u=i.modifierId,f=i.classGroupId,g=u+f;return!a.has(g)&&(a.add(g),o(f).forEach(function(m){return a.add(u+m)}),!0)}).reverse().map(function(i){return i.originalClassName}).join(" ")}function qg(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];var n,o,a,i=u;function u(g){var m=t[0],S=t.slice(1).reduce(function(O,T){return T(O)},m());return n=P4(S),o=n.cache.get,a=n.cache.set,i=f,f(g)}function f(g){var m=o(g);if(m)return m;var S=k4(g,n);return a(g,S),S}return function(){return i(Pm.apply(null,arguments))}}function Qt(e){var t=function(r){return r[e]||[]};return t.isThemeGetter=!0,t}var Ua=/^\[(.+)\]$/,N4=/^\d+\/\d+$/,E4=new Set(["px","full","screen"]),j4=/^(\d+)?(xs|sm|md|lg|xl)$/,A4=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh)/,R4=/^-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/;function Rn(e){return!Number.isNaN(Number(e))||E4.has(e)||N4.test(e)||ba(e)}function ba(e){var t,r=(t=Ua.exec(e))==null?void 0:t[1];return!!r&&(r.startsWith("length:")||A4.test(r))}function r5(e){var t,r=(t=Ua.exec(e))==null?void 0:t[1];return!!r&&r.startsWLine truncated
* tabbable 6.2.0
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
*/var SF=["input:not([inert])","select:not([inert])","textarea:not([inert])","a[href]:not([inert])","button:not([inert])","[tabindex]:not(slot):not([inert])","audio[controls]:not([inert])","video[controls]:not([inert])",'[contenteditable]:not([contenteditable="false"]):not([inert])',"details>summary:first-of-type:not([inert])","details:not([inert])"],s0=SF.join(","),uO=typeof Element>"u",su=uO?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,Ud=!uO&&Element.prototype.getRootNode?function(e){var t;return e==null||(t=e.getRootNode)===null||t===void 0?void 0:t.call(e)}:function(e){return e==null?void 0:e.ownerDocument},Wd=function e(t,r){var n;r===void 0&&(r=!0);var o=t==null||(n=t.getAttribute)===null||n===void 0?void 0:n.call(t,"inert"),a=o===""||o==="true",i=a||r&&t&&e(t.parentNode);return i},CF=function(t){var r,n=t==null||(r=t.getAttribute)===null||r===void 0?void 0:r.call(t,"contenteditable");return n===""||n==="true"},TF=function(t,r,n){if(Wd(t))return[];var o=Array.prototype.slice.apply(t.querySelectorAll(s0));return r&&su.call(t,s0)&&o.unshift(t),o=o.filter(n),o},PF=function e(t,r,n){for(var o=[],a=Array.from(t);a.length;){var i=a.shift();if(!Wd(i,!1))if(i.tagName==="SLOT"){var u=i.assignedElements(),f=u.length?u:i.children,g=e(f,!0,n);n.flatten?o.push.apply(o,g):o.push({scopeParent:i,candidates:g})}else{var m=su.call(i,s0);m&&n.filter(i)&&(r||!t.includes(i))&&o.push(i);var S=i.shadowRoot||typeof n.getShadowRoot=="function"&&n.getShadowRoot(i),O=!Wd(S,!1)&&(!n.shadowRootFilter||n.shadowRootFilter(i));if(S&&O){var T=e(S===!0?i.children:S.children,!0,n);n.flatten?o.push.apply(o,T):o.push({scopeParent:i,candidates:T})}else a.unshift.apply(a,i.children)}}return o},cO=function(t){return!isNaN(parseInt(t.getAttribute("tabindex"),10))},dO=function(t){if(!t)throw new Error("No node provided");return t.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(t.tagName)||CF(t))&&!cO(t)?0:t.tabIndex},OF=function(t,r){var n=dO(t);return n<0&&r&&!cO(t)?0:n},kF=function(t,r){return t.tabIndex===r.tabIndex?t.documentOrder-r.documentOrder:t.tabIndex-r.tabIndex},fO=function(t){return t.tagName==="INPUT"},NF=function(t){return fO(t)&&t.type==="hidden"},EF=function(t){var r=t.tagName==="DETAILS"&&Array.prototype.slice.apply(t.children).some(function(n){return n.tagName==="SUMMARY"});return r},jF=function(t,r){for(var n=0;n<t.length;n++)if(t[n].checked&&t[n].form===r)return t[n]},AF=function(t){if(!t.name)return!0;var r=t.form||Ud(t),n=function(u){return r.querySelectorAll('input[type="radio"][name="'+u+'"]')},o;if(typeof window<"u"&&typeof window.CSS<"u"&&typeof window.CSS.escape=="function")o=n(window.CSS.escape(t.name));else try{o=n(t.name)}catch(i){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",i.message),!1}var a=jF(o,t.form);return!a||a===t},RF=function(t){return fO(t)&&t.type==="radio"},MF=function(t){return RF(t)&&!AF(t)},IF=function(t){var r,n=t&&Ud(t),o=(r=n)===null||r===void 0?void 0:r.host,a=!1;if(n&&n!==t){var i,u,f;for(a=!!((i=o)!==null&&i!==void 0&&(u=i.ownerDocument)!==null&&u!==void 0&&u.contains(o)||t!=null&&(f=t.ownerDocument)!==null&&f!==void 0&&f.contains(t));!a&&o;){var g,m,S;n=Ud(o),o=(g=n)===null||g===void 0?void 0:g.host,a=!!((m=o)!==null&&m!==void 0&&(S=m.ownerDocument)!==null&&S!==void 0&&S.contains(o))}}return a},mw=function(t){var r=t.getBoundingClientRect(),n=r.width,o=r.height;return n===0&&o===0},DF=function(t,r){var n=r.displayCheck,o=r.getShadowRoot;if(getComputedStyle(t).visibility==="hidden")return!0;var a=su.call(t,"details>summary:first-of-type"),i=a?t.parentElement:t;if(su.call(i,"details:not([open]) *"))return!0;if(!n||n==="full"||n==="legacy-full"){if(typeof o=="function"){for(var u=t;t;){var f=t.parentElement,g=Ud(t);if(f&&!f.shadowRoot&&o(f)===!0)return mw(t);t.assignedSlot?t=t.assignedSlot:!f&&g!==t.ownerDocument?t=g.host:t=f}t=u}if(IF(t))return!t.getClientRects().length;if(n!=="legacy-full")return!0}else if(n==="non-zero-area")return mw(t);return!1},LF=function(t){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(t.tagName))for(var r=t.parentElement;r;){if(r.tagName==="FIELDSET"&&r.disabled){for(var n=0;n<r.children.length;n++){var o=r.children.item(n);if(o.tagName==="LEGEND")return su.call(r,"fieldset[disabled] *")?!0:!o.contains(t)}return!0}r=r.parentElement}return!1},FF=function(t,r){return!(r.disabled||Wd(r)||NF(r)||DF(r,t)||EF(r)||LF(r))},bw=function(t,r){return!(MF(r)||dO(r)<0||!FF(t,r))},zF=function(t){var r=parseInt(t.getAttribute("tabindex"),10);return!!(isNaN(r)||r>=0)},BF=function e(t){var r=[],n=[];return t.forEach(function(o,a){var i=!!o.scopeParent,u=i?o.scopeParent:o,f=OF(u,i),g=i?e(o.candidates):u;f===0?i?r.push.apply(r,g):r.push(u):n.push({documentOrder:a,tabIndex:f,item:o,isScope:i,content:g})}),n.sort(kF).reduce(function(o,a){return a.isScope?o.push.apply(o,a.content):o.push(a.content),o},[]).concatLine truncated
More details: https://www.material-tailwind.com/docs/react/collapse
`);var k=r.default.useRef(null),N=(0,g.useTheme)().navbar,A=N.styles,M=A.base.mobileNav;l=l??{},h=h??"";var D=(0,u.twMerge)((0,i.default)((0,f.default)(M)),h),L={unmount:{height:0,opacity:0,transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}},mount:{opacity:1,height:"".concat((j=k.current)===null||j===void 0?void 0:j.scrollHeight,"px"),transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}}},z=(0,a.default)(L,l),V=n.AnimatePresence,Y=(0,o.useMergeRefs)([s,k]);return r.default.createElement(n.LazyMotion,{features:n.domAnimation},r.default.createElement(V,null,r.default.createElement(n.m.div,S({},x,{ref:Y,className:D,initial:"unmount",exit:"unmount",animate:c?"mount":"unmount",variants:z}),y)))});w.displayName="MaterialTailwind.MobileNav",w.propTypes={open:m.propTypesOpen,animate:m.propTypesAnimate,className:m.propTypesClassName,children:m.propTypesChildren};var b=w})(tk);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(s,c){for(var l in c)Object.defineProperty(s,l,{enumerable:!0,get:c[l]})}t(e,{Navbar:function(){return b},MobileNav:function(){return g.MobileNav},default:function(){return d}});var r=T(P),n=T(ut),o=T(gt),a=lt,i=T(tr),u=T(nt),f=et,g=tk,m=Cf;function S(s,c,l){return c in s?Object.defineProperty(s,c,{value:l,enumerable:!0,configurable:!0,writable:!0}):s[c]=l,s}function O(){return O=Object.assign||function(s){for(var c=1;c<arguments.length;c++){var l=arguments[c];for(var h in l)Object.prototype.hasOwnProperty.call(l,h)&&(s[h]=l[h])}return s},O.apply(this,arguments)}function T(s){return s&&s.__esModule?s:{default:s}}function C(s,c){if(s==null)return{};var l=w(s,c),h,y;if(Object.getOwnPropertySymbols){var x=Object.getOwnPropertySymbols(s);for(y=0;y<x.length;y++)h=x[y],!(c.indexOf(h)>=0)&&Object.prototype.propertyIsEnumerable.call(s,h)&&(l[h]=s[h])}return l}function w(s,c){if(s==null)return{};var l={},h=Object.keys(s),y,x;for(x=0;x<h.length;x++)y=h[x],!(c.indexOf(y)>=0)&&(l[y]=s[y]);return l}var b=r.default.forwardRef(function(s,c){var l=s.variant,h=s.color,y=s.shadow,x=s.blurred,j=s.fullWidth,k=s.className,N=s.children,A=C(s,["variant","color","shadow","blurred","fullWidth","className","children"]),M=(0,f.useTheme)().navbar,D=M.defaultProps,L=M.valid,z=M.styles,V=z.base,Y=z.variants;l=l??D.variant,h=h??D.color,y=y??D.shadow,x=x??D.blurred,j=j??D.fullWidth,k=k??D.className;var q,B=(0,o.default)((0,u.default)(V.navbar.initial),(q={},S(q,(0,u.default)(V.navbar.shadow),y),S(q,(0,u.default)(V.navbar.blurred),x&&h==="white"),S(q,(0,u.default)(V.navbar.fullWidth),j),q)),H=(0,o.default)((0,u.default)(Y[(0,i.default)(L.variants,l,"filled")][(0,i.default)(L.colors,h,"white")])),K=(0,a.twMerge)((0,o.default)(B,H),k);return r.default.createElement("nav",O({},A,{ref:c,className:K}),N)});b.propTypes={variant:n.default.oneOf(m.propTypesVariant),color:n.default.oneOf(m.propTypesColor),shadow:m.propTypesShadow,blurred:m.propTypesBlurred,fullWidth:m.propTypesFullWidth,className:m.propTypesClassName,children:m.propTypesChildren},b.displayName="MaterialTailwind.Navbar";var d=Object.assign(b,{MobileNav:g.MobileNav})})(ek);var rk={},Hf={},Ls={};(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(b,d){for(var s in d)Object.defineProperty(b,s,{enumerable:!0,get:d[s]})}t(e,{propTypesOpen:function(){return a},propTypesHandler:function(){return i},propTypesPlacement:function(){return u},propTypesOffset:function(){return f},propTypesDismiss:function(){return g},propTypesAnimate:function(){return m},propTypesContent:function(){return S},propTypesInteractive:function(){return O},propTypesClassName:function(){return T},propTypesChildren:function(){return C},propTypesContextValue:function(){return w}});var r=o(ut),n=Zt;function o(b){return b&&b.__esModule?b:{default:b}}var a=r.default.bool,i=r.default.func,u=n.propTypesPlacements,f=n.propTypesOffsetType,g=n.propTypesDismissType,m=n.propTypesAnimation,S=r.default.node,O=r.default.bool,T=r.default.string,C=r.default.node.isRequired,w=r.default.shape({open:r.default.bool.isRequired,strategy:r.default.oneOf(["fixed","absolute"]).isRequired,x:r.default.number,y:r.default.number,context:r.default.instanceOf(Object).isRequired,reference:r.default.func.isRequired,floating:r.default.func.isRequired,getReferenceProps:r.default.func.isRequired,getFloatingProps:r.default.func.isRequired,appliedAnimation:m.isRequired,labelId:r.default.string.isRequired,descriptionId:r.default.string.isRequired}).isRequired})(Ls);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(f,g){for(var m in g)Object.defineProperty(f,m,{enumerable:!0,get:g[m]})}t(e,{PopoverContext:function(){return a},usePopover:function(){return i},PopoverContextProvider:function(){return u}});var r=o(P),n=Ls;function o(f){return f&&f.__esModule?f:{default:f}}var a=r.default.createContext(null);a.displayName="MaterialTailwind.PopoverContext";function i(){var f=r.default.useContext(a);if(!f)throw new Error("usePopover() must be used within a Popover. It happens when you us Line truncated
${se?"border-purple-500 ring-1 ring-purple-500/40":"border-transparent"}
${!xe||ie?"opacity-75 bg-stormGrey-200/20":""}`,children:[v.jsxs("div",{className:"flex items-center space-x-4",id:"left",children:[k&&v.jsx("input",{type:"checkbox",checked:se,onChange:()=>Ee(oe.Id,!Ue),className:`h-5 w-5 text-purple-500 focus:ring-purple-400 border-gray-600 bg-gray-900 rounded ${Ue?"":"opacity-40 cursor-not-allowed"}`,disabled:!Ue}),v.jsx("div",{className:`w-12 h-12 flex items-center justify-center rounded-full
${oe.updateStatus==="✅"?"bg-mossGreen-600":oe.updateStatus==="⚠️"?"bg-warmAmberGlow-400":"bg-sunsetCoral-500"}`}),v.jsxs("div",{children:[v.jsx("p",{className:"text-lg antialiased font-sans font-semibold leading-normal text-inherit",children:oe.Name}),v.jsxs("p",{className:"text-sm antialiased font-sans font-light leading-normal text-inherit",children:["ID: ",oe.Id]}),oe.duplicateName&&v.jsx("p",{className:"text-xs text-amber-700",children:"⚠️ Doppelter Name erkannt"})]})]}),v.jsx("div",{className:"ml-auto flex flex-col items-end gap-1 text-sm",id:"right",children:ye?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),v.jsx("span",{className:"text-warmAmberGlow-500",children:"läuft…"})]}):te?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),v.jsx("span",{className:"text-warmAmberGlow-500",children:"in Warteliste…"})]}):Oe?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"System"}),v.jsx("span",{className:"text-gray-300",children:"Redeploy deaktiviert"})]}):fe?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"Status"}),v.jsx("span",{className:"text-mossGreen-600",children:"Aktuell"})]}):j?v.jsx(W.Button,{onClick:()=>qe(oe.Id),disabled:L||ie,className:"disabled:opacity-50 disabled:cursor-not-allowed",children:"Redeploy"}):null})]})},oe.Id)})}),v.jsx(ju,{disabled:r||!!o})]})}const tV={initializing:"Vorbereitung","activating-maintenance":"Wartungsmodus aktivieren","executing-script":"Skript wird ausgeführt",waiting:"Warte auf Portainer",completed:"Abgeschlossen",failed:"Fehlgeschlagen"},_c=e=>{if(!e&&e!==0)return"-";const r=(n=>{if(n instanceof Date)return n;if(typeof n=="number"){const o=n>1e12?n:n*1e3;return new Date(o)}if(typeof n=="string"){const o=Number(n);if(!Number.isNaN(o)){const a=o>1e12?o:o*1e3;return new Date(a)}return new Date(n)}return null})(e);return!r||Number.isNaN(r.getTime())?String(e):r.toLocaleString("de-DE",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"})},Mh=()=>({host:"",port:"22",username:"",password:"",extraSshArgs:""});function rV(){var Gs,Iu;const e=$n(),{hasPermission:t,user:r}=Wr(),n=P.useCallback(()=>{},[]),{maintenance:o,update:a,script:i,ssh:u,loading:f,error:g,setMaintenanceMode:m,triggerUpdate:S,saveScript:O,resetScript:T,saveSshConfig:C,deleteSshConfig:w,testSshConnection:b,fetchSuperuserStatus:d,fetchSetupStatus:s,deleteSetupServer:c,updateSetupApiKey:l,updateSelfStackId:h,removeSuperuserAccount:y}=ea(),x=!!(r!=null&&r.isSuperuser),j=!!(x||t("maintenance-access","read")),k=!!(x||t("maintenance-access","full")),N=!!(x||t("maintenance-server-manage","read")),A=!!(x||t("maintenance-server-edit","full")),M=!!(x||t("maintenance-server-delete","full")),D=!!(x||t("maintenance-superuser-delete","full"));x||t("maintenance-portainer","read");const L=!!(x||t("maintenance-update","full"));x||t("maintenance-ssh-update","read");const z=!!(x||t("maintenance-ssh-update","full"));x||t("maintenance-duplicates","read");const V=!!(x||t("maintenance-duplicates","full")),Y=!1,q=!1,B=!!(o!=null&&o.active),H=o==null?void 0:o.message,K=(Gs=o==null?void 0:o.extra)==null?void 0:Gs.type,X=!!(a!=null&&a.running),re=B||X,[me,de]=P.useState(""),[ee,_e]=P.useState(!1),[he,pe]=P.useState(!1),[be,ve]=P.useState(""),[Ce,Ae]=P.useState(!1),[Ne,ue]=P.useState(()=>Mh()),[Ee,$e]=P.useState(!1),[Ge,tt]=P.useState(!1),[ct,qe]=P.useState(!1),[it,mt]=P.useState(!1),[We,rt]=P.useState(!1),[yt,je]=P.useState(null);P.useEffect(()=>{if(!i)return;const Pe=i.custom??i.default??i.effective??"";de(Pe)},[i]),P.useEffect(()=>{if(!u){ue(Mh()),$e(!1),tt(!1),je(null);return}ue({host:u.host??"",port:String(u.port??"22"),username:u.username??"",password:"",extraSshArgs:Array.isArray(u.extraSshArgs)?u.extraSshArgs.join(`
`):""}),$e(!!u.passwordStored),tt(!1),je(null)},[u]),P.useMemo(()=>i?i.source==="custom"&&typeof i.custom=="string"?i.custom:i.default??i.effective??"":"",[i]),i==null||i.source;const[Ke,Te]=P.useState(!0),[oe,ge]=P.useState(!1),[te,ye]=P.useState(null),[ie,se]=P.useState(""),[fe,Oe]=P.useState(!1),[xe,Ue]=P.useState(null),[Fe,Qe]=P.useState(!0),[ke,bt]=P.useState(""),[ft,At]=P.useState(""),[It,xt]=P.useState(""),[Rt,Er]=P.useState(""),[ur,$r]=P.useState(""),[rr,Hn]=P.useState(!1),[Ir,_r]=P.useState({}),[cn,dn]=P.useState(null),[J,Re]=P.useState(null),[ze,Ut]=P.useState(""),[Nt,Et]=P.useState(!1),[Ht,qt]=P.useState(""),[Pt,Xr]=P.useState([]),[mr,dr]=P.useState(!0),[On,kn]=P.useState(""),[Sr,Nn]=P.useState(!1),[ta,fn]=P.useState(null),[bo,qn]=P.useState(null),Ie=P.useRef(null),[ot,dt]=P.useState(new Map),[Xe,wt]=P.useState(!1),[Dt,nr]=P.useState(""),Lt=P.useMemo(()=>{var Pe;return((Pe=xe==null?void 0:xe.servers)==null?void 0:Pe.items)??[]},[xe]);P.useMemo(()=>{var Ve;const Pe=((Ve=xe==null?void 0:xe.apiKeys)==null?void 0:Ve.items)??[];return new Map(Pe.map(Be=>[Be.serverId,Be]))},[xe]),P.useMemo(()=>!!(xe!=null&&xe.setupComplete),[xe]);const Zr=(xe==null?void 0:xe.selfStack)??null,pn=(Zr==null?void 0:Zr.current)??"";(Iu=xe==null?void 0:xe.envDefaults)==null||Iu.selfStackId;const{page:hn,perPage:vo,perPageOptions:Ti,perPageIsAll:Wa,handlePerPageChange:Vs,setPage:Au,setTotals:Us,resetPagination:Ru}=Bs();P.useEffect(()=>()=>Ru(),[Ru]);const[Pi,Qf]=P.useState(""),[yo,Mo]=P.useState(null),[Ws,$s]=P.useState(!0),[Xf,Hs]=P.useState(""),[Zf,Oi]=P.useState(!1),[Jf,ep]=P.useState(null),$a=P.useRef(null),Gn=P.useCallback(async({silent:Pe=!1}={})=>{var Ve,Be;Te(!0),se("");try{const Le=await d(),at=!!(Le!=null&&Le.exists);ge(at),ye(at?Le.user??null:null)}catch(Le){const at=((Be=(Ve=Le.response)==null?void 0:Ve.data)==null?void 0:Be.error)||Le.message||"Superuser-Status konnte nicht geladen werden";ge(!1),ye(null),se(at),Pe||n({variant:"error",title:"Statusabfrage fehlgeschlagen",description:at})}finally{Te(!1)}},[d,n]),Jr=P.useCallback(async({silent:Pe=!1}={})=>{var Ve,Be;Pe||Qe(!0),bt("");try{const Le=await s();Ue(Le),_r({})}catch(Le){const at=((Be=(Ve=Le.response)==null?void 0:Ve.data)==null?void 0:Be.error)||Le.message||"Setup-Daten konnten nicht geladen werden";bt(at),Pe||n({variant:"error",title:"Setup-Daten",description:at})}finally{Qe(!1)}},[s,n]),qs=P.useCallback(async({silent:Pe=!1}={})=>{if($a.current)return $a.current;const Ve=(async()=>{var Be,Le;Pe?Oi(!0):$s(!0),Hs("");try{const Bt=(await Je.get("/api/maintenance/portainer-status")).data??{};Mo(Bt),ep(new Date)}catch(at){const Bt=((Le=(Be=at.response)==null?void 0:Be.data)==null?void 0:Le.error)||at.message||"Fehler beim Prüfen des Portainer-Status";Hs(Bt),n({variant:"error",title:"Statusaktualisierung fehlgeschlagen",description:Bt})}finally{Pe?Oi(!1):$s(!1),$a.current=null}})();return $a.current=Ve,Ve},[n]),ra=P.useCallback(async({silent:Pe=!1}={})=>{if(B||X){Xr([]),kn("Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert."),dr(!1),Nn(!1);return}if(Ie.current)return Ie.current;const Ve=(async()=>{var Be,Le,at;Pe?Nn(!0):dr(!0),kn("");try{const Ot=(await Je.get("/api/maintenance/duplicates")).data,br=Array.isArray(Ot)?Ot:(Ot==null?void 0:Ot.items)??[];Xr(br),qn(new Date)}catch(Bt){if(((Be=Bt.response)==null?void 0:Be.status)===423){const Ot="Wartungsmodus aktiv – Duplikat-Verwaltung ist vorübergehend deaktiviert.";Xr([]),kn(Ot),n({variant:"warning",title:"Duplikat-Verwaltung gesperrt",description:Ot})}else{const Ot=((at=(Le=Bt.response)==null?void 0:Le.data)==null?void 0:at.error)||Bt.message||"Fehler beim Laden der Wartungsdaten";kn(Ot),n({variant:"error",title:"Duplikate konnten nicht geladen werden",description:Ot})}}finally{Pe?Nn(!1):dr(!1),Ie.current=null}})();return Ie.current=Ve,Ve},[B,X,n]);P.useEffect(()=>{Gn({silent:!0})},[Gn]),P.useEffect(()=>{Jr()},[Jr]),P.useEffect(()=>{qs()},[qs]),P.useEffect(()=>{ra()},[ra]),P.useEffect(()=>{if(!N||Fe)return;(async()=>{var Ve,Be;if(!Lt.length){dt(new Map);return}wt(!0),nr("");try{const Le=await Promise.all(Lt.map(async at=>{var Bt,Ot;try{const Cr=(await Je.get(`/api/setup/servers/${at.id}/check`)).data??{};return[at.id,{online:!!Cr.online,portainer:Cr.portainer??{},error:null}]}catch(br){const Cr=((Ot=(Bt=br.response)==null?void 0:Bt.data)==null?void 0:Ot.error)||br.message||"Status unbekannt";return[at.id,{online:!1,portainer:{},error:Cr}]}}));dt(new Map(Le))}catch(Le){const at=((Be=(Ve=Le.response)==null?void 0:Ve.data)==null?void 0:Be.error)||Le.message||"Server-Status konnte nicht geladen werden.";nr(at)}finally{wt(!1)}})()},[N,Fe,Lt,Us]);const Kn=P.useMemo(()=>{const Pe=Pi.trim().toLowerCase();return Pe?Lt.filter(Ve=>[Ve.name,Ve.url].map(Le=>String(Le||"").toLowerCase()).some(Le=>Le.includes(Pe))):Lt},[Pi,Lt]);P.useEffect(()=>{Us(Kn.length)},[Kn.length,Us]);const Ha=P.useMemo(()=>{if(Wa)return Kn;const Pe=(hn-1)*vo;return Kn.slice(Pe,Pe+vo)},[Kn,hn,vo,Wa]);P.useEffect(()=>{Ut(pn Line truncated
Dies setzt die Superuser-Einrichtung zurück.`)))try{Oe(!0);const Le=await y(),at=Number((Le==null?void 0:Le.usersRemoved)??0);n({variant:"success",title:"Superuser gelöscht",description:at>1?`${at} Konten aus der Superuser-Gruppe wurden entfernt.`:"Superuser-Konto wurde entfernt."}),await Gn({silent:!0})}catch(Le){if(((Pe=Le.response)==null?void 0:Pe.status)===404)await Gn({silent:!0}),n({variant:"info",title:"Kein Superuser vorhanden",description:"Es ist kein Superuser mehr vorhanden."});else{const Bt=((Be=(Ve=Le.response)==null?void 0:Ve.data)==null?void 0:Be.error)||Le.message||"Superuser konnte nicht gelöscht werden";n({variant:"error",title:"Löschen fehlgeschlagen",description:Bt})}}finally{Oe(!1)}},[D,fe,Ke,oe,y,Gn,n]),ap=P.useCallback(async Pe=>{var at,Bt;if(!M||J===Pe)return;const Ve=Lt.find(Ot=>Ot.id===Pe);if(!Ve)return;const Be=Ve.name||Ve.url||`Server ${Pe}`,Le=[`Server "${Be}" wirklich löschen?`,"Das System benötigt anschließend erneut einen gültigen Server im Setup."].filter(Boolean).join(`
`);if(!(typeof window<"u"&&!window.confirm(Le))){Re(Pe);try{await c(Pe),n({variant:"success",title:"Server gelöscht",description:`Server "${Be}" wurde entfernt.`}),await Jr({silent:!0})}catch(Ot){const br=((Bt=(at=Ot.response)==null?void 0:at.data)==null?void 0:Bt.error)||Ot.message||"Server konnte nicht gelöscht werden.";n({variant:"error",title:"Server löschen fehlgeschlagen",description:br})}finally{Re(null)}}},[M,c,Jr,J,Lt,n]);P.useCallback((Pe,Ve)=>{A&&_r(Be=>({...Be,[Pe]:Ve}))},[A]),P.useCallback(async Pe=>{var Be,Le;if(!A)return;const Ve=(Ir[Pe]??"").trim();if(!Ve){n({variant:"warning",title:"API-Key fehlt",description:"Bitte gib einen API-Key ein."});return}if(cn!==Pe){dn(Pe);try{await l(Pe,Ve),n({variant:"success",title:"API-Key aktualisiert",description:"Der API-Key wurde gespeichert."}),_r(at=>({...at,[Pe]:""})),await Jr({silent:!0})}catch(at){const Bt=((Le=(Be=at.response)==null?void 0:Be.data)==null?void 0:Le.error)||at.message||"API-Key konnte nicht aktualisiert werden.";n({variant:"error",title:"Aktualisierung fehlgeschlagen",description:Bt})}finally{dn(null)}}},[Ir,cn,A,Jr,n,l]),P.useCallback(Pe=>{A&&(Ut(Pe),qt(""))},[A]),P.useCallback(async()=>{var Ve,Be;if(!A||!Mu)return;Et(!0),qt("");const Pe=typeof ze=="string"?ze.trim():"";try{await h(Pe),n({variant:"success",title:"Self-Stack-ID gespeichert",description:Pe?`Self-Stack-ID wurde auf "${Pe}" gesetzt.`:"Self-Stack-ID wurde entfernt."}),await Jr({silent:!0})}catch(Le){const at=((Be=(Ve=Le.response)==null?void 0:Ve.data)==null?void 0:Be.error)||Le.message||"Self-Stack-ID konnte nicht aktualisiert werden.";qt(at),n({variant:"error",title:"Self-Stack-ID",description:at})}finally{Et(!1)}},[A,Jr,Mu,ze,n,h]),P.useCallback(async()=>{var Pe,Ve;if(A&&!(!ze&&!pn)&&!(typeof window<"u"&&!window.confirm("Self-Stack-ID wirklich entfernen?"))){Et(!0),qt("");try{await h(""),Ut(""),n({variant:"success",title:"Self-Stack-ID entfernt",description:"Die Self-Stack-ID wurde gelöscht."}),await Jr({silent:!0})}catch(Be){const Le=((Ve=(Pe=Be.response)==null?void 0:Pe.data)==null?void 0:Ve.error)||Be.message||"Self-Stack-ID konnte nicht entfernt werden.";qt(Le),n({variant:"error",title:"Self-Stack-ID",description:Le})}finally{Et(!1)}}},[A,pn,Jr,ze,n,h]);const ip=P.useCallback(async Pe=>{var Ve,Be;if(k&&!(f||Ce)&&Pe!==B){if(X){n({variant:"warning",title:"Aktion nicht möglich",description:"Während eines laufenden Updates kann der Wartungsmodus nicht geändert werden."});return}Ae(!0);try{const Le=Pe&&!!K,at={active:Pe};Le&&H?at.message=H:at.message=null,await m(at),n({variant:Pe?"info":"success",title:Pe?"Wartungsmodus aktiviert":"Wartungsmodus deaktiviert",description:Pe?"Benutzer sehen jetzt die Wartungsseite.":"StackPulse steht wieder zur Verfügung."})}catch(Le){const at=((Be=(Ve=Le.response)==null?void 0:Ve.data)==null?void 0:Be.error)||Le.message||"Wartungsmodus konnte nicht geändert werden";n({variant:"error",title:"Änderung fehlgeschlagen",description:at})}finally{Ae(!1)}}},[k,f,Ce,B,X,m,H,K,n]);P.useCallback(async()=>{var Ve,Be;if(!L)return;if(B||X){n({variant:"warning",title:"Update nicht möglich",description:"Während eines aktiven Wartungsmodus kann kein weiteres Update gestartet werden."});return}const Pe=(yo==null?void 0:yo.latestVersion)??(yo==null?void 0:yo.currentVersion)??"unbekannt";if(!(typeof window<"u"&&!window.confirm(`Portainer-Update starten?
Zielversion: ${Pe}.
Während des Updates befindet sich StackPulse im Wartungsmodus.
Fortfahren?`)))try{ve(""),pe(!0),await S(),n({variant:"info",title:"Update gestartet",description:"Das Portainer-Update wurde gestartet."})}catch(Le){const at=((Be=(Ve=Le.response)==null?void 0:Ve.data)==null?void 0:Be.error)||Le.message||"Portainer-Update konnte nicht gestartet werden";ve(at),n({variant:"error",title:"Update fehlgeschlagen",description:at})}finally{pe(!1)}},[L,B,X,yo,S,n]),P.useCallback(async Pe=>{var at,Bt,Ot,br;if(!V||!Pe)return;if(B||X){n({variant:"warning",title:"Aktion nicht möglich",description:"Während des Wartungsmodus sind Bereinigungen deaktiviert."});return}const Ve=(at=Pe.canonical)==null?void 0:at.Id;if(!Ve)return;const Be=(Pe.duplicates||[]).map(Cr=>Cr.Id).filter(Boolean);if(!Be.length)return;const Le=((Bt=Pe.canonical)==null?void 0:Bt.Name)||Pe.name||`Stack ${Ve}`;if(!(typeof window<"u"&&!window.confirm(`Bereinigung für "${Le}" starten?
Es werden ${Be.length} Duplikate entfernt: ${Be.join(", ")}`))){fn(String(Ve));try{const Io=(await Je.post("/api/maintenance/duplicates/cleanup",{canonicalId:Ve,duplicateIds:Be})).data??{};if(Io.success===!1)throw new Error(Io.error||"Bereinigung fehlgeschlagen");const Du=Array.isArray(Io.results)?Io.results.filter(Yn=>Yn.status==="deleted").map(Yn=>Yn.id):Be;n({variant:"success",title:"Bereinigung abgeschlossen",description:`${Le} – entfernte IDs: ${Du.join(", ")}`}),await ra({silent:!0})}catch(Cr){const Io=((br=(Ot=Cr.response)==null?void 0:Ot.data)==null?void 0:br.error)||Cr.message||"Bereinigung fehlgeschlagen";n({variant:"error",title:"Bereinigung fehlgeschlagen",description:Io})}finally{fn(null)}}},[V,ra,B,n,X]),o!=null&&o.activatedAt&&_c(o.activatedAt),o!=null&&o.updatedAt&&_c(o.updatedAt),a==null||a.status;const xo=a!=null&&a.stage?tV[a.stage]??a.stage:"–";return a==null||a.logs,a==null||a.targetVersion,a==null||a.resultVersion,a!=null&&a.startedAt&&_c(a.startedAt),a!=null&&a.finishedAt&&_c(a.finishedAt),a==null||a.message,j?v.jsxs("div",{className:"mt-12 mb-8 flex flex-col gap-12",children:[(B||X)&&v.jsx("div",{className:"rounded-lg border border-cyan-500/60 bg-cyan-900/30 px-4 py-3 text-sm text-bluegray-100",children:v.jsxs("div",{className:"flex flex-col gap-1",children:[v.jsxs("span",{children:["Wartungsmodus aktiv",H?` – ${H}`:X?" – Portainer-Update läuft":"","."]}),X&&v.jsxs("span",{className:"text-xs text-indigo-900",children:["Phase: ",xo]})]})}),j&&v.jsxs(W.Card,{children:[v.jsx(W.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:v.jsx(W.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:v.jsx("span",{children:"Wartungsmodus"})})}),v.jsxs(W.CardBody,{className:"flex flex-col gap-4 p-4",children:[v.jsxs("div",{className:"flex items-start justify-between gap-4",children:[v.jsx("div",{className:"space-y-1",children:v.jsx("p",{className:"text-sm antialiased font-sans text-sm font-light leading-normal text-inherit",children:"Schaltet StackPulse für Benutzer in den Wartungsmodus."})}),v.jsx(W.Switch,{checked:B,disabled:!k||Ce||f||X,onChange:Pe=>ip(Pe.target.checked),ripple:!1,color:"amber"})]}),Ce&&v.jsx("p",{className:"text-xs text-stormGrey-500",children:"Wartungsmodus wird aktualisiert…"})]})]}),N&&v.jsxs(W.Card,{children:[v.jsx(W.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:v.jsxs(W.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:[v.jsx("span",{children:"Server"}),v.jsxs(W.Typography,{variant:"small",color:"white",children:[Lt.length," vorhanden"]})]})}),v.jsxs(W.CardBody,{className:"p-4",children:[A&&v.jsxs("div",{className:"mb-8 rounded-lg border border-blue-gray-50 p-4",children:[v.jsx(W.Typography,{variant:"h6",color:"blue-gray",className:"mb-2",children:"Neuen Server hinzufügen"}),v.jsx("p",{className:"text-sm text-stormGrey-600 mb-4",children:"Hinterlege deine Portainer-Instanz. Die Verbindung wird beim Speichern automatisch geprüft."}),v.jsxs("div",{className:"grid gap-4 md:grid-cols-3",children:[v.jsx(W.Input,{label:"Servername",value:ft,onChange:Pe=>At(Pe.target.value),disabled:rr||re,crossOrigin:""}),v.jsx(W.Input,{label:"Server-URL oder IP",value:It,onChange:Pe=>xt(Pe.target.value),disabled:rr||re,crossOrigin:""}),v.jsx(W.Input,{type:"password",label:"Portainer API-Key",value:Rt,onChange:Pe=>Er(Pe.target.value),disabled:rr||re,crossOrigin:""})]}),v.jsxs("div",{className:"mt-4 flex flex-col gap-3 sm:flex-row sm:items-center",children:[v.jsx(W.Button,{color:"green",onClick:rp,disabled:rr||re,children:rr?"Verifiziere …":"Server anlegen"}),v.jsx(W.Button,{variant:"text",color:"blue-gray",onClick:np,disabled:rr,children:"Formular zurücksetzen"})]}),ur&&v.jsx("p",{className:"mt-2 text-sm text-sunsetCoral-600",children:ur})]}),v.jsxs("div",{className:"flex flex-col gap-4 md:flex-row md:items-center md:justify-between",children:[v.jsx("div",{className:"md:flex-1",children:v.jsx(W.Input,{label:"Suchen nach Name oder URL",value:Pi,onChange:tp,crossOrigin:""})}),v.jsx("div",{className:"md:mt-0 mt-4 md:flex-1",children:v.jsx(W.Select,{variant:"static",label:"Einträge pro Seite",onChange:Vs,value:vo,children:Ti.map(({value:Pe,label:Ve})=>v.jsx(W.Option,{value:Pe,children:Ve},Pe))})})]}),ke&&v.jsx("div",{className:"mt-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800",children:ke}),Dt&&!Fe&&v.jsx("div",{className:"mt-4 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800",children:Dt}),v.jsx("div",{className:"overflow-x-auto rounded-lg border border-blue-gray-50 mt-6",children:v.jsxs("table",{className:"w-full min-w-[720px] table-auto text-left",children:[v.jsx("thead",{children:v.jsxs("tr",{className:"bg-blue-gray-50/50 text-xs uppercase tracking-wide text-stormGrey-400",children:[v.jsx("th",{className:"px-6 py-4 font-semibold",children:"Name"}),v.jsx("th",{className:"px-6 py-4 font-s Line truncated
`)},TV=(e=[])=>{if(!Array.isArray(e))return[];const t=[];for(let r=0;r<e.length;r+=4)t.push(e.slice(r,r+4));return t};function PV(){const e=$n(),{user:t,setSession:r,refreshSession:n}=Wr(),{maintenance:o,update:a}=ea(),[i,u]=P.useState(!0),[f,g]=P.useState(""),[m,S]=P.useState([]),[O,T]=P.useState(!1),[C,w]=P.useState(!1),b=!!(t!=null&&t.requiresSecurityPhraseDownload),d=!!(o!=null&&o.active);o==null||o.message;const s=!!(a!=null&&a.running);a!=null&&a.stage&&(SV[a.stage]??a.stage);const c=d||s,l=P.useMemo(()=>TV(m),[m]),h=P.useCallback(k=>{if(!k)return;const N=new Blob([k],{type:"text/plain;charset=utf-8"}),A=URL.createObjectURL(N),M=document.createElement("a");M.href=A;const D=t!=null&&t.username?`${t.username}-sicherheitsschluessel`:"security-phrase";M.download=`${D}.txt`,document.body.appendChild(M),M.click(),document.body.removeChild(M),URL.revokeObjectURL(A)},[t==null?void 0:t.username]),y=P.useCallback(async()=>{var k,N,A,M,D,L,z,V;u(!0),g("");try{const Y=await Je.get("/api/users/me/security-phrase"),q=Array.isArray((N=(k=Y.data)==null?void 0:k.item)==null?void 0:N.words)?Y.data.item.words:[];if(!q.length)throw new Error("EMPTY_SECURITY_PHRASE");S(q),w(!1)}catch(Y){if(((A=Y==null?void 0:Y.response)==null?void 0:A.status)===409||((D=(M=Y==null?void 0:Y.response)==null?void 0:M.data)==null?void 0:D.error)==="SECURITY_PHRASE_ALREADY_DOWNLOADED"){await n(),e("/dashboard/stacks",{replace:!0});return}if(((L=Y==null?void 0:Y.response)==null?void 0:L.status)===401){await n(),e("/auth/sign-in",{replace:!0});return}const q=((V=(z=Y==null?void 0:Y.response)==null?void 0:z.data)==null?void 0:V.error)||(Y==null?void 0:Y.message)||"Sicherheitsschlüssel konnte nicht geladen werden.";g(q)}finally{u(!1)}},[e,n]);P.useEffect(()=>{if(!t){e("/auth/sign-in",{replace:!0});return}if(!b){e("/dashboard/stacks",{replace:!0});return}y()},[t,b,y,e]);const x=P.useCallback(()=>{y()},[y]),j=P.useCallback(async()=>{var k,N,A,M,D,L;if(!(O||!Array.isArray(m)||m.length===0)){T(!0),g("");try{const z=CV(m);h(z);const V=await Je.post("/api/users/me/security-phrase/downloaded"),Y=((N=(k=V.data)==null?void 0:k.item)==null?void 0:N.downloadedAt)||null,q=(A=V.data)==null?void 0:A.session;q!=null&&q.user?r(q):await n(),w(!!Y),e("/dashboard/stacks",{replace:!0})}catch(z){if(((M=z==null?void 0:z.response)==null?void 0:M.status)===401){await n(),e("/auth/sign-in",{replace:!0});return}const V=((L=(D=z==null?void 0:z.response)==null?void 0:D.data)==null?void 0:L.error)||(z==null?void 0:z.message)||"Download konnte nicht bestätigt werden.";g(V)}finally{T(!1)}}},[m,O,h,n,e,r]);return b?v.jsxs("div",{className:"flex min-h-screen flex-col items-center justify-center bg-blue-gray-50/50 px-4 py-10",children:[v.jsxs("div",{className:"mb-6 text-center",children:[v.jsx(W.Typography,{variant:"h2",className:"font-semibold text-blue-gray-800",children:"Sicherheitsschlüssel sichern"}),v.jsx(W.Typography,{color:"blue-gray",className:"mt-2 max-w-3xl text-base",children:"Bitte lade den einmaligen Sicherheitsschlüssel herunter und bewahre ihn sicher auf. Er wird benötigt, um dein Passwort zurückzusetzen. Ohne den Download kannst du die Anwendung nicht weiter nutzen."})]}),v.jsx(W.Card,{className:"w-full max-w-3xl shadow-lg",children:v.jsxs(W.CardBody,{className:"space-y-6",children:[i&&v.jsx("div",{className:"flex items-center justify-center py-10",children:v.jsx(W.Spinner,{className:"h-10 w-10 text-blue-gray-500"})}),!i&&f&&v.jsx(W.Alert,{color:"red",className:"border border-red-200 bg-red-50 text-red-700",icon:v.jsx(zz,{className:"h-5 w-5"}),children:v.jsxs("div",{className:"flex items-start justify-between gap-4",children:[v.jsxs("div",{children:[v.jsx(W.Typography,{variant:"h6",color:"red",children:"Fehler"}),v.jsx(W.Typography,{color:"red",className:"text-sm",children:f})]}),v.jsx(W.Button,{size:"sm",color:"red",variant:"text",onClick:x,className:"normal-case",children:"Erneut versuchen"})]})}),!i&&!f&&v.jsxs(v.Fragment,{children:[v.jsxs("div",{className:"rounded-lg border border-blue-gray-100 bg-blue-gray-50/60 p-6",children:[v.jsx(W.Typography,{variant:"h5",className:"mb-4 text-blue-gray-800",children:"Deine Sicherheitsphrase"}),v.jsx("div",{className:"space-y-3",children:l.map((k,N)=>v.jsx("div",{className:"flex flex-wrap items-center justify-center gap-4 text-lg font-mono tracking-wide text-blue-gray-900 md:text-xl",children:k.map((A,M)=>v.jsx("span",{className:"uppercase",children:A},`phrase-${N}-${M}`))},`phrase-row-${N}`))})]}),v.jsxs("div",{className:"flex flex-col items-center justify-between gap-4 md:flex-row",children:[v.jsx("div",{className:"text-sm text-blue-gray-600",children:"Der Download startet im TXT-Format. Bewahre die Datei sicher auf – sie kann später nicht erneut angezeigt werden."}),v.jsxs(W.Button,{color:"blue",size:"md",className:"flex items-center gap-2 normal-case",onClick:j,disabled:c||O||C||m.length===0,children:[v.jsx(Dz,{className:"h-5 w-5"}),"Sicherheitsschlüssel herunterladen"]})]})]})]})})]}):null}const Lh=e=>{if(! Line truncated
`):""}),Te(!!We.passwordStored),ge(!1),Ue(null)},[We]);const Er=P.useMemo(()=>Ce?Ce.source==="custom"&&typeof Ce.custom=="string"?Ce.custom:Ce.default??Ce.effective??"":"",[Ce]),ur=Ce?me!==Er:!1,$r=(Ce==null?void 0:Ce.source)==="custom"?"Benutzerdefiniert":"Standard",rr=C?(N.name||"").trim()!==(C.name||"")||(N.url||"").trim()!==(C.url||""):!1,Hn=P.useMemo(()=>{if(!(C!=null&&C.url))return"-";try{return new URL(C.url).hostname||C.url}catch{return C.url}},[C]),Ir=((Nn=V==null?void 0:V.portainer)==null?void 0:Nn.edition)??"-",_r=(Ir||"").toLowerCase().includes("community"),cn=((ta=V==null?void 0:V.agent)==null?void 0:ta.online)??(Ne==null?void 0:Ne.online)??null,dn=cn===!0?"Agent online":cn===!1?"Agent offline":"Unbekannt",J=P.useMemo(()=>{const Ie={host:yt.host.trim(),port:Number.parseInt(yt.port,10)||22,username:yt.username.trim(),extraSshArgs:(yt.extraSshArgs||"").split(/\r?\n/).map(dt=>dt.trim()).filter(Boolean)},ot=yt.password??"";return ot?Ie.password=ot:Ke||(Ie.password=""),Ie},[yt,Ke]),Re=P.useCallback((Ie,ot)=>{m&&A(dt=>({...dt,[Ie]:ot}))},[m]),ze=P.useCallback(async()=>{var Ie,ot,dt,Xe;if(!(!m||!r||!rr)){x(!0);try{await Je.put(`/api/setup/servers/${r}`,{name:((Ie=N.name)==null?void 0:Ie.trim())??"",url:((ot=N.url)==null?void 0:ot.trim())??""}),u({variant:"success",title:"Server gespeichert",description:"Serverdetails wurden aktualisiert."}),await ft(),await At({silent:!0})}catch(wt){const Dt=((Xe=(dt=wt.response)==null?void 0:dt.data)==null?void 0:Xe.error)||wt.message||"Server konnte nicht aktualisiert werden";u({variant:"error",title:"Speichern fehlgeschlagen",description:Dt})}finally{x(!1)}}},[m,r,rr,N,ft,At,u]),Ut=P.useCallback((Ie,ot)=>{$e(dt=>({...dt,[Ie]:ot}))},[]),Nt=P.useCallback(async()=>{var Ie,ot;if(r){mt(!0);try{await Je.put(`/api/servers/${r}/agent`,{agentUrl:Ee.agentUrl}),u({variant:"success",title:"Agent gespeichert",description:"Agent-Einstellungen wurden aktualisiert."}),await It()}catch(dt){const Xe=((ot=(Ie=dt.response)==null?void 0:Ie.data)==null?void 0:ot.error)||dt.message||"Agent konnte nicht aktualisiert werden";u({variant:"error",title:"Agent speichern fehlgeschlagen",description:Xe})}finally{mt(!1)}}},[r,Ee,It,u]),Et=P.useCallback(async()=>{var ot,dt;if(!m||!r)return;const Ie=(M||"").trim();if(!Ie){u({variant:"warning",title:"API-Key fehlt",description:"Bitte gib einen API-Key ein."});return}z(!0);try{await i(r,Ie),u({variant:"success",title:"API-Key gespeichert",description:"Der neue API-Key wurde hinterlegt."}),D(""),await ft(),await At({silent:!0})}catch(Xe){const wt=((dt=(ot=Xe.response)==null?void 0:ot.data)==null?void 0:dt.error)||Xe.message||"API-Key konnte nicht gespeichert werden";u({variant:"error",title:"Speichern fehlgeschlagen",description:wt})}finally{z(!1)}},[M,m,r,i,ft,At,u]),Ht=P.useCallback(()=>{At()},[At]),qt=P.useCallback(async()=>{var ot,dt;if(!S||!r||j===r)return;const Ie=(C==null?void 0:C.name)||(C==null?void 0:C.url)||`Server ${r}`;if(!(typeof window<"u"&&!window.confirm(`Server "${Ie}" wirklich löschen?`))){k(r);try{await a(r),u({variant:"success",title:"Server gelöscht",description:`Server "${Ie}" wurde entfernt.`}),e("/dashboard/maintenance/servers")}catch(Xe){const wt=((dt=(ot=Xe.response)==null?void 0:ot.data)==null?void 0:dt.error)||Xe.message||"Server konnte nicht gelöscht werden";u({variant:"error",title:"Löschen fehlgeschlagen",description:wt})}finally{k(null)}}},[S,a,e,r,j,C,u]),Pt=P.useCallback(async()=>{var Ie,ot;if(!(!T||!r||!Ce))try{_e(!0),ve(""),await Je.put(`/api/setup/servers/${r}/update-script`,{script:me}),await xt(),u({variant:"success",title:"Skript gespeichert",description:"Das benutzerdefinierte Portainer-Update-Skript wurde aktualisiert."})}catch(dt){const Xe=((ot=(Ie=dt.response)==null?void 0:Ie.data)==null?void 0:ot.error)||dt.message||"Skript konnte nicht gespeichert werden";ve(Xe),u({variant:"error",title:"Speichern fehlgeschlagen",description:Xe})}finally{_e(!1)}},[T,r,Ce,me,xt,u]),Xr=P.useCallback(async()=>{var Ie,ot;if(!(!T||!r))try{_e(!0),ve(""),await Je.delete(`/api/setup/servers/${r}/update-script`),await xt(),u({variant:"info",title:"Standardskript wiederhergestellt",description:"Es wird wieder das Standard-Update-Skript verwendet."})}catch(dt){const Xe=((ot=(Ie=dt.response)==null?void 0:Ie.data)==null?void 0:ot.error)||dt.message||"Standardskript konnte nicht wiederhergestellt werden";ve(Xe),u({variant:"error",title:"Zurücksetzen fehlgeschlagen",description:Xe})}finally{_e(!1)}},[T,r,xt,u]),mr=P.useCallback((Ie,ot)=>{T&&(je(dt=>({...dt,[Ie]:ot})),Ie==="password"&&Te(!1))},[T]),dr=P.useCallback(async()=>{var Ie,ot;if(!(!T||!r))try{ye(!0),await Je.put(`/api/setup/servers/${r}/ssh-config`,J),Ue(null),ge(!1),await Rt(),u({variant:"success",title:"SSH-Konfiguration gespeichert",description:"Verbindungseinstellungen wurden aktualisiert."})}catch(dt){const Xe=((ot=(Ie=dt.response)==null?void 0:Ie.data)==null?void 0:ot.error)||dt.message||"SSH-Konfiguration konnte nicht gespeichert werden";bt(Xe),u({variant:"error",ti Line truncated
-1
View File
@@ -1 +0,0 @@
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#eee}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#bdbdbd}input::placeholder,textarea::placeholder{opacity:1;color:#bdbdbd}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 540px){.container{max-width:540px}}@media (min-width: 720px){.container{max-width:720px}}@media (min-width: 960px){.container{max-width:960px}}@media (min-width: 1140px){.container{max-width:1140px}}@media (min-width: 1320px){.container{max-width:1320px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.\!invisible{visibility:hidden!important}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.\!absolute{position:absolute!important}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bLine truncated
+91
View File
@@ -0,0 +1,91 @@
function HE(e,t){for(var r=0;r<t.length;r++){const n=t[r];if(typeof n!="string"&&!Array.isArray(n)){for(const o in n)if(o!=="default"&&!(o in e)){const a=Object.getOwnPropertyDescriptor(n,o);a&&Object.defineProperty(e,o,a.get?a:{enumerable:!0,get:()=>n[o]})}}}return Object.freeze(Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}))}(function(){const t=document.createElement("link").relList;if(t&&t.supports&&t.supports("modulepreload"))return;for(const o of document.querySelectorAll('link[rel="modulepreload"]'))n(o);new MutationObserver(o=>{for(const a of o)if(a.type==="childList")for(const i of a.addedNodes)i.tagName==="LINK"&&i.rel==="modulepreload"&&n(i)}).observe(document,{childList:!0,subtree:!0});function r(o){const a={};return o.integrity&&(a.integrity=o.integrity),o.referrerPolicy&&(a.referrerPolicy=o.referrerPolicy),o.crossOrigin==="use-credentials"?a.credentials="include":o.crossOrigin==="anonymous"?a.credentials="omit":a.credentials="same-origin",a}function n(o){if(o.ep)return;o.ep=!0;const a=r(o);fetch(o.href,a)}})();function Zw(e){return e&&e.__esModule&&Object.prototype.hasOwnProperty.call(e,"default")?e.default:e}function su(e){if(e.__esModule)return e;var t=e.default;if(typeof t=="function"){var r=function n(){return this instanceof n?Reflect.construct(t,arguments,this.constructor):t.apply(this,arguments)};r.prototype=t.prototype}else r={};return Object.defineProperty(r,"__esModule",{value:!0}),Object.keys(e).forEach(function(n){var o=Object.getOwnPropertyDescriptor(e,n);Object.defineProperty(r,n,o.get?o:{enumerable:!0,get:function(){return e[n]}})}),r}var Jw={exports:{}},Zd={},e_={exports:{}},zt={};/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var lu=Symbol.for("react.element"),GE=Symbol.for("react.portal"),qE=Symbol.for("react.fragment"),KE=Symbol.for("react.strict_mode"),YE=Symbol.for("react.profiler"),QE=Symbol.for("react.provider"),XE=Symbol.for("react.context"),ZE=Symbol.for("react.forward_ref"),JE=Symbol.for("react.suspense"),eA=Symbol.for("react.memo"),tA=Symbol.for("react.lazy"),ay=Symbol.iterator;function rA(e){return e===null||typeof e!="object"?null:(e=ay&&e[ay]||e["@@iterator"],typeof e=="function"?e:null)}var t_={isMounted:function(){return!1},enqueueForceUpdate:function(){},enqueueReplaceState:function(){},enqueueSetState:function(){}},r_=Object.assign,n_={};function ws(e,t,r){this.props=e,this.context=t,this.refs=n_,this.updater=r||t_}ws.prototype.isReactComponent={};ws.prototype.setState=function(e,t){if(typeof e!="object"&&typeof e!="function"&&e!=null)throw Error("setState(...): takes an object of state variables to update or a function which returns an object of state variables.");this.updater.enqueueSetState(this,e,t,"setState")};ws.prototype.forceUpdate=function(e){this.updater.enqueueForceUpdate(this,e,"forceUpdate")};function o_(){}o_.prototype=ws.prototype;function a0(e,t,r){this.props=e,this.context=t,this.refs=n_,this.updater=r||t_}var i0=a0.prototype=new o_;i0.constructor=a0;r_(i0,ws.prototype);i0.isPureReactComponent=!0;var iy=Array.isArray,a_=Object.prototype.hasOwnProperty,s0={current:null},i_={key:!0,ref:!0,__self:!0,__source:!0};function s_(e,t,r){var n,o={},a=null,i=null;if(t!=null)for(n in t.ref!==void 0&&(i=t.ref),t.key!==void 0&&(a=""+t.key),t)a_.call(t,n)&&!i_.hasOwnProperty(n)&&(o[n]=t[n]);var l=arguments.length-2;if(l===1)o.children=r;else if(1<l){for(var f=Array(l),g=0;g<l;g++)f[g]=arguments[g+2];o.children=f}if(e&&e.defaultProps)for(n in l=e.defaultProps,l)o[n]===void 0&&(o[n]=l[n]);return{$$typeof:lu,type:e,key:a,ref:i,props:o,_owner:s0.current}}function nA(e,t){return{$$typeof:lu,type:e.type,key:t,ref:e.ref,props:e.props,_owner:e._owner}}function l0(e){return typeof e=="object"&&e!==null&&e.$$typeof===lu}function oA(e){var t={"=":"=0",":":"=2"};return"$"+e.replace(/[=:]/g,function(r){return t[r]})}var sy=/\/+/g;function Op(e,t){return typeof e=="object"&&e!==null&&e.key!=null?oA(""+e.key):t.toString(36)}function Tc(e,t,r,n,o){var a=typeof e;(a==="undefined"||a==="boolean")&&(e=null);var i=!1;if(e===null)i=!0;else switch(a){case"string":case"number":i=!0;break;case"object":switch(e.$$typeof){case lu:case GE:i=!0}}if(i)return i=e,o=o(i),e=n===""?"."+Op(i,0):n,iy(o)?(r="",e!=null&&(r=e.replace(sy,"$&/")+"/"),Tc(o,t,r,"",function(g){return g})):o!=null&&(l0(o)&&(o=nA(o,r+(!o.key||i&&i.key===o.key?"":(""+o.key).replace(sy,"$&/")+"/")+e)),t.push(o)),1;if(i=0,n=n===""?".":n+":",iy(e))for(var l=0;l<e.length;l++){a=e[l];var f=n+Op(a,l);i+=Tc(a,t,r,f,o)}else if(f=rA(e),typeof f=="function")for(e=f.call(e),l=0;!(a=e.next()).done;)a=a.value,f=n+Op(a,l++),i+=Tc(a,t,r,f,o);else if(a==="object")throw t=String(e),Error("Objects are not valid as a React child (found: "+(t==="[object Object]"?"object with keys {"+Object.keys(e).join(", ")+"}":t)+"). If you meant to render a collection of children, use an array instead.");return i}function Ku(e,t,r){if(e==null)return e;var n=[],o=0;return Tc(e,n,"","",function(a){return t.call(r,a,o++)}),n}function aA(e){if(e._status===-1){var t=e._result;t=t(),t.then(function(r){(e._status===0||e._status===-1)&&(e._status=1,e._result=r)},function(r){(e._status===0||e._status===-1)&&(e._status=2,e._result=r)}),e._status===-1&&(e._status=0,e._result=t)}if(e._status===1)return e._result.default;throw e._result}var rn={current:null},Pc={transition:null},iA={ReactCurrentDispatcher:rn,ReactCurrentBatchConfig:Pc,ReactCurrentOwner:s0};zt.Children={map:Ku,forEach:function(e,t,r){Ku(e,function(){t.apply(this,arguments)},r)},count:function(e){var t=0;return Ku(e,function(){t++}),t},toArray:function(e){return Ku(e,function(t){return t})||[]},only:function(e){if(!l0(e))throw Error("React.Children.only expected to receive a single React element child.");return e}};zt.Component=ws;zt.Fragment=qE;zt.Profiler=YE;zt.PureComponent=a0;zt.StrictMode=KE;zt.Suspense=JE;zt.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED=iA;zt.cloneElement=function(e,t,r){if(e==null)throw Error("React.cloneElement(...): The argument must be a React element, but you passed "+e+".");var n=r_({},e.props),o=e.key,a=e.ref,i=e._owner;if(t!=null){if(t.ref!==void 0&&(a=t.ref,i=s0.current),t.key!==void 0&&(o=""+t.key),e.type&&e.type.defaultProps)var l=e.type.defaultProps;for(f in t)a_.call(t,f)&&!i_.hasOwnProperty(f)&&(n[f]=t[f]===void 0&&l!==void 0?l[f]:t[f])}var f=arguments.length-2;if(f===1)n.children=r;else if(1<f){l=Array(f);for(var g=0;g<f;g++)l[g]=arguments[g+2];n.children=l}return{$$typeof:lu,type:e.type,key:o,ref:a,props:n,_owner:i}};zt.createContext=function(e){return e={$$typeof:XE,_currentValue:e,_currentValue2:e,_threadCount:0,Provider:null,Consumer:null,_defaultValue:null,_globalName:null},e.Provider={$$typeof:QE,_conLine truncated
* @license React
* react-jsx-runtime.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var sA=P,lA=Symbol.for("react.element"),uA=Symbol.for("react.fragment"),cA=Object.prototype.hasOwnProperty,dA=sA.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,fA={key:!0,ref:!0,__self:!0,__source:!0};function l_(e,t,r){var n,o={},a=null,i=null;r!==void 0&&(a=""+r),t.key!==void 0&&(a=""+t.key),t.ref!==void 0&&(i=t.ref);for(n in t)cA.call(t,n)&&!fA.hasOwnProperty(n)&&(o[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps,t)o[n]===void 0&&(o[n]=t[n]);return{$$typeof:lA,type:e,key:a,ref:i,props:o,_owner:dA.current}}Zd.Fragment=uA;Zd.jsx=l_;Zd.jsxs=l_;Jw.exports=Zd;var v=Jw.exports;function u_(e,t){return function(){return e.apply(t,arguments)}}const{toString:pA}=Object.prototype,{getPrototypeOf:c0}=Object,Jd=(e=>t=>{const r=pA.call(t);return e[r]||(e[r]=r.slice(8,-1).toLowerCase())})(Object.create(null)),ho=e=>(e=e.toLowerCase(),t=>Jd(t)===e),ef=e=>t=>typeof t===e,{isArray:_s}=Array,Rl=ef("undefined");function hA(e){return e!==null&&!Rl(e)&&e.constructor!==null&&!Rl(e.constructor)&&wn(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const c_=ho("ArrayBuffer");function gA(e){let t;return typeof ArrayBuffer<"u"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&c_(e.buffer),t}const mA=ef("string"),wn=ef("function"),d_=ef("number"),tf=e=>e!==null&&typeof e=="object",bA=e=>e===!0||e===!1,Oc=e=>{if(Jd(e)!=="object")return!1;const t=c0(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Symbol.toStringTag in e)&&!(Symbol.iterator in e)},vA=ho("Date"),yA=ho("File"),xA=ho("Blob"),wA=ho("FileList"),_A=e=>tf(e)&&wn(e.pipe),SA=e=>{let t;return e&&(typeof FormData=="function"&&e instanceof FormData||wn(e.append)&&((t=Jd(e))==="formdata"||t==="object"&&wn(e.toString)&&e.toString()==="[object FormData]"))},CA=ho("URLSearchParams"),[TA,PA,OA,kA]=["ReadableStream","Request","Response","Headers"].map(ho),NA=e=>e.trim?e.trim():e.replace(/^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g,"");function uu(e,t,{allOwnKeys:r=!1}={}){if(e===null||typeof e>"u")return;let n,o;if(typeof e!="object"&&(e=[e]),_s(e))for(n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else{const a=r?Object.getOwnPropertyNames(e):Object.keys(e),i=a.length;let l;for(n=0;n<i;n++)l=a[n],t.call(null,e[l],l,e)}}function f_(e,t){t=t.toLowerCase();const r=Object.keys(e);let n=r.length,o;for(;n-- >0;)if(o=r[n],t===o.toLowerCase())return o;return null}const Ya=(()=>typeof globalThis<"u"?globalThis:typeof self<"u"?self:typeof window<"u"?window:global)(),p_=e=>!Rl(e)&&e!==Ya;function Th(){const{caseless:e}=p_(this)&&this||{},t={},r=(n,o)=>{const a=e&&f_(t,o)||o;Oc(t[a])&&Oc(n)?t[a]=Th(t[a],n):Oc(n)?t[a]=Th({},n):_s(n)?t[a]=n.slice():t[a]=n};for(let n=0,o=arguments.length;n<o;n++)arguments[n]&&uu(arguments[n],r);return t}const jA=(e,t,r,{allOwnKeys:n}={})=>(uu(t,(o,a)=>{r&&wn(o)?e[a]=u_(o,r):e[a]=o},{allOwnKeys:n}),e),EA=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),AA=(e,t,r,n)=>{e.prototype=Object.create(t.prototype,n),e.prototype.constructor=e,Object.defineProperty(e,"super",{value:t.prototype}),r&&Object.assign(e.prototype,r)},RA=(e,t,r,n)=>{let o,a,i;const l={};if(t=t||{},e==null)return t;do{for(o=Object.getOwnPropertyNames(e),a=o.length;a-- >0;)i=o[a],(!n||n(i,e,t))&&!l[i]&&(t[i]=e[i],l[i]=!0);e=r!==!1&&c0(e)}while(e&&(!r||r(e,t))&&e!==Object.prototype);return t},MA=(e,t,r)=>{e=String(e),(r===void 0||r>e.length)&&(r=e.length),r-=t.length;const n=e.indexOf(t,r);return n!==-1&&n===r},IA=e=>{if(!e)return null;if(_s(e))return e;let t=e.length;if(!d_(t))return null;const r=new Array(t);for(;t-- >0;)r[t]=e[t];return r},DA=(e=>t=>e&&t instanceof e)(typeof Uint8Array<"u"&&c0(Uint8Array)),LA=(e,t)=>{const n=(e&&e[Symbol.iterator]).call(e);let o;for(;(o=n.next())&&!o.done;){const a=o.value;t.call(e,a[0],a[1])}},FA=(e,t)=>{let r;const n=[];for(;(r=e.exec(t))!==null;)n.push(r);return n},zA=ho("HTMLFormElement"),BA=e=>e.toLowerCase().replace(/[-_\s]([a-z\d])(\w*)/g,function(r,n,o){return n.toUpperCase()+o}),ly=(({hasOwnProperty:e})=>(t,r)=>e.call(t,r))(Object.prototype),VA=ho("RegExp"),h_=(e,t)=>{const r=Object.getOwnPropertyDescriptors(e),n={};uu(r,(o,a)=>{let i;(i=t(o,a,e))!==!1&&(n[a]=i||o)}),Object.defineProperties(e,n)},UA=e=>{h_(e,(t,r)=>{if(wn(e)&&["arguments","caller","callee"].indexOf(r)!==-1)return!1;const n=e[r];if(wn(n)){if(t.enumerable=!1,"writable"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error("Can not rewrite read-only method '"+r+"'")})}})},WA=(e,t)=>{const r={},n=o=>{o.forEach(a=>{r[a]=!0})};return _s(e)?n(e):n(String(e).split(t)),r},$A=()=>{},HA=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t,kp="abcdefghijklmnopqrstuvwxyz",uy="0123456789",g_={DIGIT:uy,ALPHA:kp,ALPHA_DIGIT:kp+kp.toUpperCase()+uy},GA=(e=16,t=g_.ALPHA_DIGIT)=>{let r="";const{length:n}=t;for(;e--;)r+=t[Math.random()*n|0];return r};function qA(e){return!!(e&&wn(e.append)&&e[Symbol.toStringTag]==="FormData"&&e[Symbol.iterator])}const KA=e=>{const t=new Array(10),r=(n,o)=>{if(tf(n)){if(t.indexOf(n)>=0)return;if(!("toJSON"in n)){t[o]=n;const a=_Line truncated
`).forEach(function(i){o=i.indexOf(":"),r=i.substring(0,o).trim().toLowerCase(),n=i.substring(o+1).trim(),!(!r||t[r]&&g2[r])&&(r==="set-cookie"?t[r]?t[r].push(n):t[r]=[n]:t[r]=t[r]?t[r]+", "+n:n)}),t},py=Symbol("internals");function Zs(e){return e&&String(e).trim().toLowerCase()}function kc(e){return e===!1||e==null?e:Se.isArray(e)?e.map(kc):String(e)}function b2(e){const t=Object.create(null),r=/([^\s,;=]+)\s*(?:=\s*([^,;]+))?/g;let n;for(;n=r.exec(e);)t[n[1]]=n[2];return t}const v2=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function Np(e,t,r,n,o){if(Se.isFunction(n))return n.call(this,t,r);if(o&&(t=r),!!Se.isString(t)){if(Se.isString(n))return t.indexOf(n)!==-1;if(Se.isRegExp(n))return n.test(t)}}function y2(e){return e.trim().toLowerCase().replace(/([a-z\d])(\w*)/g,(t,r,n)=>r.toUpperCase()+n)}function x2(e,t){const r=Se.toCamelCase(" "+t);["get","set","has"].forEach(n=>{Object.defineProperty(e,n+r,{value:function(o,a,i){return this[n].call(this,t,o,a,i)},configurable:!0})})}class nf{constructor(t){t&&this.set(t)}set(t,r,n){const o=this;function a(l,f,g){const m=Zs(f);if(!m)throw new Error("header name must be a non-empty string");const S=Se.findKey(o,m);(!S||o[S]===void 0||g===!0||g===void 0&&o[S]!==!1)&&(o[S||f]=kc(l))}const i=(l,f)=>Se.forEach(l,(g,m)=>a(g,m,f));if(Se.isPlainObject(t)||t instanceof this.constructor)i(t,r);else if(Se.isString(t)&&(t=t.trim())&&!v2(t))i(m2(t),r);else if(Se.isHeaders(t))for(const[l,f]of t.entries())a(f,l,n);else t!=null&&a(r,t,n);return this}get(t,r){if(t=Zs(t),t){const n=Se.findKey(this,t);if(n){const o=this[n];if(!r)return o;if(r===!0)return b2(o);if(Se.isFunction(r))return r.call(this,o,n);if(Se.isRegExp(r))return r.exec(o);throw new TypeError("parser must be boolean|regexp|function")}}}has(t,r){if(t=Zs(t),t){const n=Se.findKey(this,t);return!!(n&&this[n]!==void 0&&(!r||Np(this,this[n],n,r)))}return!1}delete(t,r){const n=this;let o=!1;function a(i){if(i=Zs(i),i){const l=Se.findKey(n,i);l&&(!r||Np(n,n[l],l,r))&&(delete n[l],o=!0)}}return Se.isArray(t)?t.forEach(a):a(t),o}clear(t){const r=Object.keys(this);let n=r.length,o=!1;for(;n--;){const a=r[n];(!t||Np(this,this[a],a,t,!0))&&(delete this[a],o=!0)}return o}normalize(t){const r=this,n={};return Se.forEach(this,(o,a)=>{const i=Se.findKey(n,a);if(i){r[i]=kc(o),delete r[a];return}const l=t?y2(a):String(a).trim();l!==a&&delete r[a],r[l]=kc(o),n[l]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const r=Object.create(null);return Se.forEach(this,(n,o)=>{n!=null&&n!==!1&&(r[o]=t&&Se.isArray(n)?n.join(", "):n)}),r}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,r])=>t+": "+r).join(`
`)}get[Symbol.toStringTag](){return"AxiosHeaders"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...r){const n=new this(t);return r.forEach(o=>n.set(o)),n}static accessor(t){const n=(this[py]=this[py]={accessors:{}}).accessors,o=this.prototype;function a(i){const l=Zs(i);n[l]||(x2(o,i),n[l]=!0)}return Se.isArray(t)?t.forEach(a):a(t),this}}nf.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);Se.reduceDescriptors(nf.prototype,({value:e},t)=>{let r=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(n){this[r]=n}}});Se.freezeMethods(nf);const oo=nf;function jp(e,t){const r=this||h0,n=t||r,o=oo.from(n.headers);let a=n.data;return Se.forEach(e,function(l){a=l.call(r,a,o.normalize(),t?t.status:void 0)}),o.normalize(),a}function C_(e){return!!(e&&e.__CANCEL__)}function Ss(e,t,r){jt.call(this,e??"canceled",jt.ERR_CANCELED,t,r),this.name="CanceledError"}Se.inherits(Ss,jt,{__CANCEL__:!0});function T_(e,t,r){const n=r.config.validateStatus;!r.status||!n||n(r.status)?e(r):t(new jt("Request failed with status code "+r.status,[jt.ERR_BAD_REQUEST,jt.ERR_BAD_RESPONSE][Math.floor(r.status/100)-4],r.config,r.request,r))}function w2(e){const t=/^([-+\w]{1,25})(:?\/\/|:)/.exec(e);return t&&t[1]||""}function _2(e,t){e=e||10;const r=new Array(e),n=new Array(e);let o=0,a=0,i;return t=t!==void 0?t:1e3,function(f){const g=Date.now(),m=n[a];i||(i=g),r[o]=f,n[o]=g;let S=a,O=0;for(;S!==o;)O+=r[S++],S=S%e;if(o=(o+1)%e,o===a&&(a=(a+1)%e),g-i<t)return;const T=m&&g-m;return T?Math.round(O*1e3/T):void 0}}function S2(e,t){let r=0,n=1e3/t,o,a;const i=(g,m=Date.now())=>{r=m,o=null,a&&(clearTimeout(a),a=null),e.apply(null,g)};return[(...g)=>{const m=Date.now(),S=m-r;S>=n?i(g,m):(o=g,a||(a=setTimeout(()=>{a=null,i(o)},n-S)))},()=>o&&i(o)]}const rd=(e,t,r=3)=>{let n=0;const o=_2(50,250);return S2(a=>{const i=a.loaded,l=a.lengthComputable?a.total:void 0,f=i-n,g=o(f),m=i<=l;n=i;const S={loaded:i,total:l,progress:l?i/l:void 0,bytes:f,rate:g||void 0,estimated:g&&l&&m?(l-i)/g:void 0,event:a,lengthComputable:l!=null,[t?"download":"upload"]:!0};e(S)},r)},hy=(e,t)=>{const r=e!=null;return[n=>t[0]({lengthComputable:r,total:e,loaded:n}),t[1]]},gy=e=>(...t)=>Se.asap(()=>e(...t)),C2=_n.hasStandardBrowserEnv?function(){const t=_n.navigator&&/(msie|trident)/i.test(_n.navigator.userAgent),r=document.createElement("a");let n;function o(a){let i=a;return t&&(r.setAttribute("href",i),i=r.href),r.setAttribute("href",i),{href:r.href,protocol:r.protocol?r.protocol.replace(/:$/,""):"",host:r.host,search:r.search?r.search.replace(/^\?/,""):"",hash:r.hash?r.hash.replace(/^#/,""):"",hostname:r.hostname,port:r.port,pathname:r.pathname.charAt(0)==="/"?r.pathname:"/"+r.pathname}}return n=o(window.location.href),function(i){const l=Se.isString(i)?o(i):i;return l.protocol===n.protocol&&l.host===n.host}}():function(){return function(){return!0}}(),T2=_n.hasStandardBrowserEnv?{write(e,t,r,n,o,a){const i=[e+"="+encodeURIComponent(t)];Se.isNumber(r)&&i.push("expires="+new Date(r).toGMTString()),Se.isString(n)&&i.push("path="+n),Se.isString(o)&&i.push("domain="+o),a===!0&&i.push("secure"),document.cookie=i.join("; ")},read(e){const t=document.cookie.match(new RegExp("(^|;\\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove(e){this.write(e,"",Date.now()-864e5)}}:{write(){},read(){return null},remove(){}};function P2(e){return/^([a-z][a-z\d+\-.]*:)?\/\//i.test(e)}function O2(e,t){return t?e.replace(/\/?\/$/,"")+"/"+t.replace(/^\/+/,""):e}function P_(e,t){return e&&!P2(t)?O2(e,t):t}const my=e=>e instanceof oo?{...e}:e;function ni(e,t){t=t||{};const r={};function n(g,m,S){return Se.isPlainObject(g)&&Se.isPlainObject(m)?Se.merge.call({caseless:S},g,m):Se.isPlainObject(m)?Se.merge({},m):Se.isArray(m)?m.slice():m}function o(g,m,S){if(Se.isUndefined(m)){if(!Se.isUndefined(g))return n(void 0,g,S)}else return n(g,m,S)}function a(g,m){if(!Se.isUndefined(m))return n(void 0,m)}function i(g,m){if(Se.isUndefined(m)){if(!Se.isUndefined(g))return n(void 0,g)}else return n(void 0,m)}function l(g,m,S){if(S in t)return n(g,m);if(S in e)return n(void 0,g)}const f={url:a,method:a,data:a,baseURL:i,transformRequest:i,transformResponse:i,paramsSerializer:i,timeout:i,timeoutMessage:i,withCredentials:i,withXSRFToken:i,adapter:i,responseType:i,xsrfCookieName:i,xsrfHeaderName:i,onUploadProgress:i,onDownloadProgress:i,decompress:i,maxContentLength:i,maxBodyLength:i,beforeRedirect:i,transport:i,httpAgent:i,httpsAgent:i,cancelToken:i,socketPath:i,responseEncoding:i,validateStatus:l,headers:(g,m)=>o(my(g),my(m),!0)};return Se.forEach(Object.keys(Object.assign({},e,t)),function(m){const S=f[m]||o,O=S(e[m],t[m],m);Se.isUndefined(O)&&S!==l||(r[m]=O)}),r}const O_=e=>{const t=ni({},e);let{data:r,withXSRFToken:n,xsrfHeaderName:o,xsrfCookieName:a,headers:i,auth:l}=t;t.headers=i=oo.from(i),t.url=w_(P_(t.baseURL,t.url),e.params,e.paramsSerializer),l&&i.set("Authorization","Basic "+btoa((l.username||"")+":"+(l.password?unescape(enco Line truncated
`+a.map(yy).join(`
`):" "+yy(a[0]):"as no adapter specified";throw new jt("There is no suitable adapter to dispatch the request "+i,"ERR_NOT_SUPPORT")}return n},adapters:jh};function Ep(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new Ss(null,e)}function xy(e){return Ep(e),e.headers=oo.from(e.headers),e.data=jp.call(e,e.transformRequest),["post","put","patch"].indexOf(e.method)!==-1&&e.headers.setContentType("application/x-www-form-urlencoded",!1),j_.getAdapter(e.adapter||h0.adapter)(e).then(function(n){return Ep(e),n.data=jp.call(e,e.transformResponse,n),n.headers=oo.from(n.headers),n},function(n){return C_(n)||(Ep(e),n&&n.response&&(n.response.data=jp.call(e,e.transformResponse,n.response),n.response.headers=oo.from(n.response.headers))),Promise.reject(n)})}const E_="1.7.5",g0={};["object","boolean","number","function","string","symbol"].forEach((e,t)=>{g0[e]=function(n){return typeof n===e||"a"+(t<1?"n ":" ")+e}});const wy={};g0.transitional=function(t,r,n){function o(a,i){return"[Axios v"+E_+"] Transitional option '"+a+"'"+i+(n?". "+n:"")}return(a,i,l)=>{if(t===!1)throw new jt(o(i," has been removed"+(r?" in "+r:"")),jt.ERR_DEPRECATED);return r&&!wy[i]&&(wy[i]=!0,console.warn(o(i," has been deprecated since v"+r+" and will be removed in the near future"))),t?t(a,i,l):!0}};function z2(e,t,r){if(typeof e!="object")throw new jt("options must be an object",jt.ERR_BAD_OPTION_VALUE);const n=Object.keys(e);let o=n.length;for(;o-- >0;){const a=n[o],i=t[a];if(i){const l=e[a],f=l===void 0||i(l,a,e);if(f!==!0)throw new jt("option "+a+" must be "+f,jt.ERR_BAD_OPTION_VALUE);continue}if(r!==!0)throw new jt("Unknown option "+a,jt.ERR_BAD_OPTION)}}const Eh={assertOptions:z2,validators:g0},oa=Eh.validators;class od{constructor(t){this.defaults=t,this.interceptors={request:new fy,response:new fy}}async request(t,r){try{return await this._request(t,r)}catch(n){if(n instanceof Error){let o;Error.captureStackTrace?Error.captureStackTrace(o={}):o=new Error;const a=o.stack?o.stack.replace(/^.+\n/,""):"";try{n.stack?a&&!String(n.stack).endsWith(a.replace(/^.+\n.+\n/,""))&&(n.stack+=`
`+a):n.stack=a}catch{}}throw n}}_request(t,r){typeof t=="string"?(r=r||{},r.url=t):r=t||{},r=ni(this.defaults,r);const{transitional:n,paramsSerializer:o,headers:a}=r;n!==void 0&&Eh.assertOptions(n,{silentJSONParsing:oa.transitional(oa.boolean),forcedJSONParsing:oa.transitional(oa.boolean),clarifyTimeoutError:oa.transitional(oa.boolean)},!1),o!=null&&(Se.isFunction(o)?r.paramsSerializer={serialize:o}:Eh.assertOptions(o,{encode:oa.function,serialize:oa.function},!0)),r.method=(r.method||this.defaults.method||"get").toLowerCase();let i=a&&Se.merge(a.common,a[r.method]);a&&Se.forEach(["delete","get","head","post","put","patch","common"],C=>{delete a[C]}),r.headers=oo.concat(i,a);const l=[];let f=!0;this.interceptors.request.forEach(function(w){typeof w.runWhen=="function"&&w.runWhen(r)===!1||(f=f&&w.synchronous,l.unshift(w.fulfilled,w.rejected))});const g=[];this.interceptors.response.forEach(function(w){g.push(w.fulfilled,w.rejected)});let m,S=0,O;if(!f){const C=[xy.bind(this),void 0];for(C.unshift.apply(C,l),C.push.apply(C,g),O=C.length,m=Promise.resolve(r);S<O;)m=m.then(C[S++],C[S++]);return m}O=l.length;let T=r;for(S=0;S<O;){const C=l[S++],w=l[S++];try{T=C(T)}catch(b){w.call(this,b);break}}try{m=xy.call(this,T)}catch(C){return Promise.reject(C)}for(S=0,O=g.length;S<O;)m=m.then(g[S++],g[S++]);return m}getUri(t){t=ni(this.defaults,t);const r=P_(t.baseURL,t.url);return w_(r,t.params,t.paramsSerializer)}}Se.forEach(["delete","get","head","options"],function(t){od.prototype[t]=function(r,n){return this.request(ni(n||{},{method:t,url:r,data:(n||{}).data}))}});Se.forEach(["post","put","patch"],function(t){function r(n){return function(a,i,l){return this.request(ni(l||{},{method:t,headers:n?{"Content-Type":"multipart/form-data"}:{},url:a,data:i}))}}od.prototype[t]=r(),od.prototype[t+"Form"]=r(!0)});const Nc=od;class m0{constructor(t){if(typeof t!="function")throw new TypeError("executor must be a function.");let r;this.promise=new Promise(function(a){r=a});const n=this;this.promise.then(o=>{if(!n._listeners)return;let a=n._listeners.length;for(;a-- >0;)n._listeners[a](o);n._listeners=null}),this.promise.then=o=>{let a;const i=new Promise(l=>{n.subscribe(l),a=l}).then(o);return i.cancel=function(){n.unsubscribe(a)},i},t(function(a,i,l){n.reason||(n.reason=new Ss(a,i,l),r(n.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const r=this._listeners.indexOf(t);r!==-1&&this._listeners.splice(r,1)}static source(){let t;return{token:new m0(function(o){t=o}),cancel:t}}}const B2=m0;function V2(e){return function(r){return e.apply(null,r)}}function U2(e){return Se.isObject(e)&&e.isAxiosError===!0}const Ah={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511};Object.entries(Ah).forEach(([e,t])=>{Ah[t]=e});const W2=Ah;function A_(e){const t=new Nc(e),r=u_(Nc.prototype.request,t);return Se.extend(r,Nc.prototype,t,{allOwnKeys:!0}),Se.extend(r,t,null,{allOwnKeys:!0}),r.create=function(o){return A_(ni(e,o))},r}const wr=A_(h0);wr.Axios=Nc;wr.CanceledError=Ss;wr.CancelToken=B2;wr.isCancel=C_;wr.VERSION=E_;wr.toFormData=rf;wr.AxiosError=jt;wr.Cancel=wr.CanceledError;wr.all=function(t){return Promise.all(t)};wr.spread=V2;wr.isAxiosError=U2;wr.mergeConfig=ni;wr.AxiosHeaders=oo;wr.formToJSON=e=>S_(Se.isHTMLForm(e)?new FormData(e):e);wr.getAdapter=j_.getAdapter;wr.HttpStatusCode=W2;wr.default=wr;const Je=wr;var Rh={},R_={exports:{}},On={},M_={exports:{}},I_={};/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/(function(e){function t(G,B){var $=G.length;G.push(B);e:for(;0<$;){var q=$-1>>>1,ee=G[q];if(0<o(ee,B))G[q]=B,G[$]=ee,$=q;else break e}}function r(G){return G.length===0?null:G[0]}function n(G){if(G.length===0)return null;var B=G[0],$=G.pop();if($!==B){G[0]=$;e:for(var q=0,ee=G.length,oe=ee>>>1;q<oe;){var ge=2*(q+1)-1,fe=G[ge],re=ge+1,be=G[re];if(0>o(fe,$))re<ee&&0>o(be,fe)?(G[q]=be,G[re]=$,q=re):(G[q]=fe,G[ge]=$,q=ge);else if(re<ee&&0>o(be,$))G[q]=be,G[re]=$,q=re;else break e}}return B}function o(G,B){var $=G.sortIndex-B.sortIndex;return $!==0?$:G.id-B.id}if(typeof performance=="object"&&typeof performance.now=="function"){var a=performance;e.unstable_now=function(){return a.now()}}else{var i=Date,l=i.now();e.unstable_now=function(){return i.now()-l}}var f=[],g=[],m=1,S=null,O=3,T=!1,C=!1,w=!1,b=typeof setTimeout=="function"?setTimeout:null,d=typeof clearTimeout=="function"?clearTimeout:null,s=typeof setImmediate<"u"?setImmediate:null;typeof navigator<"u"&&navigator.scheduling!==void 0&&navigator.scheduling.isInputPending!==void 0&&navigator.scheduling.isInputPending.bind(navigator.scheduling);function c(G){for(var B=r(g);B!==null;){if(B.callback===null)n(g);else if(B.startTime<=G)n(g),B.sortIndex=B.expirationTime,t(f,B);else break;B=r(g)}}function u(G){if(w=!1,c(G),!C)if(r(f)!==null)C=!0,H(h);else{var B=r(g);B!==null&&Y(u,B.startTime-G)}}function h(G,B){C=!1,w&&(w=!1,d(E),E=-1),T=!0;var $=O;try{for(c(B),S=r(f);S!==null&&(!(S.expirationTime>B)||G&&!A());){var q=S.callback;if(typeof q=="function"){S.callback=null,O=S.priorityLevel;var ee=q(S.expirationTime<=B);B=e.unstable_now(),typeof ee=="function"?S.callback=ee:S===r(f)&&n(f),c(B)}else n(f);S=r(f)}if(S!==null)var oe=!0;else{var ge=r(g);ge!==null&&Y(u,ge.startTime-B),oe=!1}return oe}finally{S=null,O=$,T=!1}}var y=!1,x=null,E=-1,k=5,j=-1;function A(){return!(e.unstable_now()-j<k)}function M(){if(x!==null){var G=e.unstable_now();j=G;var B=!0;try{B=x(!0,G)}finally{B?D():(y=!1,x=null)}}else y=!1}var D;if(typeof s=="function")D=function(){s(M)};else if(typeof MessageChannel<"u"){var L=new MessageChannel,z=L.port2;L.port1.onmessage=M,D=function(){z.postMessage(null)}}else D=function(){b(M,0)};function H(G){x=G,y||(y=!0,D())}function Y(G,B){E=b(function(){G(e.unstable_now())},B)}e.unstable_IdlePriority=5,e.unstable_ImmediatePriority=1,e.unstable_LowPriority=4,e.unstable_NormalPriority=3,e.unstable_Profiling=null,e.unstable_UserBlockingPriority=2,e.unstable_cancelCallback=function(G){G.callback=null},e.unstable_continueExecution=function(){C||T||(C=!0,H(h))},e.unstable_forceFrameRate=function(G){0>G||125<G?console.error("forceFrameRate takes a positive int between 0 and 125, forcing frame rates higher than 125 fps is not supported"):k=0<G?Math.floor(1e3/G):5},e.unstable_getCurrentPriorityLevel=function(){return O},e.unstable_getFirstCallbackNode=function(){return r(f)},e.unstable_next=function(G){switch(O){case 1:case 2:case 3:var B=3;break;default:B=O}var $=O;O=B;try{return G()}finally{O=$}},e.unstable_pauseExecution=function(){},e.unstable_requestPaint=function(){},e.unstable_runWithPriority=function(G,B){switch(G){case 1:case 2:case 3:case 4:case 5:break;default:G=3}var $=O;O=G;try{return B()}finally{O=$}},e.unstable_scheduleCallback=function(G,B,$){var q=e.unstable_now();switch(typeof $=="object"&&$!==null?($=$.delay,$=typeof $=="number"&&0<$?q+$:q):$=q,G){case 1:var ee=-1;break;case 2:ee=250;break;case 5:ee=1073741823;break;case 4:ee=1e4;break;default:ee=5e3}return ee=$+ee,G={id:m++,callback:B,priorityLevel:G,startTime:$,expirationTime:ee,sortIndex:-1},$>q?(G.sortIndex=$,t(g,G),r(f)===null&&G===r(g)&&(w?(d(E),E=-1):w=!0,Y(u,$-q))):(G.sortIndex=ee,t(f,G),C||T||(C=!0,H(h))),G},e.unstable_shouldYield=A,e.unstable_wrapCallback=function(G){var B=O;return function(){var $=O;O=B;try{return G.apply(this,arguments)}finally{O=$}}}})(I_);M_.exports=I_;var $2=M_.exports;/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/var D_=P,Tn=$2;function Re(e){for(var t="https://reactjs.org/docs/error-decoder.html?invariant="+e,r=1;r<arguments.length;r++)t+="&args[]="+encodeURIComponent(arguments[r]);return"Minified React error #"+e+"; visit "+t+" for the full message or use the non-minified dev environment for full errors and additional helpful warnings."}var L_=new Set,Ml={};function pi(e,t){is(e,t),is(e+"Capture",t)}function is(e,t){for(Ml[e]=t,e=0;e<t.length;e++)L_.add(t[e])}var Bo=!(typeof window>"u"||typeof window.document>"u"||typeof window.document.createElement>"u"),Mh=Object.prototype.hasOwnProperty,H2=/^[:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD][:A-Z_a-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u02FF\u0370-\u037D\u037F-\u1FFF\u200C-\u200D\u2070-\u218F\u2C00-\u2FEF\u3001-\uD7FF\uF900-\uFDCF\uFDF0-\uFFFD\-.0-9\u00B7\u0300-\u036F\u203F-\u2040]*$/,_y={},Sy={};function G2(e){return Mh.call(Sy,e)?!0:Mh.call(_y,e)?!1:H2.test(e)?Sy[e]=!0:(_y[e]=!0,!1)}function q2(e,t,r,n){if(r!==null&&r.type===0)return!1;switch(typeof t){case"function":case"symbol":return!0;case"boolean":return n?!1:r!==null?!r.acceptsBooleans:(e=e.toLowerCase().slice(0,5),e!=="data-"&&e!=="aria-");default:return!1}}function K2(e,t,r,n){if(t===null||typeof t>"u"||q2(e,t,r,n))return!0;if(n)return!1;if(r!==null)switch(r.type){case 3:return!t;case 4:return t===!1;case 5:return isNaN(t);case 6:return isNaN(t)||1>t}return!1}function nn(e,t,r,n,o,a,i){this.acceptsBooleans=t===2||t===3||t===4,this.attributeName=n,this.attributeNamespace=o,this.mustUseProperty=r,this.propertyName=e,this.type=t,this.sanitizeURL=a,this.removeEmptyString=i}var zr={};"children dangerouslySetInnerHTML defaultValue defaultChecked innerHTML suppressContentEditableWarning suppressHydrationWarning style".split(" ").forEach(function(e){zr[e]=new nn(e,0,!1,e,null,!1,!1)});[["acceptCharset","accept-charset"],["className","class"],["htmlFor","for"],["httpEquiv","http-equiv"]].forEach(function(e){var t=e[0];zr[t]=new nn(t,1,!1,e[1],null,!1,!1)});["contentEditable","draggable","spellCheck","value"].forEach(function(e){zr[e]=new nn(e,2,!1,e.toLowerCase(),null,!1,!1)});["autoReverse","externalResourcesRequired","focusable","preserveAlpha"].forEach(function(e){zr[e]=new nn(e,2,!1,e,null,!1,!1)});"allowFullScreen async autoFocus autoPlay controls default defer disabled disablePictureInPicture disableRemotePlayback formNoValidate hidden loop noModule noValidate open playsInline readOnly required reversed scoped seamless itemScope".split(" ").forEach(function(e){zr[e]=new nn(e,3,!1,e.toLowerCase(),null,!1,!1)});["checked","multiple","muted","selected"].forEach(function(e){zr[e]=new nn(e,3,!0,e,null,!1,!1)});["capture","download"].forEach(function(e){zr[e]=new nn(e,4,!1,e,null,!1,!1)});["cols","rows","size","span"].forEach(function(e){zr[e]=new nn(e,6,!1,e,null,!1,!1)});["rowSpan","start"].forEach(function(e){zr[e]=new nn(e,5,!1,e.toLowerCase(),null,!1,!1)});var b0=/[\-:]([a-z])/g;function v0(e){return e[1].toUpperCase()}"accent-height alignment-baseline arabic-form baseline-shift cap-height clip-path clip-rule color-interpolation color-interpolation-filters color-profile color-rendering dominant-baseline enable-background fill-opacity fill-rule flood-color flood-opacity font-family font-size font-size-adjust font-stretch font-style font-variant font-weight glyph-name glyph-orientation-horizontal glyph-orientation-vertical horiz-adv-x horiz-origin-x image-rendering letter-spacing lighting-color marker-end marker-mid marker-start overline-position overline-thickness paint-order panose-1 pointer-events rendering-intent shape-rendering stop-color stop-opacity strikethrough-position strikethrough-thickness stroke-dasharray stroke-dashoffset stroke-linecap stroke-linejoin stroke-miterlimit stroke-opacity stroke-width text-anchor text-decoration text-rendering underline-position underline-thickness unicode-bidi unicode-range units-per-em v-alphabetic v-hanging v-ideographic v-mathematical vector-effect vert-adv-y vert-origin-x vert-origin-y word-spacing writing-mode xmlns:xlink x-height".split(" ").forEach(function(e){var t=e.replace(b0,v0);zr[t]=new nn(t,1,!1,e,null,!1,!1)});"xlink:actuate xlink:arcrole xlink:role xlink:show xlink:title xlink:type".split(" ").forEach(function(e){var t=e.replace(b0,v0);zr[t]=new nn(t,1,!1,e,"http://www.w3.org/1999/xlink",!1,!1)});["xml:base","xml:lang","xml:space"].forEach(function(e){var t=e.replace(b0,v0);zr[t]=new nn(t,1,!1,e,"http://www.w3.org/XML/1998/namespace",!1,!1)});["tabIndex","crossOrigin"].forEach(function(e){zr[e]=new nn(e,1,!1,e.toLowerCase(),null,!1,!1)});zr.xlinkHref=new nn("xlinkHref",1,!1,"xlink:href","http://www.w3.org/1999/xlink",!0,!1);["src","href","action","formAction"].forEach(function(e){zr[e]=new nn(e,1,!1,e.toLowerCase(),null,!0,!0)});function y0(e,t,r,n){var o=zr.hasOwnProperty(t)?zr[t]:null;(o!==null?o.type!==0:n||!(2<t.length)||t[0]!=="o"&&t[0]!== Line truncated
`+Ap+e}var Rp=!1;function Mp(e,t){if(!e||Rp)return"";Rp=!0;var r=Error.prepareStackTrace;Error.prepareStackTrace=void 0;try{if(t)if(t=function(){throw Error()},Object.defineProperty(t.prototype,"props",{set:function(){throw Error()}}),typeof Reflect=="object"&&Reflect.construct){try{Reflect.construct(t,[])}catch(g){var n=g}Reflect.construct(e,[],t)}else{try{t.call()}catch(g){n=g}e.call(t.prototype)}else{try{throw Error()}catch(g){n=g}e()}}catch(g){if(g&&n&&typeof g.stack=="string"){for(var o=g.stack.split(`
`),a=n.stack.split(`
`),i=o.length-1,l=a.length-1;1<=i&&0<=l&&o[i]!==a[l];)l--;for(;1<=i&&0<=l;i--,l--)if(o[i]!==a[l]){if(i!==1||l!==1)do if(i--,l--,0>l||o[i]!==a[l]){var f=`
`+o[i].replace(" at new "," at ");return e.displayName&&f.includes("<anonymous>")&&(f=f.replace("<anonymous>",e.displayName)),f}while(1<=i&&0<=l);break}}}finally{Rp=!1,Error.prepareStackTrace=r}return(e=e?e.displayName||e.name:"")?fl(e):""}function Y2(e){switch(e.tag){case 5:return fl(e.type);case 16:return fl("Lazy");case 13:return fl("Suspense");case 19:return fl("SuspenseList");case 0:case 2:case 15:return e=Mp(e.type,!1),e;case 11:return e=Mp(e.type.render,!1),e;case 1:return e=Mp(e.type,!0),e;default:return""}}function Fh(e){if(e==null)return null;if(typeof e=="function")return e.displayName||e.name||null;if(typeof e=="string")return e;switch(e){case Di:return"Fragment";case Ii:return"Portal";case Ih:return"Profiler";case x0:return"StrictMode";case Dh:return"Suspense";case Lh:return"SuspenseList"}if(typeof e=="object")switch(e.$$typeof){case z_:return(e.displayName||"Context")+".Consumer";case F_:return(e._context.displayName||"Context")+".Provider";case w0:var t=e.render;return e=e.displayName,e||(e=t.displayName||t.name||"",e=e!==""?"ForwardRef("+e+")":"ForwardRef"),e;case _0:return t=e.displayName||null,t!==null?t:Fh(e.type)||"Memo";case ca:t=e._payload,e=e._init;try{return Fh(e(t))}catch{}}return null}function Q2(e){var t=e.type;switch(e.tag){case 24:return"Cache";case 9:return(t.displayName||"Context")+".Consumer";case 10:return(t._context.displayName||"Context")+".Provider";case 18:return"DehydratedFragment";case 11:return e=t.render,e=e.displayName||e.name||"",t.displayName||(e!==""?"ForwardRef("+e+")":"ForwardRef");case 7:return"Fragment";case 5:return t;case 4:return"Portal";case 3:return"Root";case 6:return"Text";case 16:return Fh(t);case 8:return t===x0?"StrictMode":"Mode";case 22:return"Offscreen";case 12:return"Profiler";case 21:return"Scope";case 13:return"Suspense";case 19:return"SuspenseList";case 25:return"TracingMarker";case 1:case 0:case 17:case 2:case 14:case 15:if(typeof t=="function")return t.displayName||t.name||null;if(typeof t=="string")return t}return null}function ja(e){switch(typeof e){case"boolean":case"number":case"string":case"undefined":return e;case"object":return e;default:return""}}function V_(e){var t=e.type;return(e=e.nodeName)&&e.toLowerCase()==="input"&&(t==="checkbox"||t==="radio")}function X2(e){var t=V_(e)?"checked":"value",r=Object.getOwnPropertyDescriptor(e.constructor.prototype,t),n=""+e[t];if(!e.hasOwnProperty(t)&&typeof r<"u"&&typeof r.get=="function"&&typeof r.set=="function"){var o=r.get,a=r.set;return Object.defineProperty(e,t,{configurable:!0,get:function(){return o.call(this)},set:function(i){n=""+i,a.call(this,i)}}),Object.defineProperty(e,t,{enumerable:r.enumerable}),{getValue:function(){return n},setValue:function(i){n=""+i},stopTracking:function(){e._valueTracker=null,delete e[t]}}}}function Qu(e){e._valueTracker||(e._valueTracker=X2(e))}function U_(e){if(!e)return!1;var t=e._valueTracker;if(!t)return!0;var r=t.getValue(),n="";return e&&(n=V_(e)?e.checked?"true":"false":e.value),e=n,e!==r?(t.setValue(e),!0):!1}function ad(e){if(e=e||(typeof document<"u"?document:void 0),typeof e>"u")return null;try{return e.activeElement||e.body}catch{return e.body}}function zh(e,t){var r=t.checked;return fr({},t,{defaultChecked:void 0,defaultValue:void 0,value:void 0,checked:r??e._wrapperState.initialChecked})}function Ty(e,t){var r=t.defaultValue==null?"":t.defaultValue,n=t.checked!=null?t.checked:t.defaultChecked;r=ja(t.value!=null?t.value:r),e._wrapperState={initialChecked:n,initialValue:r,controlled:t.type==="checkbox"||t.type==="radio"?t.checked!=null:t.value!=null}}function W_(e,t){t=t.checked,t!=null&&y0(e,"checked",t,!1)}function Bh(e,t){W_(e,t);var r=ja(t.value),n=t.type;if(r!=null)n==="number"?(r===0&&e.value===""||e.value!=r)&&(e.value=""+r):e.value!==""+r&&(e.value=""+r);else if(n==="submit"||n==="reset"){e.removeAttribute("value");return}t.hasOwnProperty("value")?Vh(e,t.type,r):t.hasOwnProperty("defaultValue")&&Vh(e,t.type,ja(t.defaultValue)),t.checked==null&&t.defaultChecked!=null&&(e.defaultChecked=!!t.defaultChecked)}function Py(e,t,r){if(t.hasOwnProperty("value")||t.hasOwnProperty("defaultValue")){var n=t.type;if(!(n!=="submit"&&n!=="reset"||t.value!==void 0&&t.value!==null))return;t=""+e._wrapperState.initialValue,r||t===e.value||(e.value=t),e.defaultValue=t}r=e.name,r!==""&&(e.name=""),e.defaultChecked=!!e._wrapperState.initialChecked,r!==""&&(e.name=r)}function Vh(e,t,r){(t!=="number"||ad(e.ownerDocument)!==e)&&(r==null?e.defaultValue=""+e._wrapperState.initialValue:e.defaultValue!==""+r&&(e.defaultValue=""+r))}var pl=Array.isArray;function Yi(e,t,r,n){if(e=e.options,t){t={};for(var o=0;o<r.length;o++)t["$"+r[o]]=!0;for(r=0;r<e.length;r++)o=t.hasOwnProperty("$"+e[r].value),e[r].selected!==o&&(e[r].selected=o),o&&n&&(e[r].defaultSelected=!0)}else{for(r=""+ja(r),t=null,o=0;o<e.length;o++){if(e[o].value===r){e[o].selected=!0,n&&(e[o].defaultSelected=!0);return}t!==null||e[o].disabled||(t=e[o])}t!==null&&(t.selected=!0)}}function Uh(e,t){if(t.dan Line truncated
`).replace(sM,"")}function oc(e,t,r){if(t=Yy(t),Yy(e)!==t&&r)throw Error(Re(425))}function pd(){}var tg=null,rg=null;function ng(e,t){return e==="textarea"||e==="noscript"||typeof t.children=="string"||typeof t.children=="number"||typeof t.dangerouslySetInnerHTML=="object"&&t.dangerouslySetInnerHTML!==null&&t.dangerouslySetInnerHTML.__html!=null}var og=typeof setTimeout=="function"?setTimeout:void 0,lM=typeof clearTimeout=="function"?clearTimeout:void 0,Qy=typeof Promise=="function"?Promise:void 0,uM=typeof queueMicrotask=="function"?queueMicrotask:typeof Qy<"u"?function(e){return Qy.resolve(null).then(e).catch(cM)}:og;function cM(e){setTimeout(function(){throw e})}function qp(e,t){var r=t,n=0;do{var o=r.nextSibling;if(e.removeChild(r),o&&o.nodeType===8)if(r=o.data,r==="/$"){if(n===0){e.removeChild(o),zl(t);return}n--}else r!=="$"&&r!=="$?"&&r!=="$!"||n++;r=o}while(r);zl(t)}function _a(e){for(;e!=null;e=e.nextSibling){var t=e.nodeType;if(t===1||t===3)break;if(t===8){if(t=e.data,t==="$"||t==="$!"||t==="$?")break;if(t==="/$")return null}}return e}function Xy(e){e=e.previousSibling;for(var t=0;e;){if(e.nodeType===8){var r=e.data;if(r==="$"||r==="$!"||r==="$?"){if(t===0)return e;t--}else r==="/$"&&t++}e=e.previousSibling}return null}var Ts=Math.random().toString(36).slice(2),So="__reactFiber$"+Ts,$l="__reactProps$"+Ts,Vo="__reactContainer$"+Ts,ag="__reactEvents$"+Ts,dM="__reactListeners$"+Ts,fM="__reactHandles$"+Ts;function Qa(e){var t=e[So];if(t)return t;for(var r=e.parentNode;r;){if(t=r[Vo]||r[So]){if(r=t.alternate,t.child!==null||r!==null&&r.child!==null)for(e=Xy(e);e!==null;){if(r=e[So])return r;e=Xy(e)}return t}e=r,r=e.parentNode}return null}function fu(e){return e=e[So]||e[Vo],!e||e.tag!==5&&e.tag!==6&&e.tag!==13&&e.tag!==3?null:e}function Bi(e){if(e.tag===5||e.tag===6)return e.stateNode;throw Error(Re(33))}function cf(e){return e[$l]||null}var ig=[],Vi=-1;function Ma(e){return{current:e}}function rr(e){0>Vi||(e.current=ig[Vi],ig[Vi]=null,Vi--)}function Jt(e,t){Vi++,ig[Vi]=e.current,e.current=t}var Ea={},Kr=Ma(Ea),fn=Ma(!1),oi=Ea;function ss(e,t){var r=e.type.contextTypes;if(!r)return Ea;var n=e.stateNode;if(n&&n.__reactInternalMemoizedUnmaskedChildContext===t)return n.__reactInternalMemoizedMaskedChildContext;var o={},a;for(a in r)o[a]=t[a];return n&&(e=e.stateNode,e.__reactInternalMemoizedUnmaskedChildContext=t,e.__reactInternalMemoizedMaskedChildContext=o),o}function pn(e){return e=e.childContextTypes,e!=null}function hd(){rr(fn),rr(Kr)}function Zy(e,t,r){if(Kr.current!==Ea)throw Error(Re(168));Jt(Kr,t),Jt(fn,r)}function ES(e,t,r){var n=e.stateNode;if(t=t.childContextTypes,typeof n.getChildContext!="function")return r;n=n.getChildContext();for(var o in n)if(!(o in t))throw Error(Re(108,Q2(e)||"Unknown",o));return fr({},r,n)}function gd(e){return e=(e=e.stateNode)&&e.__reactInternalMemoizedMergedChildContext||Ea,oi=Kr.current,Jt(Kr,e),Jt(fn,fn.current),!0}function Jy(e,t,r){var n=e.stateNode;if(!n)throw Error(Re(169));r?(e=ES(e,t,oi),n.__reactInternalMemoizedMergedChildContext=e,rr(fn),rr(Kr),Jt(Kr,e)):rr(fn),Jt(fn,r)}var Mo=null,df=!1,Kp=!1;function AS(e){Mo===null?Mo=[e]:Mo.push(e)}function pM(e){df=!0,AS(e)}function Ia(){if(!Kp&&Mo!==null){Kp=!0;var e=0,t=Kt;try{var r=Mo;for(Kt=1;e<r.length;e++){var n=r[e];do n=n(!0);while(n!==null)}Mo=null,df=!1}catch(o){throw Mo!==null&&(Mo=Mo.slice(e+1)),nS(C0,Ia),o}finally{Kt=t,Kp=!1}}return null}var Ui=[],Wi=0,md=null,bd=0,Dn=[],Ln=0,ai=null,Io=1,Do="";function Ga(e,t){Ui[Wi++]=bd,Ui[Wi++]=md,md=e,bd=t}function RS(e,t,r){Dn[Ln++]=Io,Dn[Ln++]=Do,Dn[Ln++]=ai,ai=e;var n=Io;e=Do;var o=32-ao(n)-1;n&=~(1<<o),r+=1;var a=32-ao(t)+o;if(30<a){var i=o-o%5;a=(n&(1<<i)-1).toString(32),n>>=i,o-=i,Io=1<<32-ao(t)+o|r<<o|n,Do=a+e}else Io=1<<a|r<<o|n,Do=e}function R0(e){e.return!==null&&(Ga(e,1),RS(e,1,0))}function M0(e){for(;e===md;)md=Ui[--Wi],Ui[Wi]=null,bd=Ui[--Wi],Ui[Wi]=null;for(;e===ai;)ai=Dn[--Ln],Dn[Ln]=null,Do=Dn[--Ln],Dn[Ln]=null,Io=Dn[--Ln],Dn[Ln]=null}var Sn=null,yn=null,ir=!1,eo=null;function MS(e,t){var r=zn(5,null,null,0);r.elementType="DELETED",r.stateNode=t,r.return=e,t=e.deletions,t===null?(e.deletions=[r],e.flags|=16):t.push(r)}function ex(e,t){switch(e.tag){case 5:var r=e.type;return t=t.nodeType!==1||r.toLowerCase()!==t.nodeName.toLowerCase()?null:t,t!==null?(e.stateNode=t,Sn=e,yn=_a(t.firstChild),!0):!1;case 6:return t=e.pendingProps===""||t.nodeType!==3?null:t,t!==null?(e.stateNode=t,Sn=e,yn=null,!0):!1;case 13:return t=t.nodeType!==8?null:t,t!==null?(r=ai!==null?{id:Io,overflow:Do}:null,e.memoizedState={dehydrated:t,treeContext:r,retryLane:1073741824},r=zn(18,null,null,0),r.stateNode=t,r.return=e,e.child=r,Sn=e,yn=null,!0):!1;default:return!1}}function sg(e){return(e.mode&1)!==0&&(e.flags&128)===0}function lg(e){if(ir){var t=yn;if(t){var r=t;if(!ex(e,t)){if(sg(e))throw Error(Re(418));t=_a(r.nextSibling);var n=Sn;t&&ex(e,t)?MS(n,r):(e.flags=e.flags&-4097|2,ir=!1,Sn=e)}}else{if(sg(e))throw Error(Re(418));e.flags=e.flags&-4097|2,ir=!1,Sn=e}}}function tx(e){ Line truncated
Error generating stack: `+a.message+`
`+a.stack}return{value:e,source:t,stack:o,digest:null}}function Jp(e,t,r){return{value:e,source:null,stack:r??null,digest:t??null}}function fg(e,t){try{console.error(t.value)}catch(r){setTimeout(function(){throw r})}}var _M=typeof WeakMap=="function"?WeakMap:Map;function lC(e,t,r){r=Lo(-1,r),r.tag=3,r.payload={element:null};var n=t.value;return r.callback=function(){Td||(Td=!0,_g=n),fg(e,t)},r}function uC(e,t,r){r=Lo(-1,r),r.tag=3;var n=e.type.getDerivedStateFromError;if(typeof n=="function"){var o=t.value;r.payload=function(){return n(o)},r.callback=function(){fg(e,t)}}var a=e.stateNode;return a!==null&&typeof a.componentDidCatch=="function"&&(r.callback=function(){fg(e,t),typeof n!="function"&&(Ca===null?Ca=new Set([this]):Ca.add(this));var i=t.stack;this.componentDidCatch(t.value,{componentStack:i!==null?i:""})}),r}function ux(e,t,r){var n=e.pingCache;if(n===null){n=e.pingCache=new _M;var o=new Set;n.set(t,o)}else o=n.get(t),o===void 0&&(o=new Set,n.set(t,o));o.has(r)||(o.add(r),e=DM.bind(null,e,t,r),t.then(e,e))}function cx(e){do{var t;if((t=e.tag===13)&&(t=e.memoizedState,t=t!==null?t.dehydrated!==null:!0),t)return e;e=e.return}while(e!==null);return null}function dx(e,t,r,n,o){return e.mode&1?(e.flags|=65536,e.lanes=o,e):(e===t?e.flags|=65536:(e.flags|=128,r.flags|=131072,r.flags&=-52805,r.tag===1&&(r.alternate===null?r.tag=17:(t=Lo(-1,1),t.tag=2,Sa(r,t,1))),r.lanes|=1),e)}var SM=Go.ReactCurrentOwner,cn=!1;function en(e,t,r,n){t.child=e===null?VS(t,null,r,n):us(t,e.child,r,n)}function fx(e,t,r,n,o){r=r.render;var a=t.ref;return Ji(t,o),n=H0(e,t,r,n,a,o),r=G0(),e!==null&&!cn?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Wo(e,t,o)):(ir&&r&&R0(t),t.flags|=1,en(e,t,n,o),t.child)}function px(e,t,r,n,o){if(e===null){var a=r.type;return typeof a=="function"&&!rm(a)&&a.defaultProps===void 0&&r.compare===null&&r.defaultProps===void 0?(t.tag=15,t.type=a,cC(e,t,a,n,o)):(e=zc(r.type,null,n,t,t.mode,o),e.ref=t.ref,e.return=t,t.child=e)}if(a=e.child,!(e.lanes&o)){var i=a.memoizedProps;if(r=r.compare,r=r!==null?r:Vl,r(i,n)&&e.ref===t.ref)return Wo(e,t,o)}return t.flags|=1,e=Pa(a,n),e.ref=t.ref,e.return=t,t.child=e}function cC(e,t,r,n,o){if(e!==null){var a=e.memoizedProps;if(Vl(a,n)&&e.ref===t.ref)if(cn=!1,t.pendingProps=n=a,(e.lanes&o)!==0)e.flags&131072&&(cn=!0);else return t.lanes=e.lanes,Wo(e,t,o)}return pg(e,t,r,n,o)}function dC(e,t,r){var n=t.pendingProps,o=n.children,a=e!==null?e.memoizedState:null;if(n.mode==="hidden")if(!(t.mode&1))t.memoizedState={baseLanes:0,cachePool:null,transitions:null},Jt(Gi,vn),vn|=r;else{if(!(r&1073741824))return e=a!==null?a.baseLanes|r:r,t.lanes=t.childLanes=1073741824,t.memoizedState={baseLanes:e,cachePool:null,transitions:null},t.updateQueue=null,Jt(Gi,vn),vn|=e,null;t.memoizedState={baseLanes:0,cachePool:null,transitions:null},n=a!==null?a.baseLanes:r,Jt(Gi,vn),vn|=n}else a!==null?(n=a.baseLanes|r,t.memoizedState=null):n=r,Jt(Gi,vn),vn|=n;return en(e,t,o,r),t.child}function fC(e,t){var r=t.ref;(e===null&&r!==null||e!==null&&e.ref!==r)&&(t.flags|=512,t.flags|=2097152)}function pg(e,t,r,n,o){var a=pn(r)?oi:Kr.current;return a=ss(t,a),Ji(t,o),r=H0(e,t,r,n,a,o),n=G0(),e!==null&&!cn?(t.updateQueue=e.updateQueue,t.flags&=-2053,e.lanes&=~o,Wo(e,t,o)):(ir&&n&&R0(t),t.flags|=1,en(e,t,r,o),t.child)}function hx(e,t,r,n,o){if(pn(r)){var a=!0;gd(t)}else a=!1;if(Ji(t,o),t.stateNode===null)Dc(e,t),zS(t,r,n),dg(t,r,n,o),n=!0;else if(e===null){var i=t.stateNode,l=t.memoizedProps;i.props=l;var f=i.context,g=r.contextType;typeof g=="object"&&g!==null?g=Wn(g):(g=pn(r)?oi:Kr.current,g=ss(t,g));var m=r.getDerivedStateFromProps,S=typeof m=="function"||typeof i.getSnapshotBeforeUpdate=="function";S||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(l!==n||f!==g)&&ax(t,i,n,g),da=!1;var O=t.memoizedState;i.state=O,xd(t,n,i,o),f=t.memoizedState,l!==n||O!==f||fn.current||da?(typeof m=="function"&&(cg(t,r,m,n),f=t.memoizedState),(l=da||ox(t,r,l,n,O,f,g))?(S||typeof i.UNSAFE_componentWillMount!="function"&&typeof i.componentWillMount!="function"||(typeof i.componentWillMount=="function"&&i.componentWillMount(),typeof i.UNSAFE_componentWillMount=="function"&&i.UNSAFE_componentWillMount()),typeof i.componentDidMount=="function"&&(t.flags|=4194308)):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),t.memoizedProps=n,t.memoizedState=f),i.props=n,i.state=f,i.context=g,n=l):(typeof i.componentDidMount=="function"&&(t.flags|=4194308),n=!1)}else{i=t.stateNode,LS(e,t),l=t.memoizedProps,g=t.type===t.elementType?l:Xn(t.type,l),i.props=g,S=t.pendingProps,O=i.context,f=r.contextType,typeof f=="object"&&f!==null?f=Wn(f):(f=pn(r)?oi:Kr.current,f=ss(t,f));var T=r.getDerivedStateFromProps;(m=typeof T=="function"||typeof i.getSnapshotBeforeUpdate=="function")||typeof i.UNSAFE_componentWillReceiveProps!="function"&&typeof i.componentWillReceiveProps!="function"||(l!==S||O!==f)&&ax(t,i,n,f),da=!1,O=t.memoizedState,i.state=O,xd(t,n,i,o);var C=t.memoizedS Line truncated
* @remix-run/router v1.10.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/function Xl(){return Xl=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Xl.apply(this,arguments)}var ba;(function(e){e.Pop="POP",e.Push="PUSH",e.Replace="REPLACE"})(ba||(ba={}));const kx="popstate";function qM(e){e===void 0&&(e={});function t(n,o){let{pathname:a,search:i,hash:l}=n.location;return Pg("",{pathname:a,search:i,hash:l},o.state&&o.state.usr||null,o.state&&o.state.key||"default")}function r(n,o){return typeof o=="string"?o:Nd(o)}return YM(t,r,null,e)}function dr(e,t){if(e===!1||e===null||typeof e>"u")throw new Error(t)}function sm(e,t){if(!e){typeof console<"u"&&console.warn(t);try{throw new Error(t)}catch{}}}function KM(){return Math.random().toString(36).substr(2,8)}function Nx(e,t){return{usr:e.state,key:e.key,idx:t}}function Pg(e,t,r,n){return r===void 0&&(r=null),Xl({pathname:typeof e=="string"?e:e.pathname,search:"",hash:""},typeof t=="string"?Ps(t):t,{state:r,key:t&&t.key||n||KM()})}function Nd(e){let{pathname:t="/",search:r="",hash:n=""}=e;return r&&r!=="?"&&(t+=r.charAt(0)==="?"?r:"?"+r),n&&n!=="#"&&(t+=n.charAt(0)==="#"?n:"#"+n),t}function Ps(e){let t={};if(e){let r=e.indexOf("#");r>=0&&(t.hash=e.substr(r),e=e.substr(0,r));let n=e.indexOf("?");n>=0&&(t.search=e.substr(n),e=e.substr(0,n)),e&&(t.pathname=e)}return t}function YM(e,t,r,n){n===void 0&&(n={});let{window:o=document.defaultView,v5Compat:a=!1}=n,i=o.history,l=ba.Pop,f=null,g=m();g==null&&(g=0,i.replaceState(Xl({},i.state,{idx:g}),""));function m(){return(i.state||{idx:null}).idx}function S(){l=ba.Pop;let b=m(),d=b==null?null:b-g;g=b,f&&f({action:l,location:w.location,delta:d})}function O(b,d){l=ba.Push;let s=Pg(w.location,b,d);r&&r(s,b),g=m()+1;let c=Nx(s,g),u=w.createHref(s);try{i.pushState(c,"",u)}catch(h){if(h instanceof DOMException&&h.name==="DataCloneError")throw h;o.location.assign(u)}a&&f&&f({action:l,location:w.location,delta:1})}function T(b,d){l=ba.Replace;let s=Pg(w.location,b,d);r&&r(s,b),g=m();let c=Nx(s,g),u=w.createHref(s);i.replaceState(c,"",u),a&&f&&f({action:l,location:w.location,delta:0})}function C(b){let d=o.location.origin!=="null"?o.location.origin:o.location.href,s=typeof b=="string"?b:Nd(b);return dr(d,"No window.location.(origin|href) available to create URL for href: "+s),new URL(s,d)}let w={get action(){return l},get location(){return e(o,i)},listen(b){if(f)throw new Error("A history only accepts one active listener");return o.addEventListener(kx,S),f=b,()=>{o.removeEventListener(kx,S),f=null}},createHref(b){return t(o,b)},createURL:C,encodeLocation(b){let d=C(b);return{pathname:d.pathname,search:d.search,hash:d.hash}},push:O,replace:T,go(b){return i.go(b)}};return w}var jx;(function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"})(jx||(jx={}));function QM(e,t,r){r===void 0&&(r="/");let n=typeof t=="string"?Ps(t):t,o=Zl(n.pathname||"/",r);if(o==null)return null;let a=DC(e);XM(a);let i=null;for(let l=0;i==null&&l<a.length;++l)i=iI(a[l],lI(o));return i}function DC(e,t,r,n){t===void 0&&(t=[]),r===void 0&&(r=[]),n===void 0&&(n="");let o=(a,i,l)=>{let f={relativePath:l===void 0?a.path||"":l,caseSensitive:a.caseSensitive===!0,childrenIndex:i,route:a};f.relativePath.startsWith("/")&&(dr(f.relativePath.startsWith(n),'Absolute route path "'+f.relativePath+'" nested under path '+('"'+n+'" is not valid. An absolute child route path ')+"must start with the combined path of all its parent routes."),f.relativePath=f.relativePath.slice(n.length));let g=Oa([n,f.relativePath]),m=r.concat(f);a.children&&a.children.length>0&&(dr(a.index!==!0,"Index routes must not have child routes. Please remove "+('all child routes from route path "'+g+'".')),DC(a.children,t,m,g)),!(a.path==null&&!a.index)&&t.push({path:g,score:oI(g,a.index),routesMeta:m})};return e.forEach((a,i)=>{var l;if(a.path===""||!((l=a.path)!=null&&l.includes("?")))o(a,i);else for(let f of LC(a.path))o(a,i,f)}),t}function LC(e){let t=e.split("/");if(t.length===0)return[];let[r,...n]=t,o=r.endsWith("?"),a=r.replace(/\?$/,"");if(n.length===0)return o?[a,""]:[a];let i=LC(n.join("/")),l=[];return l.push(...i.map(f=>f===""?a:[a,f].join("/"))),o&&l.push(...i),l.map(f=>e.startsWith("/")&&f===""?"/":f)}function XM(e){e.sort((t,r)=>t.score!==r.score?r.score-t.score:aI(t.routesMeta.map(n=>n.childrenIndex),r.routesMeta.map(n=>n.childrenIndex)))}const ZM=/^:\w+$/,JM=3,eI=2,tI=1,rI=10,nI=-2,Ex=e=>e==="*";function oI(e,t){let r=e.split("/"),n=r.length;return r.some(Ex)&&(n+=nI),t&&(n+=eI),r.filter(o=>!Ex(o)).reduce((o,a)=>o+(ZM.test(a)?JM:a===""?tI:rI),n)}function aI(e,t){return e.length===t.length&&e.slice(0,-1).every((n,o)=>n===t[o])?e[e.length-1]-t[t.length-1]:0}function iI(e,t){let{routesMeta:r}=e,n={},o="/",a=[];for(let i=0;i<r.length;++i){let l=r[i],f=i===r.length-1,g=o==="/"?t:t.slice(o.length)||"/",m=Og({path:l.relativePath,caseSensitive:l.caseSensitive,end:f},g);if(!m)return null;Object.assign(n,m.paLine truncated
* React Router v6.17.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/function jd(){return jd=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},jd.apply(this,arguments)}const wf=P.createContext(null),zC=P.createContext(null),gi=P.createContext(null),_f=P.createContext(null),qo=P.createContext({outlet:null,matches:[],isDataRoute:!1}),BC=P.createContext(null);function bI(e,t){let{relative:r}=t===void 0?{}:t;Os()||dr(!1);let{basename:n,navigator:o}=P.useContext(gi),{hash:a,pathname:i,search:l}=Sf(e,{relative:r}),f=i;return n!=="/"&&(f=i==="/"?n:Oa([n,i])),o.createHref({pathname:f,search:l,hash:a})}function Os(){return P.useContext(_f)!=null}function Da(){return Os()||dr(!1),P.useContext(_f).location}function VC(e){P.useContext(gi).static||P.useLayoutEffect(e)}function Hn(){let{isDataRoute:e}=P.useContext(qo);return e?jI():vI()}function vI(){Os()||dr(!1);let e=P.useContext(wf),{basename:t,navigator:r}=P.useContext(gi),{matches:n}=P.useContext(qo),{pathname:o}=Da(),a=JSON.stringify(lm(n).map(f=>f.pathnameBase)),i=P.useRef(!1);return VC(()=>{i.current=!0}),P.useCallback(function(f,g){if(g===void 0&&(g={}),!i.current)return;if(typeof f=="number"){r.go(f);return}let m=um(f,JSON.parse(a),o,g.relative==="path");e==null&&t!=="/"&&(m.pathname=m.pathname==="/"?t:Oa([t,m.pathname])),(g.replace?r.replace:r.push)(m,g.state,g)},[t,r,a,o,e])}function cm(){let{matches:e}=P.useContext(qo),t=e[e.length-1];return t?t.params:{}}function Sf(e,t){let{relative:r}=t===void 0?{}:t,{matches:n}=P.useContext(qo),{pathname:o}=Da(),a=JSON.stringify(lm(n).map(i=>i.pathnameBase));return P.useMemo(()=>um(e,JSON.parse(a),o,r==="path"),[e,a,o,r])}function yI(e,t){return xI(e,t)}function xI(e,t,r){Os()||dr(!1);let{navigator:n}=P.useContext(gi),{matches:o}=P.useContext(qo),a=o[o.length-1],i=a?a.params:{};a&&a.pathname;let l=a?a.pathnameBase:"/";a&&a.route;let f=Da(),g;if(t){var m;let w=typeof t=="string"?Ps(t):t;l==="/"||(m=w.pathname)!=null&&m.startsWith(l)||dr(!1),g=w}else g=f;let S=g.pathname||"/",O=l==="/"?S:S.slice(l.length)||"/",T=QM(e,{pathname:O}),C=TI(T&&T.map(w=>Object.assign({},w,{params:Object.assign({},i,w.params),pathname:Oa([l,n.encodeLocation?n.encodeLocation(w.pathname).pathname:w.pathname]),pathnameBase:w.pathnameBase==="/"?l:Oa([l,n.encodeLocation?n.encodeLocation(w.pathnameBase).pathname:w.pathnameBase])})),o,r);return t&&C?P.createElement(_f.Provider,{value:{location:jd({pathname:"/",search:"",hash:"",state:null,key:"default"},g),navigationType:ba.Pop}},C):C}function wI(){let e=NI(),t=gI(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),r=e instanceof Error?e.stack:null,o={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"},a=null;return P.createElement(P.Fragment,null,P.createElement("h2",null,"Unexpected Application Error!"),P.createElement("h3",{style:{fontStyle:"italic"}},t),r?P.createElement("pre",{style:o},r):null,a)}const _I=P.createElement(wI,null);class SI extends P.Component{constructor(t){super(t),this.state={location:t.location,revalidation:t.revalidation,error:t.error}}static getDerivedStateFromError(t){return{error:t}}static getDerivedStateFromProps(t,r){return r.location!==t.location||r.revalidation!=="idle"&&t.revalidation==="idle"?{error:t.error,location:t.location,revalidation:t.revalidation}:{error:t.error||r.error,location:r.location,revalidation:t.revalidation||r.revalidation}}componentDidCatch(t,r){console.error("React Router caught the following error during render",t,r)}render(){return this.state.error?P.createElement(qo.Provider,{value:this.props.routeContext},P.createElement(BC.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function CI(e){let{routeContext:t,match:r,children:n}=e,o=P.useContext(wf);return o&&o.static&&o.staticContext&&(r.route.errorElement||r.route.ErrorBoundary)&&(o.staticContext._deepestRenderedBoundaryId=r.route.id),P.createElement(qo.Provider,{value:t},n)}function TI(e,t,r){var n;if(t===void 0&&(t=[]),r===void 0&&(r=null),e==null){var o;if((o=r)!=null&&o.errors)e=r.matches;else return null}let a=e,i=(n=r)==null?void 0:n.errors;if(i!=null){let l=a.findIndex(f=>f.route.id&&(i==null?void 0:i[f.route.id]));l>=0||dr(!1),a=a.slice(0,Math.min(a.length,l+1))}return a.reduceRight((l,f,g)=>{let m=f.route.id?i==null?void 0:i[f.route.id]:null,S=null;r&&(S=f.route.errorElement||_I);let O=t.concat(a.slice(0,g+1)),T=()=>{let C;return m?C=S:f.route.Component?C=P.createElement(f.route.Component,null):f.route.element?C=f.route.element:C=l,P.createElement(CI,{match:f,routeContext:{outlet:l,matches:O,isDataRoute:r!=null},children:C})};return r&&(f.route.ErrorBoundary||f.route.errorElement||g===0)?P.createElement(SI,{location:r.location,revalidation:r.revalidation,component:S,error:m,children:T(),routeContext:{outlet:null,matches:O,isDataRoute:!0}}):T()},null)}var UC=function(e){return e.UseBlocker="useBlocker",e.UseRevalidator="useRevalidator",e.UseNavigateStable=Line truncated
* React Router DOM v6.17.0
*
* Copyright (c) Remix Software Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE.md file in the root directory of this source tree.
*
* @license MIT
*/function Ad(){return Ad=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},Ad.apply(this,arguments)}function $C(e,t){if(e==null)return{};var r={},n=Object.keys(e),o,a;for(a=0;a<n.length;a++)o=n[a],!(t.indexOf(o)>=0)&&(r[o]=e[o]);return r}function RI(e){return!!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)}function MI(e,t){return e.button===0&&(!t||t==="_self")&&!RI(e)}const II=["onClick","relative","reloadDocument","replace","state","target","to","preventScrollReset","unstable_viewTransition"],DI=["aria-current","caseSensitive","className","end","style","to","unstable_viewTransition","children"],LI=P.createContext({isTransitioning:!1}),FI="startTransition",Ax=u0[FI];function zI(e){let{basename:t,children:r,future:n,window:o}=e,a=P.useRef();a.current==null&&(a.current=qM({window:o,v5Compat:!0}));let i=a.current,[l,f]=P.useState({action:i.action,location:i.location}),{v7_startTransition:g}=n||{},m=P.useCallback(S=>{g&&Ax?Ax(()=>f(S)):f(S)},[f,g]);return P.useLayoutEffect(()=>i.listen(m),[i,m]),P.createElement(AI,{basename:t,children:r,location:l.location,navigationType:l.action,navigator:i})}const BI=typeof window<"u"&&typeof window.document<"u"&&typeof window.document.createElement<"u",VI=/^(?:[a-z][a-z0-9+.-]*:|\/\/)/i,HC=P.forwardRef(function(t,r){let{onClick:n,relative:o,reloadDocument:a,replace:i,state:l,target:f,to:g,preventScrollReset:m,unstable_viewTransition:S}=t,O=$C(t,II),{basename:T}=P.useContext(gi),C,w=!1;if(typeof g=="string"&&VI.test(g)&&(C=g,BI))try{let c=new URL(window.location.href),u=g.startsWith("//")?new URL(c.protocol+g):new URL(g),h=Zl(u.pathname,T);u.origin===c.origin&&h!=null?g=h+u.search+u.hash:w=!0}catch{}let b=bI(g,{relative:o}),d=$I(g,{replace:i,state:l,target:f,preventScrollReset:m,relative:o,unstable_viewTransition:S});function s(c){n&&n(c),c.defaultPrevented||d(c)}return P.createElement("a",Ad({},O,{href:C||b,onClick:w||a?n:s,ref:r,target:f}))}),UI=P.forwardRef(function(t,r){let{"aria-current":n="page",caseSensitive:o=!1,className:a="",end:i=!1,style:l,to:f,unstable_viewTransition:g,children:m}=t,S=$C(t,DI),O=Sf(f,{relative:S.relative}),T=Da(),C=P.useContext(zC),{navigator:w}=P.useContext(gi),b=C!=null&&HI(O)&&g===!0,d=w.encodeLocation?w.encodeLocation(O).pathname:O.pathname,s=T.pathname,c=C&&C.navigation&&C.navigation.location?C.navigation.location.pathname:null;o||(s=s.toLowerCase(),c=c?c.toLowerCase():null,d=d.toLowerCase());let u=s===d||!i&&s.startsWith(d)&&s.charAt(d.length)==="/",h=c!=null&&(c===d||!i&&c.startsWith(d)&&c.charAt(d.length)==="/"),y={isActive:u,isPending:h,isTransitioning:b},x=u?n:void 0,E;typeof a=="function"?E=a(y):E=[a,u?"active":null,h?"pending":null,b?"transitioning":null].filter(Boolean).join(" ");let k=typeof l=="function"?l(y):l;return P.createElement(HC,Ad({},S,{"aria-current":x,className:E,ref:r,style:k,to:f,unstable_viewTransition:g}),typeof m=="function"?m(y):m)});var Ng;(function(e){e.UseScrollRestoration="useScrollRestoration",e.UseSubmit="useSubmit",e.UseSubmitFetcher="useSubmitFetcher",e.UseFetcher="useFetcher",e.useViewTransitionState="useViewTransitionState"})(Ng||(Ng={}));var Rx;(function(e){e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"})(Rx||(Rx={}));function WI(e){let t=P.useContext(wf);return t||dr(!1),t}function $I(e,t){let{target:r,replace:n,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:l}=t===void 0?{}:t,f=Hn(),g=Da(),m=Sf(e,{relative:i});return P.useCallback(S=>{if(MI(S,r)){S.preventDefault();let O=n!==void 0?n:Nd(g)===Nd(m);f(e,{replace:O,state:o,preventScrollReset:a,relative:i,unstable_viewTransition:l})}},[g,f,m,n,o,r,e,a,i,l])}function HI(e,t){t===void 0&&(t={});let r=P.useContext(LI);r==null&&dr(!1);let{basename:n}=WI(Ng.useViewTransitionState),o=Sf(e,{relative:t.relative});if(!r.isTransitioning)return!1;let a=Zl(r.currentLocation.pathname,n)||r.currentLocation.pathname,i=Zl(r.nextLocation.pathname,n)||r.nextLocation.pathname;return Og(o.pathname,i)!=null||Og(o.pathname,a)!=null}function GI({title:e,titleId:t,...r},n){return P.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",ref:n,"aria-labelledby":t},r),e?P.createElement("title",{id:t},e):null,P.createElement("path",{fillRule:"evenodd",d:"M7.5 3.75A1.5 1.5 0 006 5.25v13.5a1.5 1.5 0 001.5 1.5h6a1.5 1.5 0 001.5-1.5V15a.75.75 0 011.5 0v3.75a3 3 0 01-3 3h-6a3 3 0 01-3-3V5.25a3 3 0 013-3h6a3 3 0 013 3V9A.75.75 0 0115 9V5.25a1.5 1.5 0 00-1.5-1.5h-6zm5.03 4.72a.75.75 0 010 1.06l-1.72 1.72h10.94a.75.75 0 010 1.5H10.81l1.72 1.72a.75.75 0 11-1.06 1.06l-3-3a.75.75 0 010-1.06l3-3a.75.75 0 011.06 0z",clipRule:"evenodd"}))}const qI=P.forwardRef(GI),KI=qI;function YI({title:e,titleId:t,...r},n){return P.createElement("svg",Object.assign({xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",fill:"currentColor","aria-hidden":"true",rLine truncated
Copyright (c) 2018 Jed Watson.
Licensed under the MIT License (MIT), see
http://jedwatson.github.io/classnames
*/(function(e){(function(){var t={}.hasOwnProperty;function r(){for(var n=[],o=0;o<arguments.length;o++){var a=arguments[o];if(a){var i=typeof a;if(i==="string"||i==="number")n.push(a);else if(Array.isArray(a)){if(a.length){var l=r.apply(null,a);l&&n.push(l)}}else if(i==="object"){if(a.toString!==Object.prototype.toString&&!a.toString.toString().includes("[native code]")){n.push(a.toString());continue}for(var f in a)t.call(a,f)&&a[f]&&n.push(f)}}}return n.join(" ")}e.exports?(r.default=r,e.exports=r):window.classNames=r})()})(KC);var St=KC.exports,YC={exports:{}},Qn={};function fm(){for(var e,t,r=0,n="";r<arguments.length;)(e=arguments[r++])&&(t=QC(e))&&(n&&(n+=" "),n+=t);return n}function QC(e){if(typeof e=="string")return e;for(var t,r="",n=0;n<e.length;n++)e[n]&&(t=QC(e[n]))&&(r&&(r+=" "),r+=t);return r}function jg(){return jg=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var n in r)Object.prototype.hasOwnProperty.call(r,n)&&(e[n]=r[n])}return e},jg.apply(this,arguments)}function C4(e){var t=function(r){var n=r.theme,o=r.prefix,a={nextPart:new Map,validators:[]};return function(i,l){return l?i.map(function(f){return[f[0],f[1].map(function(g){return typeof g=="string"?l+g:typeof g=="object"?Object.fromEntries(Object.entries(g).map(function(m){return[l+m[0],m[1]]})):g})]}):i}(Object.entries(r.classGroups),o).forEach(function(i){Eg(i[1],a,i[0],n)}),a}(e);return{getClassGroupId:function(r){var n=r.split("-");return n[0]===""&&n.length!==1&&n.shift(),XC(n,t)||function(o){if(Mx.test(o)){var a=Mx.exec(o)[1],i=a==null?void 0:a.substring(0,a.indexOf(":"));if(i)return"arbitrary.."+i}}(r)},getConflictingClassGroupIds:function(r){return e.conflictingClassGroups[r]||[]}}}function XC(e,t){var r;if(e.length===0)return t.classGroupId;var n=t.nextPart.get(e[0]),o=n?XC(e.slice(1),n):void 0;if(o)return o;if(t.validators.length!==0){var a=e.join("-");return(r=t.validators.find(function(i){return(0,i.validator)(a)}))==null?void 0:r.classGroupId}}Object.defineProperty(Qn,"__esModule",{value:!0});var Mx=/^\[(.+)\]$/;function Eg(e,t,r,n){e.forEach(function(o){if(typeof o!="string"){if(typeof o=="function")return o.isThemeGetter?void Eg(o(n),t,r,n):void t.validators.push({validator:o,classGroupId:r});Object.entries(o).forEach(function(a){Eg(a[1],Ix(t,a[0]),r,n)})}else(o===""?t:Ix(t,o)).classGroupId=r})}function Ix(e,t){var r=e;return t.split("-").forEach(function(n){r.nextPart.has(n)||r.nextPart.set(n,{nextPart:new Map,validators:[]}),r=r.nextPart.get(n)}),r}function T4(e){if(e<1)return{get:function(){},set:function(){}};var t=0,r=new Map,n=new Map;function o(a,i){r.set(a,i),++t>e&&(t=0,n=r,r=new Map)}return{get:function(a){var i=r.get(a);return i!==void 0?i:(i=n.get(a))!==void 0?(o(a,i),i):void 0},set:function(a,i){r.has(a)?r.set(a,i):o(a,i)}}}function P4(e){var t=e.separator||":";return function(r){for(var n=0,o=[],a=0,i=0;i<r.length;i++){var l=r[i];n===0&&l===t[0]&&(t.length!==1&&r.slice(i,i+t.length)!==t||(o.push(r.slice(a,i)),a=i+t.length)),l==="["?n++:l==="]"&&n--}var f=o.length===0?r:r.substring(a),g=f.startsWith("!");return{modifiers:o,hasImportantModifier:g,baseClassName:g?f.substring(1):f}}}function O4(e){return jg({cache:T4(e.cacheSize),splitModifiers:P4(e)},C4(e))}var k4=/\s+/;function N4(e,t){var r=t.splitModifiers,n=t.getClassGroupId,o=t.getConflictingClassGroupIds,a=new Set;return e.trim().split(k4).map(function(i){var l=r(i),f=l.modifiers,g=l.hasImportantModifier,m=n(l.baseClassName);if(!m)return{isTailwindClass:!1,originalClassName:i};var S=function(O){if(O.length<=1)return O;var T=[],C=[];return O.forEach(function(w){w[0]==="["?(T.push.apply(T,C.sort().concat([w])),C=[]):C.push(w)}),T.push.apply(T,C.sort()),T}(f).join(":");return{isTailwindClass:!0,modifierId:g?S+"!":S,classGroupId:m,originalClassName:i}}).reverse().filter(function(i){if(!i.isTailwindClass)return!0;var l=i.modifierId,f=i.classGroupId,g=l+f;return!a.has(g)&&(a.add(g),o(f).forEach(function(m){return a.add(l+m)}),!0)}).reverse().map(function(i){return i.originalClassName}).join(" ")}function Ag(){for(var e=arguments.length,t=new Array(e),r=0;r<e;r++)t[r]=arguments[r];var n,o,a,i=l;function l(g){var m=t[0],S=t.slice(1).reduce(function(O,T){return T(O)},m());return n=O4(S),o=n.cache.get,a=n.cache.set,i=f,f(g)}function f(g){var m=o(g);if(m)return m;var S=N4(g,n);return a(g,S),S}return function(){return i(fm.apply(null,arguments))}}function tr(e){var t=function(r){return r[e]||[]};return t.isThemeGetter=!0,t}var La=/^\[(.+)\]$/,j4=/^\d+\/\d+$/,E4=new Set(["px","full","screen"]),A4=/^(\d+)?(xs|sm|md|lg|xl)$/,R4=/\d+(%|px|r?em|[sdl]?v([hwib]|min|max)|pt|pc|in|cm|mm|cap|ch|ex|r?lh)/,M4=/^-?((\d+)?\.?(\d+)[a-z]+|0)_-?((\d+)?\.?(\d+)[a-z]+|0)/;function Mn(e){return!Number.isNaN(Number(e))||E4.has(e)||j4.test(e)||fa(e)}function fa(e){var t,r=(t=La.exec(e))==null?void 0:t[1];return!!r&&(r.startsWith("length:")||R4.test(r))}function ZC(e){var t,r=(t=La.exec(e))==null?void 0:t[1];return!!r&&r.startsWLine truncated
* tabbable 6.2.0
* @license MIT, https://github.com/focus-trap/tabbable/blob/master/LICENSE
*/var CF=["input:not([inert])","select:not([inert])","textarea:not([inert])","a[href]:not([inert])","button:not([inert])","[tabindex]:not(slot):not([inert])","audio[controls]:not([inert])","video[controls]:not([inert])",'[contenteditable]:not([contenteditable="false"]):not([inert])',"details>summary:first-of-type:not([inert])","details:not([inert])"],qg=CF.join(","),aO=typeof Element>"u",ou=aO?function(){}:Element.prototype.matches||Element.prototype.msMatchesSelector||Element.prototype.webkitMatchesSelector,Ud=!aO&&Element.prototype.getRootNode?function(e){var t;return e==null||(t=e.getRootNode)===null||t===void 0?void 0:t.call(e)}:function(e){return e==null?void 0:e.ownerDocument},Wd=function e(t,r){var n;r===void 0&&(r=!0);var o=t==null||(n=t.getAttribute)===null||n===void 0?void 0:n.call(t,"inert"),a=o===""||o==="true",i=a||r&&t&&e(t.parentNode);return i},TF=function(t){var r,n=t==null||(r=t.getAttribute)===null||r===void 0?void 0:r.call(t,"contenteditable");return n===""||n==="true"},PF=function(t,r,n){if(Wd(t))return[];var o=Array.prototype.slice.apply(t.querySelectorAll(qg));return r&&ou.call(t,qg)&&o.unshift(t),o=o.filter(n),o},OF=function e(t,r,n){for(var o=[],a=Array.from(t);a.length;){var i=a.shift();if(!Wd(i,!1))if(i.tagName==="SLOT"){var l=i.assignedElements(),f=l.length?l:i.children,g=e(f,!0,n);n.flatten?o.push.apply(o,g):o.push({scopeParent:i,candidates:g})}else{var m=ou.call(i,qg);m&&n.filter(i)&&(r||!t.includes(i))&&o.push(i);var S=i.shadowRoot||typeof n.getShadowRoot=="function"&&n.getShadowRoot(i),O=!Wd(S,!1)&&(!n.shadowRootFilter||n.shadowRootFilter(i));if(S&&O){var T=e(S===!0?i.children:S.children,!0,n);n.flatten?o.push.apply(o,T):o.push({scopeParent:i,candidates:T})}else a.unshift.apply(a,i.children)}}return o},iO=function(t){return!isNaN(parseInt(t.getAttribute("tabindex"),10))},sO=function(t){if(!t)throw new Error("No node provided");return t.tabIndex<0&&(/^(AUDIO|VIDEO|DETAILS)$/.test(t.tagName)||TF(t))&&!iO(t)?0:t.tabIndex},kF=function(t,r){var n=sO(t);return n<0&&r&&!iO(t)?0:n},NF=function(t,r){return t.tabIndex===r.tabIndex?t.documentOrder-r.documentOrder:t.tabIndex-r.tabIndex},lO=function(t){return t.tagName==="INPUT"},jF=function(t){return lO(t)&&t.type==="hidden"},EF=function(t){var r=t.tagName==="DETAILS"&&Array.prototype.slice.apply(t.children).some(function(n){return n.tagName==="SUMMARY"});return r},AF=function(t,r){for(var n=0;n<t.length;n++)if(t[n].checked&&t[n].form===r)return t[n]},RF=function(t){if(!t.name)return!0;var r=t.form||Ud(t),n=function(l){return r.querySelectorAll('input[type="radio"][name="'+l+'"]')},o;if(typeof window<"u"&&typeof window.CSS<"u"&&typeof window.CSS.escape=="function")o=n(window.CSS.escape(t.name));else try{o=n(t.name)}catch(i){return console.error("Looks like you have a radio button with a name attribute containing invalid CSS selector characters and need the CSS.escape polyfill: %s",i.message),!1}var a=AF(o,t.form);return!a||a===t},MF=function(t){return lO(t)&&t.type==="radio"},IF=function(t){return MF(t)&&!RF(t)},DF=function(t){var r,n=t&&Ud(t),o=(r=n)===null||r===void 0?void 0:r.host,a=!1;if(n&&n!==t){var i,l,f;for(a=!!((i=o)!==null&&i!==void 0&&(l=i.ownerDocument)!==null&&l!==void 0&&l.contains(o)||t!=null&&(f=t.ownerDocument)!==null&&f!==void 0&&f.contains(t));!a&&o;){var g,m,S;n=Ud(o),o=(g=n)===null||g===void 0?void 0:g.host,a=!!((m=o)!==null&&m!==void 0&&(S=m.ownerDocument)!==null&&S!==void 0&&S.contains(o))}}return a},uw=function(t){var r=t.getBoundingClientRect(),n=r.width,o=r.height;return n===0&&o===0},LF=function(t,r){var n=r.displayCheck,o=r.getShadowRoot;if(getComputedStyle(t).visibility==="hidden")return!0;var a=ou.call(t,"details>summary:first-of-type"),i=a?t.parentElement:t;if(ou.call(i,"details:not([open]) *"))return!0;if(!n||n==="full"||n==="legacy-full"){if(typeof o=="function"){for(var l=t;t;){var f=t.parentElement,g=Ud(t);if(f&&!f.shadowRoot&&o(f)===!0)return uw(t);t.assignedSlot?t=t.assignedSlot:!f&&g!==t.ownerDocument?t=g.host:t=f}t=l}if(DF(t))return!t.getClientRects().length;if(n!=="legacy-full")return!0}else if(n==="non-zero-area")return uw(t);return!1},FF=function(t){if(/^(INPUT|BUTTON|SELECT|TEXTAREA)$/.test(t.tagName))for(var r=t.parentElement;r;){if(r.tagName==="FIELDSET"&&r.disabled){for(var n=0;n<r.children.length;n++){var o=r.children.item(n);if(o.tagName==="LEGEND")return ou.call(r,"fieldset[disabled] *")?!0:!o.contains(t)}return!0}r=r.parentElement}return!1},zF=function(t,r){return!(r.disabled||Wd(r)||jF(r)||LF(r,t)||EF(r)||FF(r))},cw=function(t,r){return!(IF(r)||sO(r)<0||!zF(t,r))},BF=function(t){var r=parseInt(t.getAttribute("tabindex"),10);return!!(isNaN(r)||r>=0)},VF=function e(t){var r=[],n=[];return t.forEach(function(o,a){var i=!!o.scopeParent,l=i?o.scopeParent:o,f=kF(l,i),g=i?e(o.candidates):l;f===0?i?r.push.apply(r,g):r.push(l):n.push({documentOrder:a,tabIndex:f,item:o,isScope:i,content:g})}),n.sort(NF).reduce(function(o,a){return a.isScope?o.push.apply(o,a.content):o.push(a.content),o},[]).concatLine truncated
More details: https://www.material-tailwind.com/docs/react/collapse
`);var k=r.default.useRef(null),j=(0,g.useTheme)().navbar,A=j.styles,M=A.base.mobileNav;u=u??{},h=h??"";var D=(0,l.twMerge)((0,i.default)((0,f.default)(M)),h),L={unmount:{height:0,opacity:0,transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}},mount:{opacity:1,height:"".concat((E=k.current)===null||E===void 0?void 0:E.scrollHeight,"px"),transition:{duration:.3,times:"[0.4, 0, 0.2, 1]"}}},z=(0,a.default)(L,u),H=n.AnimatePresence,Y=(0,o.useMergeRefs)([s,k]);return r.default.createElement(n.LazyMotion,{features:n.domAnimation},r.default.createElement(H,null,r.default.createElement(n.m.div,S({},x,{ref:Y,className:D,initial:"unmount",exit:"unmount",animate:c?"mount":"unmount",variants:z}),y)))});w.displayName="MaterialTailwind.MobileNav",w.propTypes={open:m.propTypesOpen,animate:m.propTypesAnimate,className:m.propTypesClassName,children:m.propTypesChildren};var b=w})(XO);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(s,c){for(var u in c)Object.defineProperty(s,u,{enumerable:!0,get:c[u]})}t(e,{Navbar:function(){return b},MobileNav:function(){return g.MobileNav},default:function(){return d}});var r=T(P),n=T(vt),o=T(St),a=bt,i=T(sr),l=T(ht),f=ut,g=XO,m=Cf;function S(s,c,u){return c in s?Object.defineProperty(s,c,{value:u,enumerable:!0,configurable:!0,writable:!0}):s[c]=u,s}function O(){return O=Object.assign||function(s){for(var c=1;c<arguments.length;c++){var u=arguments[c];for(var h in u)Object.prototype.hasOwnProperty.call(u,h)&&(s[h]=u[h])}return s},O.apply(this,arguments)}function T(s){return s&&s.__esModule?s:{default:s}}function C(s,c){if(s==null)return{};var u=w(s,c),h,y;if(Object.getOwnPropertySymbols){var x=Object.getOwnPropertySymbols(s);for(y=0;y<x.length;y++)h=x[y],!(c.indexOf(h)>=0)&&Object.prototype.propertyIsEnumerable.call(s,h)&&(u[h]=s[h])}return u}function w(s,c){if(s==null)return{};var u={},h=Object.keys(s),y,x;for(x=0;x<h.length;x++)y=h[x],!(c.indexOf(y)>=0)&&(u[y]=s[y]);return u}var b=r.default.forwardRef(function(s,c){var u=s.variant,h=s.color,y=s.shadow,x=s.blurred,E=s.fullWidth,k=s.className,j=s.children,A=C(s,["variant","color","shadow","blurred","fullWidth","className","children"]),M=(0,f.useTheme)().navbar,D=M.defaultProps,L=M.valid,z=M.styles,H=z.base,Y=z.variants;u=u??D.variant,h=h??D.color,y=y??D.shadow,x=x??D.blurred,E=E??D.fullWidth,k=k??D.className;var G,B=(0,o.default)((0,l.default)(H.navbar.initial),(G={},S(G,(0,l.default)(H.navbar.shadow),y),S(G,(0,l.default)(H.navbar.blurred),x&&h==="white"),S(G,(0,l.default)(H.navbar.fullWidth),E),G)),$=(0,o.default)((0,l.default)(Y[(0,i.default)(L.variants,u,"filled")][(0,i.default)(L.colors,h,"white")])),q=(0,a.twMerge)((0,o.default)(B,$),k);return r.default.createElement("nav",O({},A,{ref:c,className:q}),j)});b.propTypes={variant:n.default.oneOf(m.propTypesVariant),color:n.default.oneOf(m.propTypesColor),shadow:m.propTypesShadow,blurred:m.propTypesBlurred,fullWidth:m.propTypesFullWidth,className:m.propTypesClassName,children:m.propTypesChildren},b.displayName="MaterialTailwind.Navbar";var d=Object.assign(b,{MobileNav:g.MobileNav})})(QO);var ZO={},Hf={},Is={};(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(b,d){for(var s in d)Object.defineProperty(b,s,{enumerable:!0,get:d[s]})}t(e,{propTypesOpen:function(){return a},propTypesHandler:function(){return i},propTypesPlacement:function(){return l},propTypesOffset:function(){return f},propTypesDismiss:function(){return g},propTypesAnimate:function(){return m},propTypesContent:function(){return S},propTypesInteractive:function(){return O},propTypesClassName:function(){return T},propTypesChildren:function(){return C},propTypesContextValue:function(){return w}});var r=o(vt),n=nr;function o(b){return b&&b.__esModule?b:{default:b}}var a=r.default.bool,i=r.default.func,l=n.propTypesPlacements,f=n.propTypesOffsetType,g=n.propTypesDismissType,m=n.propTypesAnimation,S=r.default.node,O=r.default.bool,T=r.default.string,C=r.default.node.isRequired,w=r.default.shape({open:r.default.bool.isRequired,strategy:r.default.oneOf(["fixed","absolute"]).isRequired,x:r.default.number,y:r.default.number,context:r.default.instanceOf(Object).isRequired,reference:r.default.func.isRequired,floating:r.default.func.isRequired,getReferenceProps:r.default.func.isRequired,getFloatingProps:r.default.func.isRequired,appliedAnimation:m.isRequired,labelId:r.default.string.isRequired,descriptionId:r.default.string.isRequired}).isRequired})(Is);(function(e){Object.defineProperty(e,"__esModule",{value:!0});function t(f,g){for(var m in g)Object.defineProperty(f,m,{enumerable:!0,get:g[m]})}t(e,{PopoverContext:function(){return a},usePopover:function(){return i},PopoverContextProvider:function(){return l}});var r=o(P),n=Is;function o(f){return f&&f.__esModule?f:{default:f}}var a=r.default.createContext(null);a.displayName="MaterialTailwind.PopoverContext";function i(){var f=r.default.useContext(a);if(!f)throw new Error("usePopover() must be used within a Popover. It happens when you us Line truncated
${ce?"border-purple-500 ring-1 ring-purple-500/40":"border-transparent"}
${!Ne||se?"opacity-75 bg-stormGrey-200/20":""}`,children:[v.jsxs("div",{className:"flex items-center space-x-4",id:"left",children:[k&&v.jsx("input",{type:"checkbox",checked:ce,onChange:()=>Oe(Z.Id,!ct),className:`h-5 w-5 text-purple-500 focus:ring-purple-400 border-gray-600 bg-gray-900 rounded ${ct?"":"opacity-40 cursor-not-allowed"}`,disabled:!ct}),v.jsx("div",{className:`w-12 h-12 flex items-center justify-center rounded-full
${Z.updateStatus==="✅"?"bg-mossGreen-600":Z.updateStatus==="⚠️"?"bg-warmAmberGlow-400":"bg-sunsetCoral-500"}`}),v.jsxs("div",{children:[v.jsx("p",{className:"text-lg antialiased font-sans font-semibold leading-normal text-inherit",children:Z.Name}),v.jsxs("p",{className:"text-sm antialiased font-sans font-light leading-normal text-inherit",children:["ID: ",Z.Id]}),Z.duplicateName&&v.jsx("p",{className:"text-xs text-amber-700",children:"⚠️ Doppelter Name erkannt"})]})]}),v.jsx("div",{className:"ml-auto flex flex-col items-end gap-1 text-sm",id:"right",children:xe?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),v.jsx("span",{className:"text-warmAmberGlow-500",children:"läuft…"})]}):ne?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-warmAmberGlow-600",children:"Redeploy"}),v.jsx("span",{className:"text-warmAmberGlow-500",children:"in Warteliste…"})]}):Ie?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"System"}),v.jsx("span",{className:"text-gray-300",children:"Redeploy deaktiviert"})]}):Ce?v.jsxs(v.Fragment,{children:[v.jsx("span",{className:"antialiased font-sans font-light leading-normal text-xs uppercase tracking-wide text-stormGrey-500",children:"Status"}),v.jsx("span",{className:"text-mossGreen-600",children:"Aktuell"})]}):E?v.jsx(V.Button,{onClick:()=>qe(Z.Id),disabled:L||se,className:"disabled:opacity-50 disabled:cursor-not-allowed",children:"Redeploy"}):null})]})},Z.Id)})}),v.jsx(ku,{disabled:r||!!o})]})}const nV={initializing:"Vorbereitung","activating-maintenance":"Wartungsmodus aktivieren","executing-script":"Skript wird ausgeführt",waiting:"Warte auf Portainer",completed:"Abgeschlossen",failed:"Fehlgeschlagen"},Ri=e=>{if(!e&&e!==0)return"-";const r=(n=>{if(n instanceof Date)return n;if(typeof n=="number"){const o=n>1e12?n:n*1e3;return new Date(o)}if(typeof n=="string"){const o=Number(n);if(!Number.isNaN(o)){const a=o>1e12?o:o*1e3;return new Date(a)}return new Date(n)}return null})(e);return!r||Number.isNaN(r.getTime())?String(e):r.toLocaleString("de-DE",{year:"numeric",month:"2-digit",day:"2-digit",hour:"2-digit",minute:"2-digit",second:"2-digit"})},Hw=()=>({host:"",port:"22",username:"",password:"",extraSshArgs:""});function oV(){var Mu,Iu;const e=Hn(),{hasPermission:t,user:r,serverAssignments:n}=Yr(),{showToast:o}=Fa(),{maintenance:a,update:i,script:l,ssh:f,loading:g,error:m,setMaintenanceMode:S,triggerUpdate:O,saveScript:T,resetScript:C,saveSshConfig:w,deleteSshConfig:b,testSshConnection:d,fetchSetupStatus:s,deleteSetupServer:c,updateSetupApiKey:u,updateSelfStackId:h,removeSuperuserAccount:y}=Yo(),x=!!(r!=null&&r.isSuperuser),E=!!(x||t("maintenance-access","read")),k=!!(x||t("maintenance-access","full")),j=!!(x||t("maintenance-server-manage","read")),A=!!(x||t("maintenance-server-edit","full")),M=!!(x||t("maintenance-server-delete","full")),D=!!(x||t("maintenance-superuser-delete","full")),L=!!(x||t("maintenance-mtls","full")),z=L,H=!1,Y=!1,G=!!(a!=null&&a.active),B=a==null?void 0:a.message,$=(Mu=a==null?void 0:a.extra)==null?void 0:Mu.type,q=!!(i!=null&&i.running),ee=G||q,[oe,ge]=P.useState({available:!1,source:null,updatedAt:null,validTo:null}),[fe,re]=P.useState(null),[be,he]=P.useState(""),[pe,ve]=P.useState(!1),[me,ke]=P.useState(!1),[Fe,Pe]=P.useState(""),[te,Oe]=P.useState({key:"",cert:""}),[$e,Be]=P.useState(!1),[We,lt]=P.useState(""),[qe,Ke]=P.useState(!1),xt=P.useRef([]),[rt,dt]=P.useState("");P.useState(!1),P.useState(!1),P.useState("");const[Rt,Ae]=P.useState(!1),ft=P.useCallback(async()=>{var Ee,Xe,et,Ze,it;if(!(!z||!be)){ve(!0),Pe("");try{const Ye=await Je.post("/api/maintenance/mtls-server-credentials/reveal",{password:be});re({key:((Ee=Ye.data)==null?void 0:Ee.key)||"",cert:((Xe=Ye.data)==null?void 0:Xe.cert)||"",source:((et=Ye.data)==null?void 0:et.source)||oe.source})}catch(Ye){const Ut=((it=(Ze=Ye.response)==null?void 0:Ze.data)==null?void 0:it.error)||Ye.message||"Zugang fehlgeschlagen";Pe(Ut),re(null)}finally{ve(!1)}}},[z,be,oe.source]),At=P.useCallback(async()=>{var Ee,Xe;if(!(!z||!be)){ke(!0),Pe("");try{const et=await Je.post("/api/maintenance/mtls-server-credentials/download",{password:be},{responseType:"blob"}),Ze=new Blob([et.data],{type:"application/zip"}),it=window.URL.createObjectURL(Ze),Ye=document.createElement("a");Ye.href=it,Ye.download="mtls-server-credentials.zip",document.body.appendChild(Ye),Ye.click(),document.body.removeChild(Ye),window.URL.revokeObjectURL(it)}catch(et){const Ze=((Xe=(Ee=et.response)==null?void 0:Ee.data)==null?void 0:Xe.error)||et.message||"Download fehlgeschlagen";Pe(Ze)}finally{ke(!1)}}},[z,be]),Z=P.useCallback(async()=>{var et,Ze;if(!z)return;const{key:Ee,cert:Xe}=te;if Line truncated
`):""}),ce(!!f.passwordStored),Ie(!1),ct(null)},[f]),P.useEffect(()=>{if(!L)return;(async()=>{var Xe,et,Ze,it;try{const Ye=await Je.get("/api/maintenance/mtls-server-credentials/meta");ge({available:!!((Xe=Ye.data)!=null&&Xe.available),source:((et=Ye.data)==null?void 0:et.source)||null,updatedAt:((Ze=Ye.data)==null?void 0:Ze.updatedAt)||null,validTo:((it=Ye.data)==null?void 0:it.validTo)||null})}catch{ge({available:!1,source:null,updatedAt:null,validTo:null})}})()},[L]),P.useMemo(()=>l?l.source==="custom"&&typeof l.custom=="string"?l.custom:l.default??l.effective??"":"",[l]),l==null||l.source;const[Ve,tt]=P.useState(!0),[wt,Vt]=P.useState(!1),[He,Yt]=P.useState(null),[It,Dt]=P.useState(""),[kt,Gt]=P.useState(!1),[Ot,hr]=P.useState(null),[_r,kr]=P.useState(!0),[Te,Me]=P.useState(""),[Ge,gt]=P.useState(""),[X,_e]=P.useState(""),[ye,ot]=P.useState(""),[at,Ct]=P.useState(""),[Tt,Zt]=P.useState(!1),[Ft,Mr]=P.useState({}),[Ir,br]=P.useState(null),[Vr,go]=P.useState(null),[vr,Gn]=P.useState(""),[za,on]=P.useState(!1),[Dr,Sr]=P.useState(""),Qr=P.useMemo(()=>{var it;const Ee=Array.isArray((it=Ot==null?void 0:Ot.servers)==null?void 0:it.items)?Ot.servers.items:[],et=(Array.isArray(n)&&n.length?n:Array.isArray(r==null?void 0:r.serverAssignments)?r.serverAssignments:[]).map(Ye=>Number((Ye==null?void 0:Ye.serverId)??(Ye==null?void 0:Ye.server_id))).filter(Ye=>Number.isFinite(Ye)&&Ye>0);if(et.length===0)return Ee;const Ze=new Set(et);return Ee.filter(Ye=>Ze.has(Number(Ye.id)))},[Ot,r]);P.useMemo(()=>{var Xe;const Ee=((Xe=Ot==null?void 0:Ot.apiKeys)==null?void 0:Xe.items)??[];return new Map(Ee.map(et=>[et.serverId,et]))},[Ot]),P.useMemo(()=>!!(Ot!=null&&Ot.setupComplete),[Ot]);const mo=(Ot==null?void 0:Ot.selfStack)??null,Nn=(mo==null?void 0:mo.current)??"";(Iu=Ot==null?void 0:Ot.envDefaults)==null||Iu.selfStackId;const{page:gn,perPage:je,perPageOptions:Le,perPageIsAll:ze,handlePerPageChange:Ue,setPage:pt,setTotals:Lt,resetPagination:or}=Fs();P.useEffect(()=>()=>or(),[or]);const[gr,mn]=P.useState(""),[jn,Ba]=P.useState(null),[_i,Nu]=P.useState(!1),[Sb,ju]=P.useState(""),[Cb,zs]=P.useState(!1),[Qo,Si]=P.useState(null),Va=P.useRef(null),Xo=P.useCallback(async()=>{var Ee,Xe,et,Ze;tt(!0),Dt("");try{const it=await Je.get("/api/auth/superuser/status"),Ye=!!((Ee=it.data)!=null&&Ee.exists);Vt(Ye),Yt(Ye?((Xe=it.data)==null?void 0:Xe.user)??null:null)}catch(it){const Ye=((Ze=(et=it.response)==null?void 0:et.data)==null?void 0:Ze.error)||it.message||"Superuser-Status konnte nicht geladen werden";Vt(!1),Yt(null),Dt(Ye)}finally{tt(!1)}},[]),Xr=P.useCallback(async({silent:Ee=!1}={})=>{var Xe,et;Ee||kr(!0),Me("");try{const Ze=await s();hr(Ze),Mr({})}catch(Ze){const it=((et=(Xe=Ze.response)==null?void 0:Xe.data)==null?void 0:et.error)||Ze.message||"Setup-Daten konnten nicht geladen werden";Me(it),Ee||o({variant:"error",title:"Setup-Daten",description:it})}finally{kr(!1)}},[s,o]);P.useCallback(async({silent:Ee=!1}={})=>{if(Va.current)return Va.current;const Xe=(async()=>{var et,Ze;Ee?zs(!0):Nu(!0),ju("");try{const Ye=(await Je.get("/api/maintenance/portainer-status")).data??{};Ba(Ye),Si(new Date)}catch(it){const Ye=((Ze=(et=it.response)==null?void 0:et.data)==null?void 0:Ze.error)||it.message||"Fehler beim Prüfen des Portainer-Status";ju(Ye),o({variant:"error",title:"Statusaktualisierung fehlgeschlagen",description:Ye})}finally{Ee?zs(!1):Nu(!1),Va.current=null}})();return Va.current=Xe,Xe},[o]),P.useEffect(()=>{Xo({silent:!0})},[Xo]),P.useEffect(()=>{Xr()},[Xr]);const bo=P.useMemo(()=>{const Ee=gr.trim().toLowerCase();return Ee?Qr.filter(Xe=>[Xe.name,Xe.url].map(Ze=>String(Ze||"").toLowerCase()).some(Ze=>Ze.includes(Ee))):Qr},[gr,Qr]);P.useEffect(()=>{Lt(bo.length)},[bo.length,Lt]);const Ua=P.useMemo(()=>{if(ze)return bo;const Ee=(gn-1)*je;return bo.slice(Ee,Ee+je)},[bo,gn,je,ze]);P.useEffect(()=>{Gn(Nn||""),Sr("")},[Nn]);const Bs=vr!==Nn,Qf=P.useCallback(Ee=>{mn(Ee.target.value)},[]);P.useEffect(()=>{gn!==1&&pt(1)},[gr,gn,pt]);const Eu=P.useCallback(async()=>{var Ze,it,Ye,Ut;if(!A||Tt)return;const Ee=(Ge||"").trim(),Xe=(X||"").trim(),et=(ye||"").trim();if(!Xe||!et){Ct("Bitte Name, URL und API-Key angeben.");return}Ct(""),Zt(!0);try{await Je.post("/api/setup/servers",{name:Ee,url:Xe,apiKey:et}),gt(""),_e(""),ot(""),await Xr({silent:!0})}catch(qn){const Ti=((it=(Ze=qn.response)==null?void 0:Ze.data)==null?void 0:it.message)||((Ut=(Ye=qn.response)==null?void 0:Ye.data)==null?void 0:Ut.error)||qn.message||"Server konnte nicht angelegt werden.";Ct(Ti)}finally{Zt(!1)}},[A,ye,Ge,X,Tt,Xr]),Ci=P.useCallback(()=>{Tt||(gt(""),_e(""),ot(""),Ct(""))},[Tt]);P.useCallback((Ee,Xe)=>{},[Y]);const Au=P.useMemo(()=>{const Ee={host:ne.host.trim(),port:Number.parseInt(ne.port,10)||22,username:ne.username.trim(),extraSshArgs:(ne.extraSshArgs||"").split(/\r?\n/).map(et=>et.trim()).filter(Boolean)},Xe=ne.password??"";return Xe?Ee.password=Xe:se||(Ee.password=""),Ee},[ne,se]);P.useCallback(async()=>{},[Y,T,rt,l,o]),P.useCallback(async()=>{},[Y,C,o]),P.useCallb Line truncated
Dies setzt die Superuser-Einrichtung zurück.`)))try{Gt(!0);const Ze=await y(),it=Number((Ze==null?void 0:Ze.usersRemoved)??0);o({variant:"success",title:"Superuser gelöscht",description:it>1?`${it} Konten aus der Superuser-Gruppe wurden entfernt.`:"Superuser-Konto wurde entfernt."}),await Xo({silent:!0})}catch(Ze){if(((Ee=Ze.response)==null?void 0:Ee.status)===404)await Xo({silent:!0}),o({variant:"info",title:"Kein Superuser vorhanden",description:"Es ist kein Superuser mehr vorhanden."});else{const Ye=((et=(Xe=Ze.response)==null?void 0:Xe.data)==null?void 0:et.error)||Ze.message||"Superuser konnte nicht gelöscht werden";o({variant:"error",title:"Löschen fehlgeschlagen",description:Ye})}}finally{Gt(!1)}},[D,kt,Ve,wt,y,Xo,o]),Vs=P.useCallback(async Ee=>{var it,Ye;if(!M||Vr===Ee)return;const Xe=Qr.find(Ut=>Ut.id===Ee);if(!Xe)return;const et=Xe.name||Xe.url||`Server ${Ee}`,Ze=[`Server "${et}" wirklich löschen?`,"Das System benötigt anschließend erneut einen gültigen Server im Setup."].filter(Boolean).join(`
`);if(!(typeof window<"u"&&!window.confirm(Ze))){go(Ee);try{await c(Ee),o({variant:"success",title:"Server gelöscht",description:`Server "${et}" wurde entfernt.`}),await Xr({silent:!0})}catch(Ut){const qn=((Ye=(it=Ut.response)==null?void 0:it.data)==null?void 0:Ye.error)||Ut.message||"Server konnte nicht gelöscht werden.";o({variant:"error",title:"Server löschen fehlgeschlagen",description:qn})}finally{go(null)}}},[M,c,Xr,Vr,Qr,o]);P.useCallback((Ee,Xe)=>{A&&Mr(et=>({...et,[Ee]:Xe}))},[A]),P.useCallback(async Ee=>{var et,Ze;if(!A)return;const Xe=(Ft[Ee]??"").trim();if(!Xe){o({variant:"warning",title:"API-Key fehlt",description:"Bitte gib einen API-Key ein."});return}if(Ir!==Ee){br(Ee);try{await u(Ee,Xe),o({variant:"success",title:"API-Key aktualisiert",description:"Der API-Key wurde gespeichert."}),Mr(it=>({...it,[Ee]:""})),await Xr({silent:!0})}catch(it){const Ye=((Ze=(et=it.response)==null?void 0:et.data)==null?void 0:Ze.error)||it.message||"API-Key konnte nicht aktualisiert werden.";o({variant:"error",title:"Aktualisierung fehlgeschlagen",description:Ye})}finally{br(null)}}},[Ft,Ir,A,Xr,o,u]),P.useCallback(Ee=>{A&&(Gn(Ee),Sr(""))},[A]),P.useCallback(async()=>{var Xe,et;if(!A||!Bs)return;on(!0),Sr("");const Ee=typeof vr=="string"?vr.trim():"";try{await h(Ee),o({variant:"success",title:"Self-Stack-ID gespeichert",description:Ee?`Self-Stack-ID wurde auf "${Ee}" gesetzt.`:"Self-Stack-ID wurde entfernt."}),await Xr({silent:!0})}catch(Ze){const it=((et=(Xe=Ze.response)==null?void 0:Xe.data)==null?void 0:et.error)||Ze.message||"Self-Stack-ID konnte nicht aktualisiert werden.";Sr(it),o({variant:"error",title:"Self-Stack-ID",description:it})}finally{on(!1)}},[A,Xr,Bs,vr,o,h]),P.useCallback(async()=>{var Ee,Xe;if(A&&!(!vr&&!Nn)&&!(typeof window<"u"&&!window.confirm("Self-Stack-ID wirklich entfernen?"))){on(!0),Sr("");try{await h(""),Gn(""),o({variant:"success",title:"Self-Stack-ID entfernt",description:"Die Self-Stack-ID wurde gelöscht."}),await Xr({silent:!0})}catch(et){const Ze=((Xe=(Ee=et.response)==null?void 0:Ee.data)==null?void 0:Xe.error)||et.message||"Self-Stack-ID konnte nicht entfernt werden.";Sr(Ze),o({variant:"error",title:"Self-Stack-ID",description:Ze})}finally{on(!1)}}},[A,Nn,Xr,vr,o,h]);const Us=P.useCallback(async Ee=>{var Xe,et;if(k&&!(g||Rt)&&Ee!==G){if(q){o({variant:"warning",title:"Aktion nicht möglich",description:"Während eines laufenden Updates kann der Wartungsmodus nicht geändert werden."});return}Ae(!0);try{const Ze=Ee&&!!$,it={active:Ee};Ze&&B?it.message=B:it.message=null,await S(it),o({variant:Ee?"info":"success",title:Ee?"Wartungsmodus aktiviert":"Wartungsmodus deaktiviert",description:Ee?"Benutzer sehen jetzt die Wartungsseite.":"StackPulse steht wieder zur Verfügung."})}catch(Ze){const it=((et=(Xe=Ze.response)==null?void 0:Xe.data)==null?void 0:et.error)||Ze.message||"Wartungsmodus konnte nicht geändert werden";o({variant:"error",title:"Änderung fehlgeschlagen",description:it})}finally{Ae(!1)}}},[k,g,Rt,G,q,S,B,$,o]);P.useCallback(async()=>{},[H,G,q,jn,O,o]),a!=null&&a.activatedAt&&Ri(a.activatedAt),a!=null&&a.updatedAt&&Ri(a.updatedAt),i==null||i.status;const Ws=i!=null&&i.stage?nV[i.stage]??i.stage:"–";return i==null||i.logs,i==null||i.targetVersion,i==null||i.resultVersion,i!=null&&i.startedAt&&Ri(i.startedAt),i!=null&&i.finishedAt&&Ri(i.finishedAt),i==null||i.message,E?v.jsxs("div",{className:"mt-12 mb-8 flex flex-col gap-12",children:[(G||q)&&v.jsx("div",{className:"rounded-lg border border-cyan-500/60 bg-cyan-900/30 px-4 py-3 text-sm text-bluegray-100",children:v.jsxs("div",{className:"flex flex-col gap-1",children:[v.jsxs("span",{children:["Wartungsmodus aktiv",B?` – ${B}`:q?" – Portainer-Update läuft":"","."]}),q&&v.jsxs("span",{className:"text-xs text-indigo-900",children:["Phase: ",Ws]})]})}),E&&v.jsxs(V.Card,{children:[v.jsx(V.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:v.jsx(V.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:v.jsx("span",{children:"Wartungsmodus"})})}),v.jsxs(V.CardBody,{className:"flex flex-col gap-4 p-4",children:[v.jsxs("div",{className:"flex items-start justify-between gap-4",children:[v.jsx("div",{className:"space-y-1",children:v.jsx("p",{className:"text-sm antialiased font-sans text-sm font-light leading-normal text-inherit",children:"Schaltet StackPulse für Benutzer in den Wartungsmodus."})}),v.jsx(V.Switch,{checked:G,disabled:!k||Rt||g||q,onChange:Ee=>Us(Ee.target.checked),ripple:!1,color:"amber"})]}),Rt&&v.jsx("p",{className:"text-xs text-stormGrey-500",children:"Wartungsmodus wird aktualisiert…"})]})]}),L&&v.jsxs(V.Card,{children:[v.jsx(V.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:v.jsx(V.Typography,{variant:"h6",color:"white",children:"mTLS-Serverzertifikate (Backend)"})}),v.jsxs(V.CardBody,{className:"flex flex-col gap-4 p-4",children:[v.jsxs("div",{className:"flex flex-col gap-2 md:flex-row md:items-center md:justify Line truncated
`)},kV=(e=[])=>{if(!Array.isArray(e))return[];const t=[];for(let r=0;r<e.length;r+=4)t.push(e.slice(r,r+4));return t};function NV(){const e=Hn(),{user:t,setSession:r,refreshSession:n}=Yr(),{maintenance:o,update:a}=Yo(),[i,l]=P.useState(!0),[f,g]=P.useState(""),[m,S]=P.useState([]),[O,T]=P.useState(!1),[C,w]=P.useState(!1),b=!!(t!=null&&t.requiresSecurityPhraseDownload),d=!!(o!=null&&o.active);o==null||o.message;const s=!!(a!=null&&a.running);a!=null&&a.stage&&(PV[a.stage]??a.stage);const c=d||s,u=P.useMemo(()=>kV(m),[m]),h=P.useCallback(k=>{if(!k)return;const j=new Blob([k],{type:"text/plain;charset=utf-8"}),A=URL.createObjectURL(j),M=document.createElement("a");M.href=A;const D=t!=null&&t.username?`${t.username}-sicherheitsschluessel`:"security-phrase";M.download=`${D}.txt`,document.body.appendChild(M),M.click(),document.body.removeChild(M),URL.revokeObjectURL(A)},[t==null?void 0:t.username]),y=P.useCallback(async()=>{var k,j,A,M,D,L,z,H;l(!0),g("");try{const Y=await Je.get("/api/users/me/security-phrase"),G=Array.isArray((j=(k=Y.data)==null?void 0:k.item)==null?void 0:j.words)?Y.data.item.words:[];if(!G.length)throw new Error("EMPTY_SECURITY_PHRASE");S(G),w(!1)}catch(Y){if(((A=Y==null?void 0:Y.response)==null?void 0:A.status)===409||((D=(M=Y==null?void 0:Y.response)==null?void 0:M.data)==null?void 0:D.error)==="SECURITY_PHRASE_ALREADY_DOWNLOADED"){await n(),e("/dashboard/stacks",{replace:!0});return}if(((L=Y==null?void 0:Y.response)==null?void 0:L.status)===401){await n(),e("/auth/sign-in",{replace:!0});return}const G=((H=(z=Y==null?void 0:Y.response)==null?void 0:z.data)==null?void 0:H.error)||(Y==null?void 0:Y.message)||"Sicherheitsschlüssel konnte nicht geladen werden.";g(G)}finally{l(!1)}},[e,n]);P.useEffect(()=>{if(!t){e("/auth/sign-in",{replace:!0});return}if(!b){e("/dashboard/stacks",{replace:!0});return}y()},[t,b,y,e]);const x=P.useCallback(()=>{y()},[y]),E=P.useCallback(async()=>{var k,j,A,M,D,L;if(!(O||!Array.isArray(m)||m.length===0)){T(!0),g("");try{const z=OV(m);h(z);const H=await Je.post("/api/users/me/security-phrase/downloaded"),Y=((j=(k=H.data)==null?void 0:k.item)==null?void 0:j.downloadedAt)||null,G=(A=H.data)==null?void 0:A.session;G!=null&&G.user?r(G):await n(),w(!!Y),e("/dashboard/stacks",{replace:!0})}catch(z){if(((M=z==null?void 0:z.response)==null?void 0:M.status)===401){await n(),e("/auth/sign-in",{replace:!0});return}const H=((L=(D=z==null?void 0:z.response)==null?void 0:D.data)==null?void 0:L.error)||(z==null?void 0:z.message)||"Download konnte nicht bestätigt werden.";g(H)}finally{T(!1)}}},[m,O,h,n,e,r]);return b?v.jsxs("div",{className:"flex min-h-screen flex-col items-center justify-center bg-blue-gray-50/50 px-4 py-10",children:[v.jsxs("div",{className:"mb-6 text-center",children:[v.jsx(V.Typography,{variant:"h2",className:"font-semibold text-blue-gray-800",children:"Sicherheitsschlüssel sichern"}),v.jsx(V.Typography,{color:"blue-gray",className:"mt-2 max-w-3xl text-base",children:"Bitte lade den einmaligen Sicherheitsschlüssel herunter und bewahre ihn sicher auf. Er wird benötigt, um dein Passwort zurückzusetzen. Ohne den Download kannst du die Anwendung nicht weiter nutzen."})]}),v.jsx(V.Card,{className:"w-full max-w-3xl shadow-lg",children:v.jsxs(V.CardBody,{className:"space-y-6",children:[i&&v.jsx("div",{className:"flex items-center justify-center py-10",children:v.jsx(V.Spinner,{className:"h-10 w-10 text-blue-gray-500"})}),!i&&f&&v.jsx(V.Alert,{color:"red",className:"border border-red-200 bg-red-50 text-red-700",icon:v.jsx(Bz,{className:"h-5 w-5"}),children:v.jsxs("div",{className:"flex items-start justify-between gap-4",children:[v.jsxs("div",{children:[v.jsx(V.Typography,{variant:"h6",color:"red",children:"Fehler"}),v.jsx(V.Typography,{color:"red",className:"text-sm",children:f})]}),v.jsx(V.Button,{size:"sm",color:"red",variant:"text",onClick:x,className:"normal-case",children:"Erneut versuchen"})]})}),!i&&!f&&v.jsxs(v.Fragment,{children:[v.jsxs("div",{className:"rounded-lg border border-blue-gray-100 bg-blue-gray-50/60 p-6",children:[v.jsx(V.Typography,{variant:"h5",className:"mb-4 text-blue-gray-800",children:"Deine Sicherheitsphrase"}),v.jsx("div",{className:"space-y-3",children:u.map((k,j)=>v.jsx("div",{className:"flex flex-wrap items-center justify-center gap-4 text-lg font-mono tracking-wide text-blue-gray-900 md:text-xl",children:k.map((A,M)=>v.jsx("span",{className:"uppercase",children:A},`phrase-${j}-${M}`))},`phrase-row-${j}`))})]}),v.jsxs("div",{className:"flex flex-col items-center justify-between gap-4 md:flex-row",children:[v.jsx("div",{className:"text-sm text-blue-gray-600",children:"Der Download startet im TXT-Format. Bewahre die Datei sicher auf – sie kann später nicht erneut angezeigt werden."}),v.jsxs(V.Button,{color:"blue",size:"md",className:"flex items-center gap-2 normal-case",onClick:E,disabled:c||O||C||m.length===0,children:[v.jsx(Lz,{className:"h-5 w-5"}),"Sicherheitsschlüssel herunterladen"]})]})]})]})})]}):null}const cl=e=>{if(! Line truncated
`):""}),Ce(!!ue.passwordStored),Ne(!1),It(null)},[ue]);const Ge=P.useMemo(()=>Oe?Oe.source==="custom"&&typeof Oe.custom=="string"?Oe.custom:Oe.default??Oe.effective??"":"",[Oe]),gt=Oe?he!==Ge:!1,X=(Oe==null?void 0:Oe.source)==="custom"?"Benutzerdefiniert":"Standard",_e=s?(L.name||"").trim()!==(s.name||"")||(L.url||"").trim()!==(s.url||""):!1;P.useMemo(()=>{if(!(s!=null&&s.url))return"-";try{return new URL(s.url).hostname||s.url}catch{return s.url}},[s]);const ye=((Qr=$==null?void 0:$.portainer)==null?void 0:Qr.edition)??"-";(ye||"").toLowerCase().includes("community");const ot=T,at=P.useMemo(()=>{const je={host:xe.host.trim(),port:Number.parseInt(xe.port,10)||22,username:xe.username.trim(),extraSshArgs:(xe.extraSshArgs||"").split(/\r?\n/).map(ze=>ze.trim()).filter(Boolean)},Le=xe.password??"";return Le?je.password=Le:ce||(je.password=""),je},[xe,ce]),Ct=P.useCallback((je,Le)=>{m&&z(ze=>({...ze,[je]:Le}))},[m]),Tt=P.useCallback(async()=>{var je,Le,ze,Ue;if(!(!m||!r||!_e)){A(!0);try{await Je.put(`/api/setup/servers/${r}`,{name:((je=L.name)==null?void 0:je.trim())??"",url:((Le=L.url)==null?void 0:Le.trim())??""}),l({variant:"success",title:"Server gespeichert",description:"Serverdetails wurden aktualisiert."}),await hr()}catch(pt){const Lt=((Ue=(ze=pt.response)==null?void 0:ze.data)==null?void 0:Ue.error)||pt.message||"Server konnte nicht aktualisiert werden";l({variant:"error",title:"Speichern fehlgeschlagen",description:Lt})}finally{A(!1)}}},[m,r,_e,L,hr,l]),Zt=P.useCallback(async()=>{var Le,ze;if(!m||!r)return;const je=(H||"").trim();if(!je){l({variant:"warning",title:"API-Key fehlt",description:"Bitte gib einen API-Key ein."});return}B(!0);try{await i(r,je),l({variant:"success",title:"API-Key gespeichert",description:"Der neue API-Key wurde hinterlegt."}),Y(""),await hr()}catch(Ue){const pt=((ze=(Le=Ue.response)==null?void 0:Le.data)==null?void 0:ze.error)||Ue.message||"API-Key konnte nicht gespeichert werden";l({variant:"error",title:"Speichern fehlgeschlagen",description:pt})}finally{B(!1)}},[H,m,r,i,hr,l]),Ft=P.useCallback(()=>{_r()},[_r]),Mr=P.useMemo(()=>{const je=Array.isArray(Be)?Be.length:0,Le=Array.isArray(Be)?Be.reduce((ze,Ue)=>{var pt;return ze+(((pt=Ue==null?void 0:Ue.duplicates)==null?void 0:pt.length)||0)},0):0;return{groups:je,duplicateCount:Le}},[Be]),Ir=P.useCallback(async je=>{var pt,Lt,or,gr;if(!w||!je)return;const Le=(pt=je.canonical)==null?void 0:pt.Id;if(!Le)return;const ze=(je.duplicates||[]).map(mn=>mn.Id).filter(Boolean);if(!ze.length)return;const Ue=((Lt=je.canonical)==null?void 0:Lt.Name)||je.name||`Stack ${Le}`;if(!(typeof window<"u"&&!window.confirm(`Bereinigung für "${Ue}" starten?
Es werden ${ze.length} Duplikate entfernt: ${ze.join(", ")}`))){At(String(Le));try{const jn=(await Je.post("/api/maintenance/duplicates/cleanup",{canonicalId:Le,duplicateIds:ze})).data??{};if(jn.success===!1)throw new Error(jn.error||"Bereinigung fehlgeschlagen");const Ba=Array.isArray(jn.results)?jn.results.filter(_i=>_i.status==="deleted").map(_i=>_i.id):ze;l({variant:"success",title:"Bereinigung abgeschlossen",description:`${Ue} – entfernte IDs: ${Ba.join(", ")}`}),await Me({silent:!0})}catch(mn){const jn=((gr=(or=mn.response)==null?void 0:or.data)==null?void 0:gr.error)||mn.message||"Bereinigung fehlgeschlagen";l({variant:"error",title:"Bereinigung fehlgeschlagen",description:jn})}finally{At(null)}}},[w,Me,l]),br=P.useCallback(async()=>{var Le,ze;if(!O||!r||M===r)return;const je=(s==null?void 0:s.name)||(s==null?void 0:s.url)||`Server ${r}`;if(!(typeof window<"u"&&!window.confirm(`Server "${je}" wirklich löschen?`))){D(r);try{await a(r),l({variant:"success",title:"Server gelöscht",description:`Server "${je}" wurde entfernt.`}),e("/dashboard/maintenance/servers")}catch(Ue){const pt=((ze=(Le=Ue.response)==null?void 0:Le.data)==null?void 0:ze.error)||Ue.message||"Server konnte nicht gelöscht werden";l({variant:"error",title:"Löschen fehlgeschlagen",description:pt})}finally{D(null)}}},[O,a,e,r,M,s,l]),Vr=P.useCallback(async()=>{var je,Le;if(!(!d||!r||!Oe))try{me(!0),te(""),await Je.put(`/api/setup/servers/${r}/update-script`,{script:he}),await kr(),l({variant:"success",title:"Skript gespeichert",description:"Das benutzerdefinierte Portainer-Update-Skript wurde aktualisiert."})}catch(ze){const Ue=((Le=(je=ze.response)==null?void 0:je.data)==null?void 0:Le.error)||ze.message||"Skript konnte nicht gespeichert werden";te(Ue),l({variant:"error",title:"Speichern fehlgeschlagen",description:Ue})}finally{me(!1)}},[d,r,Oe,he,kr,l]),go=P.useCallback(async()=>{var je,Le;if(!(!d||!r))try{me(!0),te(""),await Je.delete(`/api/setup/servers/${r}/update-script`),await kr(),l({variant:"info",title:"Standardskript wiederhergestellt",description:"Es wird wieder das Standard-Update-Skript verwendet."})}catch(ze){const Ue=((Le=(je=ze.response)==null?void 0:je.data)==null?void 0:Le.error)||ze.message||"Standardskript konnte nicht wiederhergestellt werden";te(Ue),l({variant:"error",title:"Zurücksetzen fehlgeschlagen",description:Ue})}finally{me(!1)}},[d,r,kr,l]),vr=P.useCallback((je,Le)=>{d&&(se(ze=>({...ze,[je]:Le})),je==="password"&&Ce(!1))},[d]),Gn=P.useCallback(async()=>{var je,Le;if(!(!d||!r))try{Ve(!0),await Je.put(`/api/setup/servers/${r}/ssh-config`,at),It(null),Ne(!1),await Te(),l({variant:"success",title:"SSH-Konfiguration gespeichert",description:"Verbindungseinstellungen wurden aktualisiert."})}catch(ze){const Ue=((Le=(je=ze.response)==null?void 0:je.data)==null?void 0:Le.error)||ze.message||"SSH-Konfiguration konnte nicht gespeichert werden";Ot(Ue),l({variant:"error",title:"Speichern fehlgeschlagen",description:Ue})}finally{Ve(!1)}},[d,at,r,Te,l]),za=P.useCallback(async()=>{var je,Le,ze;if(!(!d||!r))try{wt(!0);const Ue=await Je.post(`/api/setup/servers/${r}/test-ssh`,at);It({success:!0,timestamp:new Date,details:(je=Ue.data)==null?void 0:je.result}),l({variant:"success",title:"SSH-Verbindung erfolgreich",description:"Verbindung zum Portainer-Host wurde hergestellt."})}catch(Ue){const pt=((ze=(Le=Ue.response)==null?void 0:Le.data)==null?void 0:ze.error)||Ue.message||"SSH-Verbindung fehlgeschlagen";It({success:!1,timestamp:new Date,error:pt}),l({variant:"error",title:"SSH-Test fehlgeschlagen",description:pt})}finally{wt(!1)}},[d,at,r,l]),on=P.useCallback(async()=>{var je,Le;if(!(!d||!r))try{He(!0),await Je.delete(`/api/setup/servers/${r}/ssh-config`),ne(null),se(Ch()),Ce(!1),Ne(!1),It(null),l({variant:"info",title:"SSH-Konfiguration gelöscht",description:"Die Verbindungseinstellungen wurden zurückgesetzt."})}catch(ze){const Ue=((Le=(je=ze.response)==null?void 0:je.data)==null?void 0:Le.error)||ze.message||"SSH-Konfiguration konnte nicht gelöscht werden";Ot(Ue),l({variant:"error",title:"Löschen fehlgeschlagen",description:Ue})}finally{He(!1)}},[d,r,l]),Dr=!d||ct||tt||Vt||Dt||!r;if(!g)return null;if(!r)return v.jsxs("div",{className:"mt-12 flex flex-col gap-4",children:[v.jsx(V.Alert,{color:"red",className:"border border-red-200 bg-red-50 text-red-800",children:"Ungültige Server-ID."}),v.jsx(V.Button,{variant:"outlined",color:"gray",onClick:()=>e("/dashboard/maintenance/servers"),children:"Zurück zur Übersicht"})]});const Sr=!m&&S;return v.jsx("div",{className:"mt-12 mb-8 flex flex-col gap-12",children:v.jsxs(V.Card,{children:[v.jsx(V.CardHeader,{variant:"gradient",color:"gray",className:"mb-5 p-4",children:v.jsxs(V.Typography,{variant:"h6",color:"white",className:"flex items-center justify-between",children:[v.jsxs("span",{children:["Serverdetails ",s?`– ${s.name||"Ohne Namen"}`:""]}),v.jsxs("div",{className:"flex items-center gap-2",children:[v.jsx(V.Button,{variant:"text",color:"white",size:"sm",onClic Line truncated
+1
View File
@@ -0,0 +1 @@
*,:before,:after{box-sizing:border-box;border-width:0;border-style:solid;border-color:#eee}:before,:after{--tw-content: ""}html{line-height:1.5;-webkit-text-size-adjust:100%;-moz-tab-size:4;-o-tab-size:4;tab-size:4;font-family:Roboto,sans-serif;font-feature-settings:normal;font-variation-settings:normal}body{margin:0;line-height:inherit}hr{height:0;color:inherit;border-top-width:1px}abbr:where([title]){-webkit-text-decoration:underline dotted;text-decoration:underline dotted}h1,h2,h3,h4,h5,h6{font-size:inherit;font-weight:inherit}a{color:inherit;text-decoration:inherit}b,strong{font-weight:bolder}code,kbd,samp,pre{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,Liberation Mono,Courier New,monospace;font-size:1em}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}table{text-indent:0;border-color:inherit;border-collapse:collapse}button,input,optgroup,select,textarea{font-family:inherit;font-feature-settings:inherit;font-variation-settings:inherit;font-size:100%;font-weight:inherit;line-height:inherit;color:inherit;margin:0;padding:0}button,select{text-transform:none}button,[type=button],[type=reset],[type=submit]{-webkit-appearance:button;background-color:transparent;background-image:none}:-moz-focusring{outline:auto}:-moz-ui-invalid{box-shadow:none}progress{vertical-align:baseline}::-webkit-inner-spin-button,::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}summary{display:list-item}blockquote,dl,dd,h1,h2,h3,h4,h5,h6,hr,figure,p,pre{margin:0}fieldset{margin:0;padding:0}legend{padding:0}ol,ul,menu{list-style:none;margin:0;padding:0}dialog{padding:0}textarea{resize:vertical}input::-moz-placeholder,textarea::-moz-placeholder{opacity:1;color:#bdbdbd}input::placeholder,textarea::placeholder{opacity:1;color:#bdbdbd}button,[role=button]{cursor:pointer}:disabled{cursor:default}img,svg,video,canvas,audio,iframe,embed,object{display:block;vertical-align:middle}img,video{max-width:100%;height:auto}[hidden]{display:none}*,:before,:after{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }::backdrop{--tw-border-spacing-x: 0;--tw-border-spacing-y: 0;--tw-translate-x: 0;--tw-translate-y: 0;--tw-rotate: 0;--tw-skew-x: 0;--tw-skew-y: 0;--tw-scale-x: 1;--tw-scale-y: 1;--tw-pan-x: ;--tw-pan-y: ;--tw-pinch-zoom: ;--tw-scroll-snap-strictness: proximity;--tw-gradient-from-position: ;--tw-gradient-via-position: ;--tw-gradient-to-position: ;--tw-ordinal: ;--tw-slashed-zero: ;--tw-numeric-figure: ;--tw-numeric-spacing: ;--tw-numeric-fraction: ;--tw-ring-inset: ;--tw-ring-offset-width: 0px;--tw-ring-offset-color: #fff;--tw-ring-color: rgb(33 150 243 / .5);--tw-ring-offset-shadow: 0 0 #0000;--tw-ring-shadow: 0 0 #0000;--tw-shadow: 0 0 #0000;--tw-shadow-colored: 0 0 #0000;--tw-blur: ;--tw-brightness: ;--tw-contrast: ;--tw-grayscale: ;--tw-hue-rotate: ;--tw-invert: ;--tw-saturate: ;--tw-sepia: ;--tw-drop-shadow: ;--tw-backdrop-blur: ;--tw-backdrop-brightness: ;--tw-backdrop-contrast: ;--tw-backdrop-grayscale: ;--tw-backdrop-hue-rotate: ;--tw-backdrop-invert: ;--tw-backdrop-opacity: ;--tw-backdrop-saturate: ;--tw-backdrop-sepia: }.container{width:100%}@media (min-width: 540px){.container{max-width:540px}}@media (min-width: 720px){.container{max-width:720px}}@media (min-width: 960px){.container{max-width:960px}}@media (min-width: 1140px){.container{max-width:1140px}}@media (min-width: 1320px){.container{max-width:1320px}}.pointer-events-none{pointer-events:none}.pointer-events-auto{pointer-events:auto}.visible{visibility:visible}.\!invisible{visibility:hidden!important}.invisible{visibility:hidden}.collapse{visibility:collapse}.static{position:static}.fixed{position:fixed}.\!absolute{position:absolute!important}.absolute{position:absolute}.relative{position:relative}.sticky{position:sticky}.inset-0{top:0;right:0;bLine truncated
+2 -2
View File
@@ -34,8 +34,8 @@
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
<script type="module" crossorigin src="/assets/index-027ca62d.js"></script>
<link rel="stylesheet" href="/assets/index-4d48f088.css">
<script type="module" crossorigin src="/assets/index-c3010de9.js"></script>
<link rel="stylesheet" href="/assets/index-c7ee792b.css">
</head>
<body>
<div id="root"></div>
+6 -61
View File
@@ -14,17 +14,6 @@ const updateServer = db.prepare(`
SET name = ?, url = ?, updated_at = CURRENT_TIMESTAMP
WHERE id = ?
`);
const selectAgentByServerId = db.prepare('SELECT * FROM server_agents WHERE server_id = ?');
const selectAllAgents = db.prepare('SELECT * FROM server_agents');
const upsertAgent = db.prepare(`
INSERT INTO server_agents (server_id, agent_url, agent_token, created_at, updated_at)
VALUES (?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
ON CONFLICT(server_id) DO UPDATE SET
agent_url = excluded.agent_url,
agent_token = excluded.agent_token,
updated_at = CURRENT_TIMESTAMP
`);
const deleteServerStmt = db.prepare('DELETE FROM servers WHERE id = ?');
const selectApiKeyByServerId = db.prepare('SELECT * FROM server_api_keys WHERE server_id = ?');
@@ -44,6 +33,10 @@ const deleteApiKeyByServerId = db.prepare('DELETE FROM server_api_keys WHERE ser
const countServersStmt = db.prepare('SELECT COUNT(*) as count FROM servers');
const selectFirstServerStmt = db.prepare('SELECT * FROM servers ORDER BY id ASC LIMIT 1');
function listServers() {
return selectAllServers.all();
}
const API_KEY_SECRET = crypto.createHash('sha256')
.update(process.env.PORTAINER_API_SECRET || process.env.PORTAINER_API_KEY || 'stackpulse-portainer-api-key')
.digest();
@@ -136,49 +129,6 @@ function ensureServer({ name, url }) {
return created;
}
const generateAgentToken = () => `sp_${crypto.randomBytes(16).toString('hex')}`;
function getAgentConfig(serverId, { autoCreate = false } = {}) {
const id = Number(serverId);
if (!Number.isFinite(id)) {
const error = new Error('SERVER_ID_INVALID');
error.code = 'SERVER_ID_INVALID';
throw error;
}
const server = getServerById(id);
let agent = selectAgentByServerId.get(server.id) || null;
if (!agent && autoCreate) {
const token = generateAgentToken();
upsertAgent.run(server.id, null, token);
agent = selectAgentByServerId.get(server.id) || null;
}
return agent ? { serverId: server.id, agentUrl: agent.agent_url, agentToken: agent.agent_token } : null;
}
function setAgentConfig(serverId, { agentUrl, agentToken } = {}) {
const id = Number(serverId);
if (!Number.isFinite(id)) {
const error = new Error('SERVER_ID_INVALID');
error.code = 'SERVER_ID_INVALID';
throw error;
}
const server = getServerById(id);
const normalizedUrl = typeof agentUrl === 'string' && agentUrl.trim().length
? agentUrl.trim().replace(/\/+$/, '')
: null;
let normalizedToken = typeof agentToken === 'string' ? agentToken.trim() : '';
if (!normalizedToken) {
const existing = selectAgentByServerId.get(server.id);
normalizedToken = existing?.agent_token || generateAgentToken();
}
if (!normalizedToken.startsWith('sp_')) {
normalizedToken = `sp_${normalizedToken}`;
}
upsertAgent.run(server.id, normalizedUrl, normalizedToken);
const agent = selectAgentByServerId.get(server.id);
return { serverId: server.id, agentUrl: agent.agent_url, agentToken: agent.agent_token };
}
function getServerById(serverId) {
const id = Number(serverId);
if (!Number.isFinite(id)) {
@@ -371,15 +321,12 @@ function ensureDefaultsFromEnv() {
function getServerStatus(serverId) {
const server = getServerById(serverId);
const apiKeyMeta = selectApiKeyByServerId.get(server.id) || null;
const agent = getAgentConfig(server.id, { autoCreate: true });
return {
server,
apiKey: {
hasKey: Boolean(apiKeyMeta),
updatedAt: apiKeyMeta?.updated_at ?? null
},
agent
}
};
}
@@ -484,10 +431,8 @@ export {
ensureDefaultsFromEnv,
ensureServer,
getServerById,
listServers,
setServerApiKey,
getAgentConfig,
setAgentConfig,
generateAgentToken,
getServerConnection,
updateServerDetails,
getServerStatus,
+212
View File
@@ -75,6 +75,48 @@ const insertMembershipForUser = db.prepare(`
VALUES (?, ?)
`);
const selectServerByIdForAssignment = db.prepare('SELECT id, name, url FROM servers WHERE id = ?');
const selectAssignmentsByUser = db.prepare(`
SELECT
usa.user_id,
usa.server_id,
usa.group_id,
usa.use_global_group,
usa.created_at,
usa.updated_at,
s.name AS server_name,
s.url AS server_url,
g.name AS group_name
FROM user_server_assignments usa
JOIN servers s ON s.id = usa.server_id
LEFT JOIN user_groups g ON g.id = usa.group_id
WHERE usa.user_id = ?
ORDER BY s.name ASC, usa.server_id ASC
`);
const upsertUserServerAssignment = db.prepare(`
INSERT INTO user_server_assignments (
user_id,
server_id,
group_id,
use_global_group,
created_at,
updated_at
) VALUES (
?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
)
ON CONFLICT(user_id, server_id) DO UPDATE SET
group_id = excluded.group_id,
use_global_group = excluded.use_global_group,
updated_at = CURRENT_TIMESTAMP
`);
const deleteUserServerAssignments = db.prepare(`
DELETE FROM user_server_assignments
WHERE user_id = ?
`);
const selectSecurityPhraseByUsername = db.prepare(`
SELECT
id,
@@ -883,6 +925,176 @@ export function updateUserDetails(userId, { username, email, password, avatarCol
return updatedUser;
}
const sanitizeAssignment = (row) => ({
userId: row.user_id,
serverId: row.server_id,
groupId: row.group_id ?? null,
groupName: row.group_name || null,
serverName: row.server_name || null,
serverUrl: row.server_url || null,
useGlobalGroup: Boolean(row.use_global_group),
createdAt: row.created_at || null,
updatedAt: row.updated_at || null
});
export function getUserServerAssignments(userId) {
const numericUserId = Number(userId);
if (!Number.isFinite(numericUserId) || numericUserId <= 0) {
const error = new Error('INVALID_USER_ID');
error.code = 'INVALID_USER_ID';
throw error;
}
const existingUser = selectUserCredentialsById.get(numericUserId);
if (!existingUser) {
const error = new Error('USER_NOT_FOUND');
error.code = 'USER_NOT_FOUND';
throw error;
}
const superuserMembership = isUserInGroupStatement.get(numericUserId, SUPERUSER_GROUP_NAME);
if (superuserMembership?.has_membership) {
return [];
}
const rows = selectAssignmentsByUser.all(numericUserId);
return rows.map(sanitizeAssignment);
}
export function setUserServerAssignments(userId, assignments = [], options = {}) {
const actor = options.actor || null;
const numericUserId = Number(userId);
if (!Number.isFinite(numericUserId) || numericUserId <= 0) {
const error = new Error('INVALID_USER_ID');
error.code = 'INVALID_USER_ID';
throw error;
}
const userRecord = selectUserCredentialsById.get(numericUserId);
if (!userRecord) {
const error = new Error('USER_NOT_FOUND');
error.code = 'USER_NOT_FOUND';
throw error;
}
const superuserMembership = isUserInGroupStatement.get(numericUserId, SUPERUSER_GROUP_NAME);
if (superuserMembership?.has_membership) {
const error = new Error('USER_SUPERUSER_PROTECTED');
error.code = 'USER_SUPERUSER_PROTECTED';
throw error;
}
const normalizedList = Array.isArray(assignments) ? assignments : [];
const missingServers = [];
const missingGroups = [];
const normalizedAssignments = new Map();
normalizedList.forEach((entry) => {
const rawServerId = entry?.serverId ?? entry?.server_id ?? entry?.id;
const serverId = Number(rawServerId);
if (!Number.isFinite(serverId) || serverId <= 0) {
return;
}
const serverRow = selectServerByIdForAssignment.get(serverId);
if (!serverRow) {
missingServers.push(serverId);
return;
}
const useGlobalGroup =
entry?.useGlobalGroup === true ||
entry?.use_global_group === 1 ||
entry?.use_global_group === true;
const rawGroupId = entry?.groupId ?? entry?.group_id;
const groupId = Number(rawGroupId);
const normalizedGroupId = Number.isFinite(groupId) && groupId > 0 ? groupId : null;
if (!useGlobalGroup && normalizedGroupId === null) {
missingGroups.push(serverId);
return;
}
if (normalizedGroupId !== null) {
const groupRow = selectGroupById.get(normalizedGroupId);
if (!groupRow) {
missingGroups.push(serverId);
return;
}
}
normalizedAssignments.set(serverId, {
serverId,
groupId: normalizedGroupId,
useGlobalGroup
});
});
if (missingServers.length > 0) {
const error = new Error('SERVER_NOT_FOUND');
error.code = 'SERVER_NOT_FOUND';
error.missingServerIds = missingServers;
throw error;
}
if (missingGroups.length > 0) {
const error = new Error('GROUP_NOT_FOUND');
error.code = 'GROUP_NOT_FOUND';
error.missingServerIds = missingGroups;
throw error;
}
const previousAssignments = selectAssignmentsByUser.all(numericUserId);
const applyAssignments = db.transaction(() => {
deleteUserServerAssignments.run(numericUserId);
Array.from(normalizedAssignments.values()).forEach((assignment) => {
upsertUserServerAssignment.run(
numericUserId,
assignment.serverId,
assignment.groupId,
assignment.useGlobalGroup ? 1 : 0
);
});
});
applyAssignments();
const updatedAssignments = selectAssignmentsByUser.all(numericUserId);
const sanitizedUpdated = updatedAssignments.map(sanitizeAssignment);
logEvent({
category: 'benutzer',
eventType: 'benutzer-server-zuordnung-aktualisiert',
action: 'aktualisieren',
status: 'erfolgreich',
entityType: 'benutzer',
entityId: String(numericUserId),
entityName: userRecord?.username ?? `ID ${numericUserId}`,
message: `Serverzuordnungen für Benutzer "${userRecord?.username ?? numericUserId}" aktualisiert`,
metadata: {
assignments: sanitizedUpdated.map((entry) => ({
serverId: entry.serverId,
serverName: entry.serverName,
useGlobalGroup: entry.useGlobalGroup,
groupId: entry.groupId,
groupName: entry.groupName
})),
previousAssignments: previousAssignments.map((entry) => ({
serverId: entry.server_id,
serverName: entry.server_name,
useGlobalGroup: Boolean(entry.use_global_group),
groupId: entry.group_id,
groupName: entry.group_name || null
}))
},
...resolveActorFields(actor)
});
return sanitizedUpdated;
}
const deleteMembershipsStatement = db.prepare(`
DELETE FROM user_group_memberships
WHERE user_id = ?
+8 -3
View File
@@ -8,13 +8,18 @@ StackPulse folgt einer klassischen Client/Server-Architektur, bei der das Backen
│ (React) │ ◀───── Auth-Cookie + JSON ─ │ (Express) │ ◀─────────────── │ Business Ed. │
└────────────┘ Redeploy Events └───────────────┘ (Axios Proxy) └────────────────┘
│
mTLS (optional)│
▼
StackPulse Agent
│
▼
SQLite Datenbank
```
## Hauptschichten
- **Frontend** (Vite + Material Tailwind): stellt alle Views, Formulare, Tabellen und Echtzeit-Widgets bereit. Die `AuthProvider`-Komponente verwaltet Session, Permissions und Socket-Verbindungen.
- **Backend** (Express + better-sqlite3): aggregiert Daten aus Portainer, persistiert Benutzer/Gruppen/Logs, verwaltet Sessions und stößt Redeploy- sowie Wartungsprozesse an.
- **Frontend** (Vite + Material Tailwind): stellt Views, Tabellen, Setup-Wizard und Echtzeit-Widgets bereit. `AuthProvider` verwaltet Session, Permissions und Socket-Verbindungen.
- **Backend** (Express + better-sqlite3): aggregiert Daten aus Portainer, persistiert Benutzer/Gruppen/Logs, verwaltet Sessions, Redeploy-Queue und Maintenance/Agent-Funktionen.
- **Agent (optional)**: kleiner Node-Dienst, der lokal auf dem Portainer-Host läuft. Liefert Stack-/Portainer-Metadaten per REST, authentifiziert via `X-Agent-Token` und kann mTLS-Zertifikate aus dem Backend beziehen.
- **Persistenz** (SQLite): Datei `backend/data/stackpulse.db`, WAL-Modus aktiviert. Tabellen umfassen u. a. `users`, `groups`, `permissions`, `servers`, `server_api_keys`, `event_logs`, `settings` sowie Hilfstabellen für Tokens und Sicherheitspassphrasen.
## Datenflüsse
@@ -23,7 +28,7 @@ StackPulse folgt einer klassischen Client/Server-Architektur, bei der das Backen
3. **Stack-Übersicht**: Frontend ruft `/api/stacks` auf. Das Backend proxyt `Portainer /api/endpoints/:id/docker/stacks`, ergänzt Caching, interne Metadaten und liefert Redeploy-Status.
4. **Redeploy**: UI stößt `/api/stacks/:id/redeploy`, `/api/stacks/redeploy-selection` oder `/api/stacks/redeploy-all` an. Das Backend ruft Portainer-Endpoints, protokolliert Events und broadcastet Fortschritt via Socket.IO `redeployStatus`.
5. **Logging**: Jeder relevante Vorgang erzeugt Einträge in `event_logs` (siehe `logging/eventLogs.js`). Frontend filtert/paginiert über `/api/logs` und kann Exporte `/api/logs/export` herunterladen.
6. **Wartung**: `/api/maintenance/*` verwaltet Maintenance-Mode, SSH-Zugänge, Update-Skripte und serverseitige Settings (`db/settings.js`).
6. **Wartung**: `/api/maintenance/*` verwaltet Maintenance-Mode, SSH-Zugänge, Update-Skripte sowie weitere Wartungsfunktionen.
## Verzeichnis-Referenz
| Pfad | Inhalt |
+10 -9
View File
@@ -11,10 +11,10 @@ Das Backend implementiert alle Anwendungsfälle über Express-Routen, Socket.IO
| Modul | Pfad | Aufgabe |
|-------|------|---------|
| Auth/Superuser | `auth/superuser.js` | Registrieren/Verifizieren von Superusern, Login, Passwortvalidierung, Sicherheitsphrasen. |
| Users & Groups | `users/*`, `groups/*`, `permissions/*` | CRUD für Benutzer, Gruppen, Rechtezuweisung, Ableitung effektiver Berechtigungen. |
| Users & Groups | `users/*`, `groups/*`, `permissions/*` | CRUD für Benutzer, Gruppen, Rechtezuweisung, Ableitung effektiver Berechtigungen. Rechte-Labels z. B. `Bereich & Navigation`, `Benutzergruppen bearbeiten`, `Benutzer löschen`. |
| Setup | `setup/index.js` | Verwaltung registrierter Portainer-Server, sichere Ablage der API-Keys, Setup-Status. |
| Logging | `logging/eventLogs.js` | Einheitliches Ereignislogging (Insert, Filter, Delete, Export). |
| Maintenance | `maintenance/state.js` + helper in `index.js` | Wartungsmodus, SSH-Konfiguration, Update-Skripte und Ausführung. |
| Maintenance | `maintenance/state.js` + Helper in `index.js` | Wartungsmodus, SSH-Konfiguration, Update-Skripte und Ausführung. |
| Portainer Proxy | Funktionen in `index.js` | Kommunikation mit Portainer (`axiosInstance` + dynamische `baseURL`), Environment-Erkennung und Redeploy-Strecke. |
## API-Überblick (Auszug)
@@ -56,7 +56,7 @@ Das Backend implementiert alle Anwendungsfälle über Express-Routen, Socket.IO
| GET | `/api/stacks` | Aggregierte Liste inkl. Filter, Self-Stack-Blockade und Redeploy-Status. |
| PUT | `/api/stacks/:id/redeploy` | Redeploy eines einzelnen Stacks. |
| PUT | `/api/stacks/redeploy-selection` | Redeploy einer Liste übermittelter `stackIds`. |
| PUT | `/api/stacks/redeploy-all` | Redeploy aller Stacks (Permission `stacks-redeploy-all`). |
| PUT | `/api/stacks/redeploy-all` | Redeploy aller Stacks (Recht `Redeploy Alle`). |
Das Backend veröffentlicht den Redeploy-Fortschritt parallel über Socket.IO (`redeployStatus` Event) und unterscheidet die Phasen `queued`, `started`, `success`, `error`, `info`.
@@ -75,7 +75,7 @@ Das Backend veröffentlicht den Redeploy-Fortschritt parallel über Socket.IO (`
## Datenbank & Schema
- `ensureDatabaseSchema()` erzeugt Tabellen bei jedem Start, inklusive Default-Gruppen, Permissions, Settings, Server-Einträge und Trigger.
- Permissions werden anhand der Blueprint-Datei `backend/db/dbs` eingelesen. Jede Permission definiert Key, Label, Level (`full`, `read`, `none`) und Abhängigkeiten.
- Permissions werden anhand der Blueprint-Datei `backend/db/dbs` eingelesen. Jede Permission definiert Key, Label (z. B. `Bereich & Navigation`, `Server bearbeiten`, `SSH/Update-Skript`, `Update durchführen`, `Doppelte Stacks`), Level (`full`, `read`, `none`) und Abhängigkeiten.
- Events werden in `event_logs` gespeichert. Relevante Felder:
- `category`, `event_type`, `action`, `status`, `severity`
- `entity_type`, `entity_id`, `entity_name`
@@ -85,13 +85,14 @@ Das Backend veröffentlicht den Redeploy-Fortschritt parallel über Socket.IO (`
- Filter-Parameter (`ids`, `categories`, `eventTypes`, `status`, `stackIds`, Zeitfenster) werden in `buildEventLogFilter` zentral verarbeitet.
## Berechtigungssystem
- Jeder Benutzer gehört zu beliebig vielen Gruppen; die effektiven Rechte ergeben sich aus der höchsten Ausprägung pro Permission.
- Jeder Benutzer gehört zu beliebig vielen Gruppen; die effektiven Rechte ergeben sich aus der höchsten Ausprägung pro Permission (Labels wie `Bereich & Navigation`, `Server bearbeiten`, `SSH/Update-Skript`, `Update durchführen`, `Doppelte Stacks`).
- Superuser erhalten alle Berechtigungen und können nicht über das UI entzogen werden.
- `PermissionGate` im Frontend spiegelt die gleichen Keys. Wichtigste Bereiche:
- `stacks-*` (Redeploy)
- `logs-*`
- `users-*` & `user-groups-*`
- `maintenance-*` (inkl. Unterrechte `maintenance-server-*`, `maintenance-ssh-update`, `maintenance-update`)
- `Redeploy einzeln/Auswahl/Alle`
- `Bereich & Navigation (Logs)`, `Logs Exportieren`, `Logs löschen`
- `Bereich & Navigation (Benutzer)`, `Benutzer bearbeiten/löschen`, `Sicherheitsschlüssel`
- `Bereich & Navigation (Benutzergruppen)`, `Benutzergruppen bearbeiten/löschen`
- Wartung: `Bereich & Navigation (Wartung)`, `Server-Sektion`, `Server bearbeiten/löschen`, `SSH/Update-Skript`, `Update durchführen`, `Doppelte Stacks`
## Redeploy-Workflow
1. Eingehender Request wird mit `maintenanceGuard` geprüft (Maintenance-Modus blockiert optional Redeploys).
+59
View File
@@ -0,0 +1,59 @@
# Umgebungsvariablen
Diese Seite sammelt alle relevanten Umgebungsvariablen für StackPulse (Backend) und den optionalen StackPulse Agent. Werte ohne Default sind in der Regel optional, sofern sie nicht explizit als „erforderlich“ markiert sind.
## StackPulse Backend
| Variable | Default | Erforderlich | Beschreibung |
|----------|---------|--------------|--------------|
| `PORTAINER_URL` | – | Nein | Basis-URL zu Portainer. Kann im Setup-UI hinterlegt werden; ohne Wert schlägt Portainer-Zugriff fehl, bis ein Server gespeichert ist. |
| `PORTAINER_API_KEY` | – | Nein | API-Key für Portainer. Kann im Setup-UI gespeichert (verschlüsselt) werden; ohne Wert keine API-Aufrufe. |
| `PORTAINER_SERVER_NAME` | – | Nein | Anzeigename für den initialen Servereintrag (nur kosmetisch). |
| `PORTAINER_API_SECRET` | `stackpulse-portainer-api-key` | Nein | AES-Schlüssel für gespeicherte API-Keys. Stabil halten, sonst müssen Keys neu eingetragen werden. |
| `SUPERUSER_USERNAME` | – | Nein | Legt beim Start einen Superuser an, falls noch keiner existiert; alternativ im Setup-UI anlegen. |
| `SUPERUSER_EMAIL` | – | Nein | E-Mail des initialen Superusers. |
| `SUPERUSER_PASSWORD` | – | Nein | Passwort des initialen Superusers. |
| `SELF_STACK_ID` | – | Nein | ID des StackPulse-Stacks; verhindert Self-Redeploy im UI. |
| `AUTH_COOKIE_NAME` | `sp_auth_token` | Nein | Name des Auth-Cookies. |
| `AUTH_SESSION_TTL_MS` | `43200000` (12h) | Nein | Session-Lebensdauer in Millisekunden. |
| `AUTH_COOKIE_SECURE` | `false` | Nein | Setzt Cookie nur über HTTPS, wenn `true`. |
| `SECURITY_PHRASE_SECRET` | `stackpulse-security-phrase-secret` | Nein | Secret für verschlüsselte Sicherheitsphrasen; stabil halten, sonst Phrasen neu verteilen. |
| `PORTAINER_SSH_SECRET` | – (Fallback API-Key/Default) | Nein | Secret für verschlüsselte SSH-Passwörter; stabil halten, sonst Passwort neu setzen. |
| `AGENT_MTLS_KEY` | generiert | Nein | Optional: eigenes PEM-Key für internen mTLS-Server, falls nicht automatisch erzeugt. |
| `AGENT_MTLS_CERT` | generiert | Nein | Optional: eigenes PEM-Zertifikat für internen mTLS-Server. |
| `AGENT_MTLS_PORT` | `4441` | Nein | Port des internen mTLS-Endpunkts (Agent). |
| `DISPLAY` | `:9999` | Nein | Wird an Child-Prozesse (Update-Skript) weitergereicht. |
## StackPulse Agent
| Variable | Default | Erforderlich | Beschreibung |
|----------|---------|--------------|--------------|
| `AGENT_PORT` | `7070` | Nein | Listenport des Agents (HTTPS mit mTLS). |
| `AGENT_TOKEN` | – | Ja | Shared Secret für Auth (`X-Agent-Token`) bei allen Agent-Endpoints. |
| `DOCKER_SOCKET` | `/var/run/docker.sock` | Nein | Pfad zum Docker-Socket; nötig für Stack-/Image-Infos. |
| `CONTROL_PLANE_URL` | – | Ja (für Bootstrap/Restore) | Basis-URL des StackPulse-Backends für Zertifikatsabruf ohne mTLS (z. B. `https://stackpulse:4001`). |
| `CONTROL_PLANE_MTLS_URL` | – | Nein (empfohlen) | Bevorzugte mTLS-URL des Backends (z. B. `https://stackpulse:4441`). Wird für alle Agent→Backend-Calls genutzt, sobald Zertifikate vorliegen; fehlt sie, wird aus `CONTROL_PLANE_URL` + `AGENT_MTLS_PORT` abgeleitet. |
| `AGENT_BOOTSTRAP_TOKEN` | – | Nein (für Erstbootstrap) | Einmal-Token aus dem Wartungsbereich, um mTLS-Zertifikate zu ziehen. Pflicht, wenn keine Zertifikate vorliegen und das Backend noch kein Material für den Agent gespeichert hat. |
| `AGENT_MTLS_PORT` | `4441` | Nein | Fallback-Port, falls `CONTROL_PLANE_MTLS_URL` keinen Port enthält. |
| `AGENT_SERVER_KEY` / `AGENT_SERVER_CERT` | – | Nein | PEM-Material für den Agent-Server; nur nötig bei eigenen Zertifikaten statt Backend-CA. |
| `AGENT_CLIENT_KEY` / `AGENT_CLIENT_CERT` / `AGENT_CA_CERT` | – | Nein | PEM-Material für Client/CA; nur bei eigener PKI nötig. |
| `REGISTRY_TOKEN` | – | Nein | Bearer-Token für Registry-Abfragen (Digest-Vergleich). |
| `REGISTRY_USERNAME` / `REGISTRY_PASSWORD` | – | Nein | Basic-Auth-Creds für Registries. |
| `GITHUB_USERNAME` / `GITHUB_TOKEN` / `GITHUB_PAT` | – | Nein | Alternative Credential-Quellen für GitHub Packages. |
Agent-Betrieb (wähle eine Variante):
- **Dynamisch über Backend (empfohlen)**
- Pflicht: `AGENT_TOKEN` + `CONTROL_PLANE_URL` (optional `CONTROL_PLANE_MTLS_URL`).
- Falls keine Zertifikate existieren: einmaliges `AGENT_BOOTSTRAP_TOKEN` setzen.
- Der Agent zieht Zertifikate vom Backend, schaltet auf mTLS um und braucht danach kein Bootstrap-Token mehr.
- **Statisch per ENV**
- Liefere PEMs: `AGENT_SERVER_KEY`/`AGENT_SERVER_CERT` + `AGENT_CLIENT_KEY`/`AGENT_CLIENT_CERT`/`AGENT_CA_CERT`.
- Dann kein `CONTROL_PLANE_URL`/Bootstrap nötig, da mTLS-Material bereits vorhanden ist.
- `AGENT_TOKEN` bleibt erforderlich.
Hinweise:
- Server-URL und API-Key kannst du im Setup-UI pflegen; Umgebungsvariablen setzen nur den Initialzustand.
- Secrets (`SECURITY_PHRASE_SECRET`, `PORTAINER_SSH_SECRET`, `PORTAINER_API_SECRET`) in Produktion einmalig setzen und stabil halten – sonst werden bestehende verschlüsselte Werte unlesbar.
- `CONTROL_PLANE_MTLS_URL`: bevorzugte mTLS-Zieladresse. Fehlt sie, leitet der Agent sie aus `CONTROL_PLANE_URL` + `AGENT_MTLS_PORT` ab und nutzt sie für alle Aufrufe, sobald Zertifikate vorliegen.
+44 -68
View File
@@ -1,80 +1,61 @@
# Getting Started
Dieser Leitfaden hilft dir, StackPulse lokal oder als Container aufzusetzen und die ersten Schritte im UI abzuschließen.
Dieser Leitfaden beschreibt ausschließlich den Docker-Deploy über die bereitgestellten GHCR-Images und die anschließende Einrichtung im UI.
## Voraussetzungen
- Node.js **>= 20** und npm für lokale Entwicklung
- Docker & Docker Compose (optional aber empfohlen für Deployment)
- Zugriff auf eine **Portainer Business Edition** samt API-Key
- Shell-Zugang zum Host, auf dem StackPulse laufen soll
- Docker & Docker Compose
- Portainer-Instanz (Business Edition empfohlen; bei Community/Edge zusätzlich Agent einsetzen)
## Repository & Abhängigkeiten
```bash
# Repository klonen
# git clone <repo-url>
cd stackpulse
## Zentrale Umgebungsvariablen (Kurzfassung)
| Variable | Erforderlich | Beschreibung |
|----------|--------------|--------------|
| `PORTAINER_URL` | Nein | Basis-URL zu Portainer; kann im Setup-UI hinterlegt werden, falls nicht gesetzt. |
| `PORTAINER_API_KEY` | Nein | API-Key; kann im Setup gespeichert werden (wird verschlüsselt). |
| `PORTAINER_SERVER_NAME` | Nein | Anzeige-Name für den initialen Servereintrag. |
| `PORTAINER_API_SECRET` | Nein | AES-Schlüssel für gespeicherte API-Keys; setze stabilen Wert, sonst muss der Key neu eingegeben werden. |
| `SUPERUSER_USERNAME`, `SUPERUSER_EMAIL`, `SUPERUSER_PASSWORD` | Nein | Legt automatisch einen Superuser an, falls noch keiner existiert; sonst im Setup-UI anlegen. |
| `SELF_STACK_ID` | Nein | ID des StackPulse-Stacks; schützt vor Self-Redeploy. |
| `AUTH_COOKIE_NAME`, `AUTH_SESSION_TTL_MS`, `AUTH_COOKIE_SECURE` | Nein | Session/Cookie-Tuning (Name, Dauer, Secure-Flag). |
| `SECURITY_PHRASE_SECRET` | Nein | Secret für verschlüsselte Sicherheitsphrasen; stabil halten, sonst Phrasen neu verteilen. |
| `PORTAINER_SSH_SECRET` | Nein | Secret für verschlüsselte SSH-Passwörter; stabil halten, sonst Passwort neu setzen. |
| `DISPLAY` | Nein | Wird an Update-Childprozesse weitergereicht (Default `:9999`). |
# Skript ausführbar machen
chmod +x scripts/start-dev.sh
```
Weitere Variablen (Agent, Registry, mTLS) findest du im Detail unter [Umgebungsvariablen](environment.md). Einstellungen wie Server-URL/API-Key, SSH-Config oder Update-Skript kannst du alternativ im UI (Setup/Wartung) verwalten.
## Lokale Dev-Umgebung
Das Skript `scripts/start-dev.sh` richtet Backend und Frontend automatisch ein:
1. Backend: `npm install`, `npm run migrate` (erstellt/aktualisiert `backend/data/stackpulse.db`), `npm start`
2. Frontend: `npm install`, `npm run dev` (Port 5173) und ein einmaliges `npm run build`, um statische Assets ins Backend zu spiegeln
Nach erfolgreichem Start:
- Frontend dev server: http://localhost:5173
- Backend API & statische Assets: http://localhost:4001
Zum Stoppen STRG+C verwenden.
## Manuelles Starten
```bash
# Backend
cd backend
npm install
npm run migrate
PORTAINER_URL=https://portainer.local npm start
# Frontend in zweiter Shell
cd frontend
npm install
npm run dev
```
Das Backend liest Umgebungsvariablen beim Start. Alternativ kannst du eine `.env` neben `backend/index.js` anlegen.
## Zentrale Umgebungsvariablen
| Variable | Beschreibung |
|----------|--------------|
| `PORTAINER_URL` | Basis-URL zu deiner Portainer-Instanz (inkl. Protokoll). Kann später im Setup-UI überschrieben werden. |
| `PORTAINER_API_KEY` | API-Key für Portainer. Wird in der DB verschlüsselt abgelegt, sobald du ihn im Setup speicherst. |
| `PORTAINER_SERVER_NAME` | Optionaler Anzeigename für den Server während des Setups. |
| `PORTAINER_API_SECRET` | Überschreibt den Schlüssel, der zur Verschlüsselung des API-Keys verwendet wird. |
| `SUPERUSER_USERNAME`, `SUPERUSER_EMAIL`, `SUPERUSER_PASSWORD` | Legt einen Superuser automatisch beim Start an (falls nicht vorhanden). |
| `SELF_STACK_ID` | Stack-ID von StackPulse selbst. Wird genutzt, um eigene Redeploys zu blockieren. |
| `AUTH_COOKIE_NAME`, `AUTH_SESSION_TTL_MS`, `AUTH_COOKIE_SECURE` | Feintuning für die Session-Verwaltung der API. |
| `SECURITY_PHRASE_SECRET` | Seed für die Generierung kryptografischer Sicherheitsphrasen. |
| `PORTAINER_SSH_SECRET` | Secret für SSH-Passwortverschlüsselung (Wartungs-Update). |
| `DISPLAY` | Wird an Kindprozesse weitergereicht, wenn Portainer-Updates ausgelöst werden müssen (Default `:9999`). |
Weitere Einstellungen (SSH-Config, Update-Skripte) verwaltest du später direkt im Maintenance-Bereich des UI.
**Hinweis zu Secrets:**
- `SECURITY_PHRASE_SECRET` und `PORTAINER_SSH_SECRET` steuern die AES-256-GCM-Verschlüsselung von Sicherheitsphrasen bzw. hinterlegten SSH-Passwörtern.
- Wenn sie nicht gesetzt sind, nutzt StackPulse fixe Fallbacks (`stackpulse-security-phrase-secret` bzw. `stackpulse-portainer-ssh-secret` / API-Key). Setze in Produktion eigene, stabile Werte – ein späterer Secret-Wechsel macht zuvor gespeicherte Phrasen/Passwörter unlesbar.
- Eine vollständige Übersicht aller Variablen (inkl. Agent) findest du unter „Technik → Umgebungsvariablen“.
## Ersteinrichtung im UI
1. Öffne das Frontend (`/setup` führt dich automatisch durch den Wizard).
2. Lege den Portainer-Server und den zugehörigen API-Key an – StackPulse testet die Verbindung per `/api/setup/test-portainer`.
1. Öffne das Frontend.
2. Lege den Portainer-Server und den zugehörigen API-Key an (der Wizard führt durch den Test).
3. Registriere einen Superuser oder verwende den per Umgebungsvariablen automatisch angelegten Account.
4. Nach erfolgreicher Einrichtung kannst du dich anmelden und erhältst Zugriff auf Dashboard, Logs, Benutzer & Wartung.
## Docker-Deployment
## Docker Run (Single Container)
```bash
docker run -d \
-p 4001:4001 \
-e PORTAINER_URL=https://portainer.example.com \
-e PORTAINER_API_KEY=xxxx \
-e SUPERUSER_USERNAME=admin \
-e SUPERUSER_EMAIL=admin@example.com \
-e SUPERUSER_PASSWORD=changeme \
-e SELF_STACK_ID=123 \
-e SECURITY_PHRASE_SECRET=$(openssl rand -hex 32) \
-e PORTAINER_SSH_SECRET=$(openssl rand -hex 32) \
-v stackpulse_data:/app/backend/data \
ghcr.io/mboehmlaender/stackpulse:latest
```
Das Volume `stackpulse_data` persistiert die SQLite-Datenbank. Passen Sie Secrets und Ports an Ihre Umgebung an; weitere Variablen siehe Kapitel „Umgebungsvariablen“.
## Docker-Deployment (Compose)
```yaml
# docker-compose.yml (Auszug)
services:
app:
build:
context: .
dockerfile: Dockerfile
image: ghcr.io/mboehmlaender/stackpulse:latest
ports:
- "4001:4001"
volumes:
@@ -91,13 +72,8 @@ volumes:
stackpulse_data:
```
1. `docker compose up -d --build`
2. Das Frontend ist anschließend unter Port 4001 (via Backend `public/`) erreichbar.
1. `docker compose up -d`
2. Das Frontend ist anschließend unter Port 4001 erreichbar.
3. Datenbank & Einstellungen liegen im benannten Volume `stackpulse_data`.
## Datenbank & Migration
- Datei: `backend/data/stackpulse.db`
- Migration: `npm run migrate` (führt `backend/db/ensure.js` aus und erzeugt Schema, Rechte, Defaults)
- Backup: Datei oder Volume regelmäßig sichern; enthält Benutzer, Logs und gespeicherte Secrets.
Damit bist du bereit für die tieferen Kapitel zu Architektur sowie Backend- und Frontend-Details.
+1
View File
@@ -8,6 +8,7 @@ StackPulse ist eine eigenständige Web-Anwendung, die die Portainer Business Edi
- **Auditierbare Historie** durch strukturierte Event-Logs mit Export-, Lösch- und Filterfunktionen
- **Feingranulare Rechteverwaltung** mit Gruppenrechten, Superuser-Handling und Sicherheitsphrasen
- **Wartungsautomatisierung** (SSH Update-Skripte, Maintenance-Mode) ohne direkten Portainer-Zugang
- **Agent-Integration** (mTLS) für Portainer Community/Edge, um Stacks & Versionen auch ohne Business-API auszulesen
- **Bereitstellung als Docker-Image** sowie als lokale Dev-Umgebung via `scripts/start-dev.sh`
## Technische Eckdaten
+53
View File
@@ -0,0 +1,53 @@
# Lokale Entwicklung & Migration
Dieser Abschnitt richtet sich an Contributor:innen, die StackPulse lokal bauen oder erweitern möchten. Für produktive Deployments nutze die GHCR-Images wie im Getting Started beschrieben.
## Voraussetzungen
- Node.js **>= 20** und npm
- Shell-Zugang zum Host
- Docker (optional) für lokale Tests
## Repository vorbereiten
```bash
# Repository klonen
# git clone <repo-url>
cd stackpulse
# Startskript ausführbar machen
chmod +x scripts/start-dev.sh
```
## Schnellstart (Dev-Skript)
Das Skript `scripts/start-dev.sh` richtet Backend und Frontend ein und startet beide im Dev-Modus:
1. Backend: `npm install`, `npm run migrate`, `npm start`
2. Frontend: `npm install`, `npm run dev` (Port 5173) und einmalig `npm run build` für statische Assets
Nach dem Start:
- Frontend Dev Server: http://localhost:5173
- Backend API & statische Assets: http://localhost:4001
Stoppen mit `STRG+C` in der Shell, in der das Skript läuft.
## Manuelles Starten
```bash
# Backend
cd backend
npm install
npm run migrate
PORTAINER_URL=https://portainer.local npm start
# Frontend (zweite Shell)
cd frontend
npm install
npm run dev
```
Das Backend liest Umgebungsvariablen beim Start. Alternativ kannst du eine `.env` neben `backend/index.js` anlegen.
## Datenbank & Migration
- Datei: `backend/data/stackpulse.db` (SQLite, WAL aktiviert)
- Migration: `npm run migrate` (führt `backend/db/ensure.js` aus, erzeugt/aktualisiert Schema & Defaults)
- Backup: Datei oder Volume regelmäßig sichern; enthält Benutzer, Logs und gespeicherte Secrets
## Hinweise
- Die lokalen Builds sind für Entwicklung gedacht. Für Deployments verwende `ghcr.io/mboehmlaender/stackpulse`.
- Einige Einstellungen (z. B. Server-URL, API-Key, SSH-Config) kannst du im UI setzen; für lokale Tests reichen die ENV-Defaults und das Setup-UI.
+6 -2
View File
@@ -2,6 +2,10 @@
Dieser Abschnitt deckt typische Aufgaben für Betriebsteams ab – von Backups über Wartungsmodus bis zu Störungssuche.
**Relevante Rechte**
- `Superuser löschen`
- `Bereich & Navigation (Wartung)`
## Laufender Betrieb
- **Services überwachen**: StackPulse stellt nur einen Prozess bereit (Node.js/Express). Nutze Systemd/Docker Healthchecks, indem du regelmäßig `/api/auth/session` (für Auth) oder `/api/maintenance/update-status` (für Backend + DB) abfragst.
- **Ports**: Standardmäßig lauscht nur Port 4001 (HTTP + Socket.IO). Hinter einem Reverse Proxy sollte TLS terminiert werden.
@@ -13,12 +17,12 @@ Dieser Abschnitt deckt typische Aufgaben für Betriebsteams ab – von Backups
3. **Restore**: Ersetze die Datei im Volume und starte die App neu. Beim ersten Start führt `ensureDatabaseSchema` automatisch eventuelle Nachmigrationen aus.
## Benutzer- & Rechteverwaltung
- Superuser können über `/api/auth/superuser/*` oder im UI registriert werden. Entfernen ist nur mit `maintenance-superuser-delete` möglich.
- Superuser können über `/api/auth/superuser/*` oder im UI registriert werden. Entfernen ist nur mit `Superuser löschen` möglich.
- Benutzerkonten lassen sich temporär deaktivieren (`/api/users/:id/active`). Die Historie bleibt erhalten.
- Sicherheitsphrasen sind notwendig für Passwort-Reset. Admins können neue Phrasen erzeugen (`/api/users/:id/security-phrase/renew`), müssen diese aber dem Benutzer sicher zustellen.
## Wartungsmodus & Updates
- Maintenance Mode aktivieren: `curl -X POST http://host:4001/api/maintenance/mode -H 'Content-Type: application/json' -d '{"active":true,"message":"Cluster Upgrade"}'` (Permission `maintenance-access`). Das UI informiert alle Nutzer und blockiert Redeploys.
- Maintenance Mode aktivieren: `curl -X POST http://host:4001/api/maintenance/mode -H 'Content-Type: application/json' -d '{"active":true,"message":"Cluster Upgrade"}'` (Recht `Bereich & Navigation (Wartung)`). Das UI informiert alle Nutzer und blockiert Redeploys.
- SSH-/Update-Setup: Hinterlege Host, Port, Benutzer, Passwort (optional) und zusätzliche SSH-Argumente. Passwörter werden verschlüsselt gespeichert (`PORTAINER_SSH_SECRET`).
- Update-Skript: Das Default-Skript (`DEFAULT_PORTAINER_UPDATE_SCRIPT`) führt `docker stop`, `docker run` etc. aus. Du kannst im UI oder via API ein eigenes Skript setzen – wird erst aktualisiert, wenn kein Update läuft.
- Update anstoßen: `POST /api/maintenance/portainer-update` triggert die SSH-Kommandos. Status & Logausgaben können über `/api/maintenance/update-status` überwacht werden.
+8 -3
View File
@@ -1,6 +1,11 @@
# Benutzergruppen & Rechte
Dieser Bereich verwaltet Rollenmodelle und bestimmt, welche Aktionen Benutzer:innen durchführen dürfen. Du benötigst `user-groups-access` zum Lesen sowie `user-groups-edit`/`user-groups-delete` für Änderungen.
Dieser Bereich verwaltet Rollenmodelle und bestimmt, welche Aktionen Benutzer:innen durchführen dürfen.
**Relevante Rechte**
- `Bereich & Navigation (Benutzergruppen)`
- `Benutzergruppen bearbeiten`
- `Benutzergruppen löschen`
## Gruppen anlegen
1. Button **Gruppe erstellen** wählen.
@@ -23,7 +28,7 @@ Der Rechte-Dialog zeigt die gesamte Permissionstruktur in Kategorien (Stacks, Lo
- **none** – kein Zugriff
- **read** – Zugriff auf Ansicht/Lesen
- **full** – volle Bearbeitungsrechte
3. Abhängigkeiten werden automatisch hervorgehoben. Beispiel: Um `logs-delete` nutzen zu dürfen, muss `logs-access` mindestens `full` sein.
3. Abhängigkeiten werden automatisch hervorgehoben. Beispiel: Um `Logs löschen` nutzen zu dürfen, muss `Bereich & Navigation (Logs)` mindestens `Vollzugriff` sein.
4. Speichern bestätigt die Anpassungen. StackPulse aktualisiert sofort die effektiven Rechte aller Benutzer in dieser Gruppe.
## Benutzer zu Gruppen hinzufügen
@@ -36,6 +41,6 @@ Der Rechte-Dialog zeigt die gesamte Permissionstruktur in Kategorien (Stacks, Lo
- Löschen entfernt keine Benutzer, aber deren effektive Rechte können sich dadurch verringern.
## Best Practices
- Erstelle zunächst eine Basisgruppe (z. B. „Viewer“) mit `read`-Rechten für Stacks und Logs.
- Erstelle zunächst eine Basisgruppe (z. B. „Viewer“) mit Leserechten für Stacks und Logs.
- Verwende separate Gruppen für sensible Aktionen wie Wartung oder Superuser-Verwaltung.
- Nutze sprechende Beschreibungen, damit Kolleg:innen sofort erkennen, wofür eine Gruppe gedacht ist.
+50 -31
View File
@@ -1,43 +1,62 @@
# Wartungsbereich
Der Wartungsbereich bündelt alle Funktionen rund um Maintenance-Mode, Serververwaltung, Update-Skripte und SSH-Verbindungen. Sichtbar für Benutzer:innen mit `maintenance-access` (Teilbereiche erfordern zusätzliche Unterrechte).
Der Wartungsbereich bündelt Maintenance-Mode, Serververwaltung (inkl. Agent-Integration), Update-Skripte und SSH-Konfiguration. Sichtbar für Benutzer:innen mit `Bereich & Navigation (Wartung)`; Unterbereiche haben eigene Rechte:
## Portainer-Status
- Klick auf **Status prüfen**, um eine Live-Abfrage gegen Portainer durchzuführen. Ergebnis zeigt API-Verfügbarkeit, Endpoint-IDs und Auth-Status.
- Häufige Fehler werden direkt erklärt (z. B. fehlender API-Key oder Netzwerkprobleme).
**Relevante Rechte**
- `Bereich & Navigation (Wartung)`
- `Server-Sektion`
- `Server bearbeiten` / `Server löschen`
- `SSH/Update-Skript`
- `Update durchführen`
- `Agent`
- `Doppelte Stacks`
- `mTLS Sektion`
- `Superuser löschen`
- `Server-Sektion` (lesen) zeigt die Serverübersicht.
- `Server bearbeiten` (lesen) zeigt Server-Details inkl. Portainer-Status; Felder bleiben schreibgeschützt. Vollzugriff erlaubt Bearbeiten/Speichern.
- `Server löschen` löscht Server (nur bei Vollzugriff).
- `SSH/Update-Skript` und `Update durchführen` hängen am Unterpunkt Portainer/SSH und erfordern Vollzugriff auf Server-Edit.
- `Agent` (lesen/voll) steuert die Agent-Sektion im Server-Detail.
## Maintenance-Mode
1. Schalter **Wartungsmodus aktivieren** toggeln.
2. Optionale Nachricht eingeben (z. B. „Upgrade um 22:00 Uhr“); sie erscheint im gesamten UI als Banner.
3. Aktivieren/Deaktivieren protokolliert automatisch Events in den Logs.
1. Schalter **Wartungsmodus aktivieren** umlegen.
2. Jede Aktivierung/Deaktivierung wird in den Logs protokolliert.
## Server & API-Key verwalten
- Register „Server“ listet alle Portainer-Instanzen.
- Über **Server hinzufügen** kannst du Name + URL ergänzen und anschließend API-Key speichern.
- Buttons in der Tabelle:
- **API-Key aktualisieren** – neues Token einsetzen.
- **Server löschen** – nur möglich, wenn mindestens ein weiterer Server existiert oder du sofort neue Daten einträgst.
## Serververwaltung & API-Key
- Liste zeigt alle eingetragenen Portainer-Server.
- **Server hinzufügen**/**bearbeiten**/**löschen** nur mit `Server bearbeiten` (Vollzugriff); löschen zusätzlich `Server löschen`.
- Im Server-Detail:
- **Server-URL/Name** und **Portainer API-Key**: editierbar nur mit `Server bearbeiten` Vollzugriff; bei Leserecht sichtbar, aber gesperrt.
- **Self-Stack-ID**: Stack-ID von StackPulse selbst; blockiert Redeploys dieses Stacks.
- **Status prüfen**: Live-Check gegen Portainer (Version, Edition, Update vorhanden). Sichtbar ab `Server bearbeiten` (lesen).
## Self-Stack ID
- Feld „Eigener Stack“ akzeptiert die Stack-ID, in der StackPulse selbst läuft.
- Nach dem Speichern sind Redeploy-Aktionen für diesen Stack im Dashboard deaktiviert.
## Agent-Integration (Portainer CE / Edge)
Für die Community Edition von Stackpulse kannst du je Server einen StackPulse Agent hinterlegen. Er liefert Stack- und Portainer-Metadaten, wenn die Business-API fehlt. Hierzu muss auch der Agent auf dem Server der Portainer Instanz laufen. Der Agent wird für die Business Edition nicht benötigt.
1. Im Server-Detail **Agent konfigurieren** öffnen.
2. **Agent URL** eintragen, der **Agent Token** wird automatisch generiert und kann nicht geändert werden (Token = `AGENT_TOKEN` des Agents).
StackPulse prüft Erreichbarkeit und zeigt Online-/Fehlerstatus. Sichtbar mit `Agent` lesen, Bearbeitung mit `Agent` voll.
3. mTLS-Zertifikate verwalten:
- **Bootstrap-Token erzeugen** (Einmal-Token) und im Agent als `AGENT_BOOTSTRAP_TOKEN` setzen.
- Agent holt Zertifikate automatisch ab; Status im Zertifikate-Abschnitt.
- **Zertifikate anzeigen** (PEM) (nur bei entsprechenden Rechten)
4. Portainer-Version kann gelesen werden, wenn der Agent online ist.
## SSH-Konfiguration
Erforderliche Permission: `maintenance-ssh-update`.
1. Host, Port, Benutzername eintragen. Optional Passwort und zusätzliche SSH-Argumente (z. B. `-o StrictHostKeyChecking=no`).
2. Passwort wird verschlüsselt gespeichert; alternativ kannst du Public-Key-Auth nutzen und das Feld leer lassen.
3. **Verbindung testen**: StackPulse baut eine SSH-Verbindung auf und zeigt das Ergebnis in Echtzeit.
Erfordert `SSH/Update-Skript` (sichtbar/bearbeitbar nur bei Vollzugriff auf Server-Edit).
1. Host, Port, Benutzername und Passwort sowie eventuell notwendige Extra-Argumente eintragen.
2. Passwort wird verschlüsselt gespeichert.
3. **Verbindung testen** prüft Erreichbarkeit und meldet Ergebnis direkt.
## Update-Skript
- Standard-Skript wird angezeigt und kann als Ausgangspunkt genutzt werden.
- Eigenes Skript: Inhalt in das Editorfeld kopieren und speichern. StackPulse prüft auf leere Zeilen und Basisbefehle.
- Skript zurücksetzen entfernt die benutzerdefinierte Variante und stellt den Default wieder her.
## Portainer-Update auslösen
1. Stelle sicher, dass Wartungsmodus aktiv ist und SSH-Konfiguration + Skript korrekt sind.
2. Klicke auf **Update starten**. Der aktuelle Fortschritt erscheint in einem Logpanel.
3. Der Button bleibt deaktiviert, solange ein Update läuft. Über **Status aktualisieren** kannst du den Fortschritt erneut abfragen.
## Update-Skript & Portainer-Update
- Standard-Skript ist sichtbar und kann überschrieben werden (Recht `SSH/Update-Skript` + Server-Edit Vollzugriff). **Zurücksetzen** stellt den Standard wieder her.
- Start eines Updates (`Update durchführen` + Server-Edit Vollzugriff):
1) Wartungsmodus wird automatisch aktiviert.
2) SSH-Konfiguration und Skript prüfen.
3) **Update starten** klicken. Der Fortschritt und die Logausgaben erscheinen im Fenster.
4) Während ein Update läuft, sind Änderungen am System gesperrt.
## Fehlerbehandlung
- Bei SSH-Fehlern zeigt StackPulse die genaue Ausgabe an. Passe Host/Port/Benutzer oder Skript an.
- Falls das Update-Skript Probleme verursacht, kannst du es jederzeit stoppen, indem du Portainer manuell prüfst und ggf. das Skript anpasst.
- SSH-/Update-Fehler werden mit Klartextausgabe angezeigt. Bei Bedarf Host/Port/User, Passwort oder Skript anpassen.
- Agent offline: URL/Token prüfen, Bootstrap-Token ggf. neu erzeugen und den Agent neu starten.
+2 -2
View File
@@ -1,6 +1,6 @@
# Setup-Assistent
Beim ersten Start erscheint automatisch der Setup-Bereich. Du erreichst ihn später jederzeit über `/setup`, solange nicht alle Schritte abgeschlossen sind.
Beim ersten Start erscheint automatisch der Setup-Bereich und führt dich durch alle Schritte, bis die Einrichtung abgeschlossen ist.
## Voraussetzungen
- Portainer Business Edition mit gültigem API-Key
@@ -31,5 +31,5 @@ Beim ersten Start erscheint automatisch der Setup-Bereich. Du erreichst ihn spä
## Setup wiederholen oder ändern
- Über das Dashboard → **Wartung** → Abschnitt *Serververwaltung* kannst du zusätzliche Portainer-Server hinzufügen oder entfernen.
- Superuser lässt sich im Bereich **Benutzer** löschen, sofern du `maintenance-superuser-delete` besitzt.
- Superuser lässt sich im Bereich **Benutzer** löschen, sofern du das Recht `Superuser löschen` besitzt.
- Der Self-Stack (StackPulse-eigener Stack) kann in der Wartung gepflegt werden, damit er nicht versehentlich redeployed wird.
+8 -3
View File
@@ -2,6 +2,11 @@
Die Stacks-Ansicht ist der Startpunkt nach dem Login und liefert einen Überblick über alle Stacks der angebundenen Portainer-Instanz.
**Relevante Rechte**
- `Redeploy einzeln`
- `Redeploy Auswahl`
- `Redeploy Alle`
## Aufbau
- **Header** mit Suchfeld, Filterchips für Status sowie Buttons für Redeploy-Aktionen.
- **Tabelle** mit Name, Endpoint, Status, letzte Aktion, Redeploy-Status und verfügbaren Buttons.
@@ -15,9 +20,9 @@ Die Stacks-Ansicht ist der Startpunkt nach dem Login und liefert einen Überblic
## Redeploy-Aktionen
| Aktion | Voraussetzung | Vorgehen |
|--------|---------------|----------|
| **Einzelner Stack** | Permission `stacks-redeploy-single` | Button **Redeploy** in der jeweiligen Zeile klicken und bestätigen. |
| **Mehrere Stacks** | `stacks-redeploy-selection` | Checkboxen der gewünschten Stacks aktivieren, anschließend **Redeploy Auswahl**. |
| **Alle Stacks** | `stacks-redeploy-all` | Button **Redeploy alle** auslösen. Nutze vorher Filter, um sicherzugehen, dass alle Stacks korrekt angezeigt werden. |
| **Einzelner Stack** | Recht `Redeploy einzeln` | Button **Redeploy** in der jeweiligen Zeile klicken und bestätigen. |
| **Mehrere Stacks** | Recht `Redeploy Auswahl` | Checkboxen der gewünschten Stacks aktivieren, anschließend **Redeploy Auswahl**. |
| **Alle Stacks** | Recht `Redeploy Alle` | Button **Redeploy alle** auslösen. Nutze vorher Filter, um sicherzugehen, dass alle Stacks korrekt angezeigt werden. |
Während eines Redeploys erscheint pro Stack ein Fortschrittsbadge. Du kannst das Panel schließen; beim nächsten Besuch wird der aktuelle Status aus dem Backend gelesen.
+81 -1
View File
@@ -23,10 +23,52 @@ const normalizePermissions = (raw) => {
}, {});
};
const normalizeServerPermissions = (raw) => {
if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
return {};
}
return Object.entries(raw).reduce((acc, [serverId, value]) => {
const numericId = Number(serverId);
if (!Number.isFinite(numericId)) {
return acc;
}
const permissionMap = value?.permissions && typeof value.permissions === "object"
? value.permissions
: value;
acc[numericId] = {
permissions: normalizePermissions(permissionMap),
groupId: Number.isFinite(Number(value?.groupId)) ? Number(value.groupId) : null,
useGlobalGroup: Boolean(value?.useGlobalGroup)
};
return acc;
}, {});
};
const normalizeServerAssignments = (raw) => {
if (!Array.isArray(raw)) {
return [];
}
return raw
.map((entry) => {
const serverId = Number(entry?.serverId ?? entry?.server_id ?? entry?.id);
if (!Number.isFinite(serverId)) return null;
return {
serverId,
groupId: Number.isFinite(Number(entry?.groupId ?? entry?.group_id)) ? Number(entry?.groupId ?? entry?.group_id) : null,
useGlobalGroup: Boolean(entry?.useGlobalGroup ?? entry?.use_global_group),
serverName: entry?.serverName || entry?.server_name || null,
serverUrl: entry?.serverUrl || entry?.server_url || null
};
})
.filter(Boolean);
};
export function AuthProvider({ children }) {
const [state, setState] = useState({
user: null,
permissions: {},
serverPermissions: {},
serverAssignments: [],
loading: false,
error: null,
lastErrorCode: null,
@@ -36,9 +78,13 @@ export function AuthProvider({ children }) {
const setSession = useCallback((payload) => {
const user = payload?.user ?? null;
const permissions = normalizePermissions(payload?.permissions);
const serverPermissions = normalizeServerPermissions(payload?.serverPermissions);
const serverAssignments = normalizeServerAssignments(payload?.serverAssignments ?? user?.serverAssignments);
setState({
user,
permissions,
serverPermissions,
serverAssignments,
loading: false,
error: null,
lastErrorCode: null,
@@ -50,6 +96,8 @@ export function AuthProvider({ children }) {
setState((prev) => ({
user: null,
permissions: {},
serverPermissions: {},
serverAssignments: [],
loading: false,
error: null,
lastErrorCode: null,
@@ -80,6 +128,8 @@ export function AuthProvider({ children }) {
error: payload?.error ?? null,
lastErrorCode: payload?.error ?? null,
initialized: true,
serverPermissions: {},
serverAssignments: []
});
return { ok: false, status: response.status, error: payload?.error ?? null };
}
@@ -91,6 +141,8 @@ export function AuthProvider({ children }) {
setState({
user: null,
permissions: {},
serverPermissions: {},
serverAssignments: [],
loading: false,
error: message,
lastErrorCode: "NETWORK_ERROR",
@@ -140,10 +192,36 @@ export function AuthProvider({ children }) {
[state.permissions, state.user]
);
const hasServerPermission = useCallback(
(serverId, permissionKey, requiredLevel = "full") => {
if (!permissionKey) {
return true;
}
if (state.user?.isSuperuser) {
return true;
}
const numericServerId = Number(serverId);
let permissionMap = null;
if (Number.isFinite(numericServerId)) {
const entry = state.serverPermissions?.[numericServerId] || state.serverPermissions?.[String(numericServerId)];
if (entry?.permissions && typeof entry.permissions === "object") {
permissionMap = entry.permissions;
}
}
const basePermissions = permissionMap || state.permissions || {};
const current = basePermissions[permissionKey] ?? "none";
const currentPriority = LEVEL_PRIORITY[current] ?? 0;
const requiredPriority = LEVEL_PRIORITY[requiredLevel] ?? LEVEL_PRIORITY.full;
return currentPriority >= requiredPriority;
},
[state.serverPermissions, state.permissions, state.user]
);
const value = useMemo(
() => ({
user: state.user,
permissions: state.permissions,
serverPermissions: state.serverPermissions,
loading: state.loading,
error: state.error,
lastErrorCode: state.lastErrorCode,
@@ -154,8 +232,10 @@ export function AuthProvider({ children }) {
clearSession,
logout,
hasPermission,
hasServerPermission,
serverAssignments: state.serverAssignments,
}),
[state, refreshSession, setSession, clearSession, logout, hasPermission]
[state, refreshSession, setSession, clearSession, logout, hasPermission, hasServerPermission]
);
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
File diff suppressed because it is too large. Load diff
+322 -199
View File
@@ -1,4 +1,4 @@
import React, { useCallback, useEffect, useMemo, useState } from "react";
import React, { useCallback, useEffect, useMemo, useRef, useState } from "react";
import axios from "axios";
import { useNavigate, useParams } from "react-router-dom";
import { useAuth } from "@/components/AuthProvider.jsx";
@@ -54,6 +54,12 @@ const formatCreatedAt = (value) => {
});
};
const resolveStackType = (type) => {
if (type === 1) return "Git";
if (type === 2) return "Compose";
return type ?? "-";
};
const createEmptySshDraft = () => ({
host: "",
port: "22",
@@ -70,16 +76,34 @@ export function ServerDetail() {
return Number.isFinite(numeric) ? numeric : null;
}, [serverId]);
const { hasPermission, user: authUser } = useAuth();
const { hasServerPermission, user: authUser } = useAuth();
const { deleteSetupServer, updateSetupApiKey } = useMaintenance();
const { showToast } = useToast();
const isSuperuserAccount = Boolean(authUser?.isSuperuser);
const canViewServers = Boolean(isSuperuserAccount || hasPermission("maintenance-server-manage", "read"));
const canEditServers = Boolean(isSuperuserAccount || hasPermission("maintenance-server-edit", "full"));
const canDeleteServers = Boolean(isSuperuserAccount || hasPermission("maintenance-server-delete", "full"));
const canViewSsh = Boolean(isSuperuserAccount || hasPermission("maintenance-ssh-update", "read"));
const canManageSsh = Boolean(isSuperuserAccount || hasPermission("maintenance-ssh-update", "full"));
const canViewServers = Boolean(
isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-server-manage", "read")
);
const canEditServers = Boolean(
isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-server-edit", "full")
);
const canReadServers = Boolean(
isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-server-edit", "read")
);
const canDeleteServers = Boolean(
isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-server-delete", "full")
);
const canViewPortainer = Boolean(
isSuperuserAccount
|| hasServerPermission(selectedServerId, "maintenance-server-edit", "read")
|| hasServerPermission(selectedServerId, "maintenance-server-manage", "read")
);
const canViewDuplicates = Boolean(isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-duplicates", "read"));
const canManageDuplicates = Boolean(isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-duplicates", "full"));
const canViewSsh = Boolean(isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-ssh-update", "read"));
const canManageSsh = Boolean(isSuperuserAccount || hasServerPermission(selectedServerId, "maintenance-ssh-update", "full"));
const canViewScript = canViewSsh;
const canManageScript = canManageSsh;
const [serverDetail, setServerDetail] = useState(null);
const [apiKeyMeta, setApiKeyMeta] = useState(null);
@@ -104,11 +128,13 @@ export function ServerDetail() {
const [scriptError, setScriptError] = useState("");
const [scriptConfig, setServerScriptConfig] = useState(null);
const [agentConfig, setAgentConfig] = useState(null);
const [agentDraft, setAgentDraft] = useState({ agentUrl: "", agentToken: "" });
const [agentLoading, setAgentLoading] = useState(false);
const [agentError, setAgentError] = useState("");
const [agentSaving, setAgentSaving] = useState(false);
const [duplicates, setDuplicates] = useState([]);
const [duplicatesLoading, setDuplicatesLoading] = useState(false);
const [duplicatesRefreshing, setDuplicatesRefreshing] = useState(false);
const [duplicatesError, setDuplicatesError] = useState("");
const [duplicatesUpdatedAt, setDuplicatesUpdatedAt] = useState(null);
const [activeCleanupId, setActiveCleanupId] = useState(null);
const duplicatesRequestRef = useRef(null);
const [serverSshConfig, setServerSshConfig] = useState(null);
const [sshDraft, setSshDraft] = useState(() => createEmptySshDraft());
@@ -134,17 +160,26 @@ export function ServerDetail() {
setServerDetail(response.data?.server ?? null);
setApiKeyMeta(response.data?.apiKey ?? null);
} catch (err) {
const status = err.response?.status;
const message = err.response?.data?.error || err.message || "Serverdetails konnten nicht geladen werden";
setServerError(message);
setServerDetail(null);
setApiKeyMeta(null);
if (status === 403 || message === "SERVER_ACCESS_DENIED") {
showToast({
variant: "error",
title: "Kein Zugriff",
description: "Du hast keine Berechtigung für diesen Server."
});
navigate(-1);
}
} finally {
setServerLoading(false);
}
}, [selectedServerId]);
}, [selectedServerId, showToast, navigate]);
const loadServerStatus = useCallback(async ({ silent = false } = {}) => {
if (!selectedServerId) {
if (!selectedServerId || !canViewPortainer) {
setStatusData(null);
setStatusError("");
setStatusFetchedAt(null);
@@ -159,43 +194,26 @@ export function ServerDetail() {
setStatusData(response.data ?? null);
setStatusFetchedAt(new Date());
} catch (err) {
const status = err.response?.status;
const message = err.response?.data?.error || err.message || "Status konnte nicht ermittelt werden";
setStatusError(message);
setStatusData(null);
setStatusFetchedAt(null);
if (status === 403 || message === "SERVER_ACCESS_DENIED") {
showToast({
variant: "error",
title: "Kein Zugriff",
description: "Du hast keine Berechtigung für diesen Server."
});
navigate(-1);
}
} finally {
setStatusLoading(false);
}
}, [selectedServerId]);
const loadAgentConfig = useCallback(async () => {
if (!selectedServerId) {
setAgentConfig(null);
setAgentDraft({ agentUrl: "", agentToken: "" });
return;
}
setAgentLoading(true);
setAgentError("");
try {
const response = await axios.get(`/api/servers/${selectedServerId}/agent`);
const payload = response.data ?? null;
setAgentConfig(payload);
setAgentDraft({
agentUrl: payload?.agentUrl ?? "",
agentToken: payload?.agentToken ?? ""
});
} catch (err) {
const message = err.response?.data?.error || err.message || "Agent konnte nicht geladen werden";
setAgentError(message);
setAgentConfig(null);
setAgentDraft({ agentUrl: "", agentToken: "" });
} finally {
setAgentLoading(false);
}
}, [selectedServerId]);
}, [selectedServerId, canViewPortainer, showToast, navigate]);
const loadServerScript = useCallback(async () => {
if (!selectedServerId) {
if (!selectedServerId || !canViewSsh) {
setServerScriptConfig(null);
setScriptDraft("");
return;
@@ -216,10 +234,10 @@ export function ServerDetail() {
} finally {
setScriptLoading(false);
}
}, [selectedServerId]);
}, [selectedServerId, canViewSsh]);
const loadServerSshConfig = useCallback(async () => {
if (!selectedServerId) {
if (!selectedServerId || !canViewSsh) {
setServerSshConfig(null);
return;
}
@@ -235,7 +253,50 @@ export function ServerDetail() {
} finally {
setSshConfigLoading(false);
}
}, [selectedServerId]);
}, [selectedServerId, canViewSsh]);
const fetchDuplicates = useCallback(async ({ silent = false } = {}) => {
if (!canViewDuplicates) {
setDuplicates([]);
setDuplicatesError("");
setDuplicatesUpdatedAt(null);
return;
}
if (duplicatesRequestRef.current) {
return duplicatesRequestRef.current;
}
const requestPromise = (async () => {
if (silent) {
setDuplicatesRefreshing(true);
} else {
setDuplicatesLoading(true);
}
setDuplicatesError("");
try {
const response = await axios.get("/api/maintenance/duplicates");
const payload = response.data;
const items = Array.isArray(payload) ? payload : payload?.items ?? [];
setDuplicates(items);
setDuplicatesUpdatedAt(new Date());
} catch (err) {
const message = err.response?.data?.error || err.message || "Fehler beim Laden der Wartungsdaten";
setDuplicatesError(message);
} finally {
if (silent) {
setDuplicatesRefreshing(false);
} else {
setDuplicatesLoading(false);
}
duplicatesRequestRef.current = null;
}
})();
duplicatesRequestRef.current = requestPromise;
return requestPromise;
}, [canViewDuplicates]);
useEffect(() => {
loadServerDetail();
@@ -245,6 +306,16 @@ export function ServerDetail() {
loadServerStatus();
}, [loadServerStatus]);
useEffect(() => {
fetchDuplicates();
}, [fetchDuplicates, canViewDuplicates]);
useEffect(() => {
setStatusData(null);
setStatusError("");
setStatusFetchedAt(null);
}, [selectedServerId]);
useEffect(() => {
loadServerScript();
}, [loadServerScript]);
@@ -253,10 +324,6 @@ export function ServerDetail() {
loadServerSshConfig();
}, [loadServerSshConfig]);
useEffect(() => {
loadAgentConfig();
}, [loadAgentConfig]);
useEffect(() => {
if (!serverDetail) {
setServerDraft({ name: "", url: "" });
@@ -315,8 +382,7 @@ export function ServerDetail() {
}, [serverDetail]);
const portainerEditionLabel = statusData?.portainer?.edition ?? "-";
const isCommunityEdition = (portainerEditionLabel || "").toLowerCase().includes("community");
const agentOnline = statusData?.agent?.online ?? agentConfig?.online ?? null;
const agentStatusLabel = agentOnline === true ? "Agent online" : agentOnline === false ? "Agent offline" : "Unbekannt";
const showPortainerSection = canViewPortainer;
const normalizedSshDraft = useMemo(() => {
const normalized = {
@@ -360,39 +426,13 @@ export function ServerDetail() {
description: "Serverdetails wurden aktualisiert."
});
await loadServerDetail();
await loadServerStatus({ silent: true });
} catch (err) {
const message = err.response?.data?.error || err.message || "Server konnte nicht aktualisiert werden";
showToast({ variant: "error", title: "Speichern fehlgeschlagen", description: message });
} finally {
setServerSaving(false);
}
}, [canEditServers, selectedServerId, serverDirty, serverDraft, loadServerDetail, loadServerStatus, showToast]);
const handleAgentDraftChange = useCallback((field, value) => {
setAgentDraft((prev) => ({ ...prev, [field]: value }));
}, []);
const handleAgentSave = useCallback(async () => {
if (!selectedServerId) return;
setAgentSaving(true);
try {
await axios.put(`/api/servers/${selectedServerId}/agent`, {
agentUrl: agentDraft.agentUrl
});
showToast({
variant: "success",
title: "Agent gespeichert",
description: "Agent-Einstellungen wurden aktualisiert."
});
await loadAgentConfig();
} catch (err) {
const message = err.response?.data?.error || err.message || "Agent konnte nicht aktualisiert werden";
showToast({ variant: "error", title: "Agent speichern fehlgeschlagen", description: message });
} finally {
setAgentSaving(false);
}
}, [selectedServerId, agentDraft, loadAgentConfig, showToast]);
}, [canEditServers, selectedServerId, serverDirty, serverDraft, loadServerDetail, showToast]);
const handleApiKeySave = useCallback(async () => {
if (!canEditServers || !selectedServerId) {
@@ -417,19 +457,79 @@ export function ServerDetail() {
});
setApiKeyDraft("");
await loadServerDetail();
await loadServerStatus({ silent: true });
} catch (err) {
const message = err.response?.data?.error || err.message || "API-Key konnte nicht gespeichert werden";
showToast({ variant: "error", title: "Speichern fehlgeschlagen", description: message });
} finally {
setApiKeySaving(false);
}
}, [apiKeyDraft, canEditServers, selectedServerId, updateSetupApiKey, loadServerDetail, loadServerStatus, showToast]);
}, [apiKeyDraft, canEditServers, selectedServerId, updateSetupApiKey, loadServerDetail, showToast]);
const handleStatusRefresh = useCallback(() => {
loadServerStatus();
}, [loadServerStatus]);
const duplicateTotals = useMemo(() => {
const groups = Array.isArray(duplicates) ? duplicates.length : 0;
const duplicateCount = Array.isArray(duplicates)
? duplicates.reduce((sum, entry) => sum + ((entry?.duplicates?.length) || 0), 0)
: 0;
return { groups, duplicateCount };
}, [duplicates]);
const handleDuplicateCleanup = useCallback(async (entry) => {
if (!canManageDuplicates || !entry) return;
const canonicalId = entry.canonical?.Id;
if (!canonicalId) return;
const duplicateIds = (entry.duplicates || []).map((dup) => dup.Id).filter(Boolean);
if (!duplicateIds.length) return;
const canonicalName = entry.canonical?.Name || entry.name || `Stack ${canonicalId}`;
if (typeof window !== "undefined") {
const confirmation = window.confirm(
`Bereinigung für "${canonicalName}" starten?\n` +
`Es werden ${duplicateIds.length} Duplikate entfernt: ${duplicateIds.join(", ")}`
);
if (!confirmation) {
return;
}
}
setActiveCleanupId(String(canonicalId));
try {
const response = await axios.post("/api/maintenance/duplicates/cleanup", {
canonicalId,
duplicateIds
});
const payload = response.data ?? {};
if (payload.success === false) {
throw new Error(payload.error || "Bereinigung fehlgeschlagen");
}
const removedIds = Array.isArray(payload.results)
? payload.results.filter((result) => result.status === "deleted").map((result) => result.id)
: duplicateIds;
showToast({
variant: "success",
title: "Bereinigung abgeschlossen",
description: `${canonicalName} – entfernte IDs: ${removedIds.join(", ")}`
});
await fetchDuplicates({ silent: true });
} catch (err) {
const message = err.response?.data?.error || err.message || "Bereinigung fehlgeschlagen";
showToast({
variant: "error",
title: "Bereinigung fehlgeschlagen",
description: message
});
} finally {
setActiveCleanupId(null);
}
}, [canManageDuplicates, fetchDuplicates, showToast]);
const handleDeleteServer = useCallback(async () => {
if (!canDeleteServers || !selectedServerId || serverDeleteId === selectedServerId) {
return;
@@ -593,6 +693,8 @@ export function ServerDetail() {
);
}
const isReadOnlyServer = !canEditServers && canReadServers;
return (
<div className="mt-12 mb-8 flex flex-col gap-12">
<Card>
@@ -633,13 +735,16 @@ export function ServerDetail() {
)}
{serverDetail && (
<>
{showPortainerSection ? (
<div className="rounded-md border border-blue-gray-100 p-4">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<Typography variant="small" color="blue-gray" className="font-semibold uppercase">
Verbindungsstatus
{statusFetchedAt && (
<span className="antialiased font-sans font-light text-xs text-blue-gray-400 pl-2">Stand: {formatCreatedAt(statusFetchedAt)}</span>
<span className="antialiased font-sans font-light text-xs text-blue-gray-400 pl-2">
Stand: {formatCreatedAt(statusFetchedAt)}
</span>
)}
</Typography>
<p className="text-sm text-stormGrey-600 break-all">{serverDetail.url}</p>
@@ -689,13 +794,19 @@ export function ServerDetail() {
size="sm"
onClick={handleStatusRefresh}
disabled={statusLoading}
className="w-full sm:w-auto"
>
{statusLoading ? "Prüfe…" : "Status aktualisieren"}
{statusLoading ? "Prüfe…" : "Status manuell abgleichen"}
</Button>
</div>
</div>
) : (
<Alert color="blue" className="border border-blue-200 bg-blue-50 text-blue-900">
Keine Berechtigung für die Portainer-Sektion.
</Alert>
)}
{showPortainerSection && canEditServers && (
<div className="rounded-md border border-blue-gray-100 p-4">
<Typography variant="small" color="blue-gray" className="font-semibold uppercase">
Server
@@ -705,17 +816,17 @@ export function ServerDetail() {
label="Servername"
value={serverDraft.name}
onChange={(event) => handleServerDraftChange("name", event.target.value)}
disabled={!canEditServers || serverSaving}
disabled={(!canEditServers && !isReadOnlyServer) || serverSaving || isReadOnlyServer}
/>
<Input
label="Server-URL"
value={serverDraft.url}
onChange={(event) => handleServerDraftChange("url", event.target.value)}
disabled={!canEditServers || serverSaving}
disabled={(!canEditServers && !isReadOnlyServer) || serverSaving || isReadOnlyServer}
/>
</div>
{canEditServers && (
<div className="flex justify-end mt-2 mb-5">
{canEditServers && !isReadOnlyServer && (
<div className="flex mt-2 mb-5">
<Button color="blue" size="sm" onClick={handleServerSave} disabled={serverSaving || !serverDirty}>
{serverSaving ? "Speichere…" : "Server speichern"}
</Button>
@@ -735,125 +846,21 @@ export function ServerDetail() {
disabled={!canEditServers || apiKeySaving}
/>
{canEditServers && (
<div className="flex justify-end">
<Button color="blue" size="sm" onClick={handleApiKeySave} disabled={apiKeySaving || !apiKeyDraft.trim()}>
<div className="flex">
<Button
color="blue"
size="sm"
onClick={handleApiKeySave}
disabled={apiKeySaving || !apiKeyDraft.trim()}
className="w-full sm:w-auto"
>
{apiKeySaving ? "Speichere…" : "API-Key speichern"}
</Button>
</div>
)}
</div>
</div>
{isCommunityEdition && (
<div className="rounded-md border border-blue-gray-100 p-4">
<div className="flex flex-col gap-4 md:flex-row md:items-center md:justify-between">
<div>
<Typography variant="small" color="blue-gray" className="font-semibold uppercase">
Agent (CE)
</Typography>
<p className="text-sm text-stormGrey-600 break-all">
{agentDraft.agentUrl || "Keine Agent-URL hinterlegt"}
</p>
{agentError && <p className="text-xs text-sunsetCoral-600 mt-1">{agentError}</p>}
</div>
<Chip
value={agentStatusLabel}
size="sm"
color={agentOnline === true ? "green" : agentOnline === false ? "red" : "blue-gray"}
variant="ghost"
/>
</div>
<div className="mt-4 grid gap-3 md:grid-cols-2">
<Input
label="Agent URL"
value={agentDraft.agentUrl}
onChange={(e) => handleAgentDraftChange("agentUrl", e.target.value)}
disabled={agentSaving}
/>
<div className="w-full">
<div className="relative w-full">
<Input
label="Agent Token"
value={agentDraft.agentToken}
readOnly
disabled
className="pr-24"
/>
<Button
variant="outlined"
color="gray"
size="sm"
className="!absolute right-1 top-1"
onClick={async () => {
if (!agentDraft.agentToken) return;
const text = agentDraft.agentToken;
const tryClipboard = async () => {
if (typeof navigator !== "undefined" && navigator?.clipboard?.writeText) {
await navigator.clipboard.writeText(text);
return true;
}
return false;
};
const tryFallback = () => {
try {
const textarea = document.createElement("textarea");
textarea.value = text;
textarea.style.position = "fixed";
textarea.style.opacity = "0";
document.body.appendChild(textarea);
textarea.select();
document.execCommand("copy");
document.body.removeChild(textarea);
return true;
} catch {
return false;
}
};
try {
const ok = (await tryClipboard()) || tryFallback();
if (ok) {
showToast({
variant: "success",
title: "Token kopiert",
description: "Agent-Token wurde in die Zwischenablage kopiert."
});
} else {
throw new Error("Clipboard API nicht verfügbar");
}
} catch (err) {
showToast({
variant: "error",
title: "Kopieren fehlgeschlagen",
description: err?.message || "Token konnte nicht kopiert werden."
});
}
}}
>
Kopieren
</Button>
</div>
</div>
</div>
<div className="mt-4 flex justify-end gap-2">
<Button
variant="outlined"
color="gray"
size="sm"
onClick={loadAgentConfig}
disabled={agentLoading}
>
{agentLoading ? "Lade…" : "Agent neu laden"}
</Button>
<Button color="blue" size="sm" onClick={handleAgentSave} disabled={agentSaving}>
{agentSaving ? "Speichere…" : "Agent speichern"}
</Button>
</div>
</div>
)}
</>
)}
</CardBody>
</Card>
{canViewSsh && (
<div className="grid gap-6 lg:grid-cols-2">
<Card>
@@ -1017,6 +1024,122 @@ export function ServerDetail() {
</Card>
</div>
)}
{canViewDuplicates && (
<Card>
<CardHeader variant="gradient" color="gray" className="mb-5 p-4">
<Typography
variant="h6"
color="white"
className="flex items-center justify-between"
>
<span>Doppelte Stacks</span>
</Typography>
</CardHeader>
<CardBody className="flex flex-col gap-4 p-4">
<div className="flex items-start justify-between gap-4">
<div className="space-y-1">
<p className="text-sm antialiased font-sans font-light leading-normal text-inherit">
{duplicatesLoading
? "Analyse läuft…"
: duplicateTotals.groups === 0
? "Keine Duplikate gefunden"
: `${duplicateTotals.groups} Stack-Namen mit insgesamt ${duplicateTotals.duplicateCount} Duplikaten gefunden`}
</p>
{duplicatesUpdatedAt && !duplicatesLoading && (
<p className="mt-1 text-xs text-gray-500">
Stand: {duplicatesUpdatedAt.toLocaleString("de-DE", {
hour: "2-digit",
minute: "2-digit",
second: "2-digit"
})}
</p>
)}
</div>
<Button
onClick={() => fetchDuplicates({ silent: false })}
disabled={duplicatesLoading || duplicatesRefreshing || activeCleanupId !== null}
>
Aktualisieren
</Button>
</div>
{duplicatesError && (
<Alert color="red" className="border border-red-200 bg-red-50 text-red-800">
{duplicatesError}
</Alert>
)}
{duplicatesLoading ? (
<div className="flex flex-col gap-4 p-4 text-sm text-blue-gray-500">
Daten werden geladen…
</div>
) : duplicateTotals.groups === 0 ? (
<div className="rounded-xl border-mossGreen-500/80 bg-mossGreen-900/90 text-mossGreen-100 p-6 text-center text-sm text-white">
Es wurden keine doppelten Stacks gefunden.
</div>
) : (
<div className="space-y-5">
{duplicates.map((entry) => {
const canonicalId = entry?.canonical?.Id;
const duplicatesForEntry = entry?.duplicates || [];
const isProcessing = activeCleanupId === String(canonicalId);
return (
<div
key={canonicalId || entry.name}
className="rounded-xl border border-gray-700 bg-gray-800/70 p-6 shadow"
>
<div className="flex flex-col gap-4 md:flex-row md:items-start md:justify-between">
<div className="space-y-1">
<div className="flex items-center gap-2">
<h3 className="text-lg font-semibold text-white">{entry.name}</h3>
<span className="rounded-full bg-amber-500/20 px-3 py-0.5 text-xs font-medium text-amber-200">
{duplicatesForEntry.length} Duplikat{duplicatesForEntry.length === 1 ? "" : "e"}
</span>
</div>
<p className="text-sm text-gray-300">
Behaltener Stack: ID {canonicalId}
</p>
<p className="text-xs text-gray-500">
Typ: {resolveStackType(entry?.canonical?.Type)} • Erstellt: {formatCreatedAt(entry?.canonical?.Created)}
</p>
</div>
<button
type="button"
onClick={() => handleDuplicateCleanup(entry)}
disabled={!canManageDuplicates || isProcessing || duplicatesRefreshing || duplicatesLoading}
className="self-start rounded-lg bg-sunsetCoral-500 px-4 py-2 text-sm font-semibold text-white transition hover:bg-sunsetCoral-600 disabled:opacity-50"
>
{isProcessing ? "Bereinigung läuft…" : `Bereinigen (${duplicatesForEntry.length})`}
</button>
</div>
<div className="mt-5 grid gap-3">
{duplicatesForEntry.map((duplicate) => (
<div
key={duplicate.Id}
className="rounded-lg border border-sunsetCoral-500/40 bg-sunsetCoral-900/20 p-4 text-sm text-white"
>
<div className="flex flex-wrap items-center justify-between gap-2">
<span className="font-semibold text-white">ID: {duplicate.Id}</span>
<span>Typ: {resolveStackType(duplicate.Type)}</span>
<span>Erstellt: {formatCreatedAt(duplicate.Created)}</span>
</div>
</div>
))}
</div>
</div>
);
})}
</div>
)}
</CardBody>
</Card>
)}
</>
)}
</CardBody>
</Card>
</div>
);
}
+417 -15
View File
@@ -14,7 +14,6 @@ import {
} from "@material-tailwind/react";
import { useToast } from "@/components/ToastProvider.jsx";
import { platformSettingsData } from "@/data";
import { AVATAR_COLORS } from "@/data/avatarColors.js";
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
import { useAuth } from "@/components/AuthProvider.jsx";
@@ -69,6 +68,47 @@ const mapUser = (item) => ({
securityPhraseDownloadedAt: item?.securityPhraseDownloadedAt || null
});
const normalizeServer = (item) => {
if (!item || typeof item !== "object") {
return null;
}
const id = Number(item.id ?? item.serverId ?? item.server_id);
if (!Number.isFinite(id)) {
return null;
}
const name = item.name || item.serverName || item.server_name || `Server ${id}`;
return {
id,
name,
url: item.url || item.serverUrl || item.server_url || ""
};
};
const normalizeAssignmentsList = (list, servers = []) => {
const serverMap = new Map(servers.map((server) => [server.id, server]));
if (!Array.isArray(list)) {
return [];
}
return list
.map((entry) => {
const serverId = Number(entry?.serverId ?? entry?.server_id);
if (!Number.isFinite(serverId)) {
return null;
}
const groupId = Number(entry?.groupId ?? entry?.group_id);
const serverFallback = serverMap.get(serverId);
return {
serverId,
serverName: entry?.serverName || entry?.server_name || serverFallback?.name || "",
serverUrl: entry?.serverUrl || entry?.server_url || serverFallback?.url || "",
groupId: Number.isFinite(groupId) ? groupId : null,
groupName: entry?.groupName || entry?.group_name || "",
useGlobalGroup: Boolean(entry?.useGlobalGroup ?? entry?.use_global_group)
};
})
.filter(Boolean);
};
const extractPrimaryGroupId = (user) => {
if (!user || !Array.isArray(user.groups) || user.groups.length === 0) {
return null;
@@ -123,6 +163,11 @@ export function UserDetails() {
const [securityPhraseLoading, setSecurityPhraseLoading] = useState(false);
const [securityPhraseError, setSecurityPhraseError] = useState("");
const [renewingSecurityPhrase, setRenewingSecurityPhrase] = useState(false);
const [serverAssignments, setServerAssignments] = useState([]);
const [availableServers, setAvailableServers] = useState([]);
const [assignmentsError, setAssignmentsError] = useState("");
const [savingAssignments, setSavingAssignments] = useState(false);
const initialAssignmentsRef = useRef([]);
const maintenanceActive = Boolean(maintenanceMeta?.active);
const maintenanceMessage = maintenanceMeta?.message;
@@ -172,6 +217,17 @@ export function UserDetails() {
if (!item.id) {
throw new Error("USER_NOT_FOUND");
}
const normalizedServers = Array.isArray(response.data?.servers)
? response.data.servers.map(normalizeServer).filter(Boolean)
: [];
setAvailableServers(normalizedServers);
const normalizedAssignments = normalizeAssignmentsList(
response.data?.item?.serverAssignments ?? response.data?.serverAssignments ?? [],
normalizedServers
);
initialAssignmentsRef.current = normalizedAssignments;
setServerAssignments(normalizedAssignments);
setAssignmentsError("");
setUser(item);
setSecurityPhraseWords([]);
setSecurityPhraseDownloadedAt(item.securityPhraseDownloadedAt || null);
@@ -195,6 +251,9 @@ export function UserDetails() {
setUser(null);
initialFormValuesRef.current = buildInitialFormValues(null);
setFormValues(buildInitialFormValues(null));
setServerAssignments([]);
setAvailableServers([]);
setAssignmentsError("");
setError(message);
showToast({
variant: "error",
@@ -341,6 +400,208 @@ export function UserDetails() {
}));
}, [canEditUsers]);
const assignmentsChanged = useMemo(() => {
const serialize = (list) =>
JSON.stringify(
list
.map((entry) => ({
serverId: Number(entry.serverId) || null,
groupId: entry.useGlobalGroup ? null : (Number(entry.groupId) || null),
useGlobalGroup: Boolean(entry.useGlobalGroup)
}))
.sort((a, b) => (a.serverId || 0) - (b.serverId || 0))
);
return serialize(serverAssignments) !== serialize(initialAssignmentsRef.current);
}, [serverAssignments]);
const usedServerIds = useMemo(
() =>
new Set(
serverAssignments
.map((entry) => Number(entry.serverId))
.filter((value) => Number.isFinite(value) && value > 0)
),
[serverAssignments]
);
const getServerOptionsForRow = useCallback(
(currentServerId) => {
return availableServers.filter(
(server) => server.id === currentServerId || !usedServerIds.has(server.id)
);
},
[availableServers, usedServerIds]
);
const handleAddAssignment = useCallback(() => {
if (!canEditUsers || isSuperuserUser) return;
const usedServerIds = new Set(
serverAssignments
.map((entry) => Number(entry.serverId))
.filter((value) => Number.isFinite(value) && value > 0)
);
const nextServer = availableServers.find((server) => !usedServerIds.has(server.id));
if (!nextServer) {
setAssignmentsError("Alle verfügbaren Server sind bereits zugeordnet.");
return;
}
setAssignmentsError("");
setServerAssignments((prev) => [
...prev,
{
serverId: nextServer.id,
serverName: nextServer.name ?? "",
serverUrl: nextServer.url ?? "",
groupId: null,
groupName: "",
useGlobalGroup: true
}
]);
}, [canEditUsers, serverAssignments, availableServers]);
const handleAssignmentServerChange = useCallback(
(index, value) => {
if (!canEditUsers || isSuperuserUser) return;
const numeric = Number(value);
const server = availableServers.find((item) => item.id === numeric);
setServerAssignments((prev) =>
prev.map((entry, idx) =>
idx === index
? {
...entry,
serverId: Number.isFinite(numeric) ? numeric : null,
serverName: server?.name || entry.serverName,
serverUrl: server?.url || entry.serverUrl
}
: entry
)
);
},
[availableServers, canEditUsers]
);
const handleAssignmentGroupChange = useCallback(
(index, value) => {
if (!canEditUsers || isSuperuserUser) return;
const numeric = Number(value);
setServerAssignments((prev) =>
prev.map((entry, idx) =>
idx === index
? {
...entry,
groupId: Number.isFinite(numeric) ? numeric : null,
useGlobalGroup: false
}
: entry
)
);
},
[canEditUsers]
);
const handleAssignmentUseGlobalToggle = useCallback(
(index, checked) => {
if (!canEditUsers || isSuperuserUser) return;
setServerAssignments((prev) =>
prev.map((entry, idx) =>
idx === index
? {
...entry,
useGlobalGroup: Boolean(checked),
groupId: checked ? null : entry.groupId
}
: entry
)
);
},
[canEditUsers]
);
const handleRemoveAssignment = useCallback(
(index) => {
if (!canEditUsers || isSuperuserUser) return;
setServerAssignments((prev) => prev.filter((_, idx) => idx !== index));
},
[canEditUsers, isSuperuserUser]
);
const handleSaveAssignments = useCallback(async () => {
if (!canEditUsers || !numericUserId || !assignmentsChanged || isSuperuserUser) {
return;
}
const normalizedPayload = [];
const seenServers = new Set();
for (const assignment of serverAssignments) {
const serverId = Number(assignment.serverId);
if (!Number.isFinite(serverId) || serverId <= 0) {
setAssignmentsError("Bitte wähle für jede Zuordnung einen Server aus.");
return;
}
if (seenServers.has(serverId)) {
setAssignmentsError("Jeder Server kann nur einmal zugeordnet werden.");
return;
}
seenServers.add(serverId);
const useGlobalGroup = Boolean(assignment.useGlobalGroup);
const groupId = Number(assignment.groupId);
if (!useGlobalGroup && (!Number.isFinite(groupId) || groupId <= 0)) {
setAssignmentsError("Bitte wähle eine Rechtegruppe oder aktiviere die globale Gruppe.");
return;
}
normalizedPayload.push({
serverId,
groupId: useGlobalGroup ? null : groupId,
useGlobalGroup
});
}
setAssignmentsError("");
setSavingAssignments(true);
try {
const response = await axios.put(
`/api/users/${numericUserId}/server-assignments`,
{ assignments: normalizedPayload }
);
const normalizedAssignments = normalizeAssignmentsList(
response.data?.items ?? [],
availableServers
);
initialAssignmentsRef.current = normalizedAssignments;
setServerAssignments(normalizedAssignments);
showToast({
variant: "success",
title: "Serverzuordnungen gespeichert",
description: "Die Zuordnungen wurden erfolgreich aktualisiert."
});
} catch (err) {
const serverError = err.response?.data?.error;
let message = "Die Serverzuordnungen konnten nicht gespeichert werden.";
if (serverError === "USER_NOT_FOUND") {
message = "Der Benutzer wurde nicht gefunden.";
} else if (serverError === "INVALID_USER_ID") {
message = "Die Benutzer-ID ist ungültig.";
} else if (serverError === "USER_SUPERUSER_PROTECTED") {
message = "Für den Superuser können keine Server-Zuordnungen gesetzt werden.";
} else if (serverError === "SERVER_NOT_FOUND") {
message = "Ein ausgewählter Server existiert nicht mehr.";
} else if (serverError === "GROUP_NOT_FOUND") {
message = "Eine ausgewählte Rechtegruppe existiert nicht mehr.";
} else if (serverError === "INSUFFICIENT_PERMISSIONS") {
message = "Keine Berechtigung zum Ändern der Zuordnungen.";
}
setAssignmentsError(message);
showToast({
variant: "error",
title: "Speichern fehlgeschlagen",
description: message
});
} finally {
setSavingAssignments(false);
}
}, [canEditUsers, numericUserId, serverAssignments, availableServers, assignmentsChanged, showToast]);
const handleSaveUser = useCallback(async () => {
if (!canEditUsers || !user || !hasChanges) {
return;
@@ -775,30 +1036,171 @@ export function UserDetails() {
</div>
<div>
<Typography variant="h6" color="blue-gray" className="mb-3">
Platform Settings
Server-Zuordnungen
</Typography>
<div className="flex flex-col gap-12">
{platformSettingsData.map(({ title, options }) => (
<div key={title}>
<Typography className="mb-4 block text-xs font-semibold uppercase text-blue-gray-500">
{title}
{isSuperuserUser ? (
<div className="mb-4 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">
Für den Superuser gelten immer alle Rechte und es können keine server-spezifischen Zuordnungen gesetzt werden.
</div>
) : (
<>
<Typography className="mb-4 text-sm text-blue-gray-500">
Ohne Zuordnungen hat der Benutzer Zugriff auf alle Server mit seinen globalen Rechten.
Lege hier Server-spezifische Gruppen fest oder markiere den Server explizit zur Nutzung der globalen Gruppe.
</Typography>
<div className="flex flex-col gap-6">
{options.map(({ checked, label }) => (
{assignmentsError && (
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
{assignmentsError}
</div>
)}
<div className="flex flex-col gap-4">
{serverAssignments.length === 0 ? (
<div className="rounded-lg border border-blue-gray-100 bg-blue-gray-50/60 px-4 py-3 text-sm text-blue-gray-600">
Keine Server zugeordnet. Die globalen Gruppen gelten für alle Server.
</div>
) : (
serverAssignments.map((assignment, index) => {
const serverOptions = getServerOptionsForRow(assignment.serverId);
const groupSelectValue =
assignment.useGlobalGroup || !assignment.groupId
? ""
: String(assignment.groupId);
return (
<div
key={`assignment-${assignment.serverId ?? index}-${index}`}
className="rounded-lg border border-blue-gray-100 bg-white px-4 py-3 shadow-sm"
>
<div className="grid gap-4 md:grid-cols-2">
<div>
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
Server
</Typography>
<Select
label="Server auswählen"
value={
assignment.serverId
? String(assignment.serverId)
: ""
}
onChange={(value) =>
handleAssignmentServerChange(index, value)
}
disabled={
maintenanceLocked ||
savingAssignments ||
!canEditUsers
}
variant="outlined"
>
{serverOptions.map((server) => (
<Option key={server.id} value={String(server.id)}>
{server.name}
</Option>
))}
</Select>
</div>
<div>
<Typography className="mb-2 block text-xs font-semibold uppercase text-blue-gray-500">
Rechtegruppe
</Typography>
<div className="flex flex-col gap-2">
<Switch
key={label}
id={label}
label={label}
defaultChecked={checked}
id={`assignment-global-${index}`}
label="Globale Gruppe verwenden"
checked={assignment.useGlobalGroup}
onChange={({ target }) =>
handleAssignmentUseGlobalToggle(
index,
target?.checked
)
}
disabled={
maintenanceLocked ||
savingAssignments ||
!canEditUsers
}
labelProps={{
className: "text-sm font-normal text-blue-gray-500",
className: "text-sm font-normal text-blue-gray-600"
}}
/>
<Select
label="Server-Gruppe wählen"
value={groupSelectValue}
onChange={(value) =>
handleAssignmentGroupChange(index, value)
}
disabled={
maintenanceLocked ||
savingAssignments ||
!canEditUsers ||
assignment.useGlobalGroup
}
variant="outlined"
className="max-w-xl"
>
<Option value="">Globale Gruppe</Option>
{availableGroups.map((group) => (
<Option key={group.id} value={String(group.id)}>
{group.name}
</Option>
))}
</Select>
</div>
</div>
))}
</div>
<div className="mt-3 flex justify-end">
<Button
variant="text"
color="red"
className="normal-case"
onClick={() => handleRemoveAssignment(index)}
disabled={
maintenanceLocked ||
savingAssignments ||
!canEditUsers
}
>
Entfernen
</Button>
</div>
</div>
);
})
)}
</div>
<div className="mt-4 flex flex-wrap items-center gap-3">
<Button
variant="outlined"
color="blue"
className="normal-case"
onClick={handleAddAssignment}
disabled={
maintenanceLocked ||
savingAssignments ||
!canEditUsers ||
availableServers.length === 0 ||
usedServerIds.size >= availableServers.length
}
>
Server hinzufügen
</Button>
<Button
color="green"
className="normal-case"
onClick={handleSaveAssignments}
disabled={
maintenanceLocked ||
savingAssignments ||
!canEditUsers ||
!assignmentsChanged
}
>
{savingAssignments ? "Speichert ..." : "Zuordnungen speichern"}
</Button>
{savingAssignments && <Spinner className="h-4 w-4" />}
</div>
</>
)}
</div>
</div>
+178 -88
View File
@@ -78,8 +78,14 @@ const LEVEL_PRIORITY = {
full: 2
};
const normalizePermissionLevel = (key, value) =>
key === "maintenance-server-delete" && value !== "full" ? "none" : value;
const isDependencySatisfied = (value, requirement) => {
const req = requirement || "!=none";
if (req === "!=none") return value !== "none";
if (req.startsWith("!=")) return value !== req.substring(2);
if (req.startsWith("=")) return value === req.substring(1);
if (!req) return true;
return value === req;
};
export function UserGroupDetail() {
const { groupId } = useParams();
@@ -177,22 +183,28 @@ export function UserGroupDetail() {
};
};
const normalizeGroup = (group, groupIdx = 0) => {
const groupItems = Array.isArray(group.items)
? group.items.map((item, itemIdx) => normalizeItem(item, itemIdx)).filter(Boolean)
: [];
const childGroups = Array.isArray(group.groups)
? group.groups.map((child, childIdx) => normalizeGroup(child, childIdx))
: [];
return {
...group,
sortOrder: typeof group.sortOrder === "number" ? group.sortOrder : groupIdx,
items: groupItems,
groups: childGroups
};
};
const normalizedSections = rawSections.map((section, sectionIndex) => {
const sectionItems = Array.isArray(section.items)
? section.items.map((item, itemIdx) => normalizeItem(item, itemIdx)).filter(Boolean)
: [];
const sectionGroups = Array.isArray(section.groups)
? section.groups.map((group, groupIdx) => {
const groupItems = Array.isArray(group.items)
? group.items.map((item, itemIdx) => normalizeItem(item, itemIdx)).filter(Boolean)
: [];
return {
...group,
sortOrder: typeof group.sortOrder === "number" ? group.sortOrder : groupIdx,
items: groupItems
};
})
? section.groups.map((group, groupIdx) => normalizeGroup(group, groupIdx))
: [];
return {
@@ -515,16 +527,52 @@ export function UserGroupDetail() {
if (!permissionKey || !canAdjustPermissions || isSuperuserGroup) {
return;
}
const nextValue = normalizePermissionLevel(permissionKey, value);
setPermissionSelection((prev) => {
const shouldResetDelete =
permissionKey === "maintenance-server-manage" && nextValue !== "full";
const shouldResetEdit =
permissionKey === "maintenance-server-manage" && nextValue === "none";
const shouldResetDeleteFromEdit =
permissionKey === "maintenance-server-edit" && nextValue !== "full";
const getValueWithDefaults = (key, selectionMap) =>
selectionMap[key] ?? permissionDefaults[key] ?? "none";
if (!shouldResetDelete && !shouldResetEdit && !shouldResetDeleteFromEdit && prev[permissionKey] === nextValue) {
const collectAllItems = () => {
const items = [];
const walkGroups = (groups) => {
(groups || []).forEach((group) => {
(group.items || []).forEach((item) => items.push(item));
if (group.groups) {
walkGroups(group.groups);
}
});
};
permissionSections.forEach((section) => {
(section.items || []).forEach((item) => items.push(item));
walkGroups(section.groups);
});
return items;
};
const allItems = collectAllItems();
const itemMap = new Map(allItems.map((item) => [item.key, item]));
const clampToAllowed = (itemKey, desiredLevel) => {
const item = itemMap.get(itemKey);
if (!item) return desiredLevel;
const allowed = Array.isArray(item.availableLevels) && item.availableLevels.length
? item.availableLevels
: LEVEL_OPTIONS.map((opt) => opt.value);
if (allowed.includes(desiredLevel)) {
return desiredLevel;
}
// Fallback: wähle niedrigstes erlaubtes Level
return allowed.reduce((lowest, candidate) => {
return LEVEL_PRIORITY[candidate] < LEVEL_PRIORITY[lowest] ? candidate : lowest;
}, allowed[0] || "none");
};
const nextValue = clampToAllowed(permissionKey, value);
setPermissionSelection((prev) => {
const currentValue = getValueWithDefaults(permissionKey, prev);
const isDowngrade = LEVEL_PRIORITY[nextValue] < LEVEL_PRIORITY[currentValue];
if (prev[permissionKey] === nextValue) {
return prev;
}
@@ -533,19 +581,31 @@ export function UserGroupDetail() {
[permissionKey]: nextValue
};
if (shouldResetDelete) {
nextState["maintenance-server-delete"] = "none";
// Kaskade: Nur bei Downgrade des auslösenden Rechts abhängige Rechte herabstufen.
if (isDowngrade) {
let changed = true;
while (changed) {
changed = false;
allItems.forEach((item) => {
const dependencies = Array.isArray(item.dependencies) ? item.dependencies : [];
if (!dependencies.length) return;
const satisfied = dependencies.every((dep) =>
isDependencySatisfied(getValueWithDefaults(dep.key, nextState), dep.requiredLevel)
);
if (!satisfied) {
const cascadedValue = clampToAllowed(item.key, nextValue);
if (getValueWithDefaults(item.key, nextState) !== cascadedValue) {
nextState[item.key] = cascadedValue;
changed = true;
}
if (shouldResetEdit) {
nextState["maintenance-server-edit"] = "none";
}
if (shouldResetDeleteFromEdit) {
nextState["maintenance-server-delete"] = "none";
});
}
}
return nextState;
});
}, [canAdjustPermissions, isSuperuserGroup]);
}, [canAdjustPermissions, isSuperuserGroup, permissionDefaults, permissionSections]);
const getPermissionValue = useCallback(
(permissionKey) => {
@@ -559,7 +619,7 @@ export function UserGroupDetail() {
value = permissionDefaults[permissionKey];
}
if (typeof value === "string") {
return normalizePermissionLevel(permissionKey, value);
return value;
}
return value;
},
@@ -572,12 +632,6 @@ export function UserGroupDetail() {
),
[]
);
const radioCheckedIcon = useMemo(
() => (
<span className="mx-auto block h-2.5 w-2.5 rounded-full border border-blue-gray-700 bg-gray-900" />
),
[]
);
const isPermissionRowVisible = useCallback(
(row) => {
@@ -617,9 +671,7 @@ export function UserGroupDetail() {
const rowName = `permission-${ownerKey}-${row.key}`;
const currentValue = getPermissionValue(row.key) ?? "none";
const displayValue = normalizePermissionLevel(row.key, currentValue);
const serverManageLevel = getPermissionValue("maintenance-server-manage") ?? "none";
const serverEditLevel = getPermissionValue("maintenance-server-edit") ?? "none";
const displayValue = currentValue;
const { addTopBorder = true } = options;
const rowClasses = [
@@ -632,11 +684,7 @@ export function UserGroupDetail() {
const baseLevels = Array.isArray(row.availableLevels) && row.availableLevels.length
? row.availableLevels
: LEVEL_OPTIONS.map((option) => option.value);
const availableLevels = new Set(
row.key === "maintenance-server-delete"
? baseLevels.filter((level) => level === "full" || level === "none")
: baseLevels
);
const availableLevels = new Set(baseLevels);
const levelOptions = LEVEL_OPTIONS;
return (
@@ -648,22 +696,12 @@ export function UserGroupDetail() {
{levelOptions.map((option) => {
const optionId = `${rowName}-${option.value}`;
const checked = displayValue === option.value;
const requiresManageFull =
row.key === "maintenance-server-delete" &&
option.value === "full" &&
(LEVEL_PRIORITY[serverManageLevel] ?? 0) < LEVEL_PRIORITY.full;
const requiresEditFull =
row.key === "maintenance-server-delete" &&
option.value === "full" &&
(LEVEL_PRIORITY[serverEditLevel] ?? 0) < LEVEL_PRIORITY.full;
const disabled =
!availableLevels.has(option.value) ||
permissionsLoading ||
savingGroup ||
!canAdjustPermissions ||
isSuperuserGroup ||
requiresManageFull ||
requiresEditFull;
isSuperuserGroup;
return (
<ListItem
@@ -689,7 +727,6 @@ export function UserGroupDetail() {
className: "p-0"
}}
icon={radioIcon}
checkedIcon={radioCheckedIcon}
/>
</ListItemPrefix>
<Typography
@@ -708,6 +745,30 @@ export function UserGroupDetail() {
);
};
const groupHasVisibleRows = (grp) => {
if (!grp) {
return false;
}
const groupItems = Array.isArray(grp.items) ? grp.items : [];
const childGroups = Array.isArray(grp.groups) ? grp.groups : [];
if (groupItems.some(isPermissionRowVisible)) {
return true;
}
return childGroups.some(groupHasVisibleRows);
};
const sectionHasVisibleRows = (section) => {
if (!section) {
return false;
}
const sectionItems = Array.isArray(section.items) ? section.items : [];
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
if (sectionItems.some(isPermissionRowVisible)) {
return true;
}
return sectionGroups.some(groupHasVisibleRows);
};
return (
<>
<div className="mt-12 flex flex-col gap-12">
@@ -725,9 +786,6 @@ export function UserGroupDetail() {
</div>
</div>
)}
<div className="relative h-72 w-full overflow-hidden rounded-xl bg-[url('/img/background-image.png')] bg-cover\tbg-center">
<div className="absolute inset-0 h-full w-full bg-gray-900/75" />
</div>
<Card className="mx-3 -mt-16 mb-6 lg:mx-4 border border-blue-gray-100">
<CardBody className="p-4">
<div className="mb-10 flex flex-wrap items-center justify-between gap-6">
@@ -893,7 +951,7 @@ export function UserGroupDetail() {
Die Superuser-Gruppe besitzt automatisch Vollzugriff auf alle Bereiche. Berechtigungen können hier nicht angepasst werden.
</div>
) : !canViewPermissionSettings ? null : (
<div className="rounded-xl border border-blue-gray-100 bg-white shadow-sm">
<>
{permissionsLoading && (
<div className="border-b border-blue-gray-100 px-4 py-4">
<div className="flex items-center gap-3 text-sm text-blue-gray-500">
@@ -913,53 +971,85 @@ export function UserGroupDetail() {
Für diese Gruppe sind keine Berechtigungen definiert.
</div>
) : (
permissionSections.map((section, sectionIndex) => {
(() => {
const renderedSections = permissionSections
.map((section, sectionIndex) => {
const sectionItems = Array.isArray(section.items) ? section.items : [];
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
if (!sectionHasVisibleRows(section)) {
return null;
}
const renderGroup = (currentGroup, path = `${section.key || sectionIndex}-group`, depth = 0) => {
if (!groupHasVisibleRows(currentGroup)) {
return null;
}
const groupItems = Array.isArray(currentGroup.items) ? currentGroup.items : [];
const childGroups = Array.isArray(currentGroup.groups) ? currentGroup.groups : [];
return (
<div
key={currentGroup.key || path}
className=""
>
<div className="border-b border-blue-gray-100 bg-blue-gray-50/80 px-4 py-3">
<Typography className={`${depth > 0 ? "ml-2" : ""} text-xs font-semibold uppercase tracking-wide text-blue-gray-600`}>
{currentGroup.title}
</Typography>
</div>
<div className="divide-y divide-blue-gray-100">
{groupItems.map((row) =>
renderPermissionRow(row, `${path}-${row.key}`, {
addTopBorder: false
})
)}
{childGroups.map((childGroup, childIdx) =>
renderGroup(childGroup, `${path}-${childGroup.key || childIdx}`, depth + 1)
)}
</div>
</div>
);
};
return (
<div
key={section.key || sectionIndex}
className={sectionIndex === 0 ? "" : "border-t border-blue-gray-100"}
className="overflow-hidden rounded-xl border border-blue-gray-100 bg-white shadow-sm"
>
<div className="border-b border-blue-gray-100 px-4 py-4">
<div className="border-b border-blue-gray-100 bg-blue-gray-50/70 px-4 py-3">
<Typography variant="h6" className="text-lg font-semibold text-blue-gray-700">
{section.title}
</Typography>
</div>
{sectionItems.map((row, rowIndex) =>
<div className="divide-y divide-blue-gray-100">
{sectionItems.map((row) =>
renderPermissionRow(row, section.key || sectionIndex, {
addTopBorder: rowIndex !== 0
addTopBorder: false
})
)}
{sectionGroups.map((group, groupIdx) => {
const groupItems = Array.isArray(group.items) ? group.items : [];
const hasVisibleRows = groupItems.some(isPermissionRowVisible);
{sectionGroups.map((group, groupIdx) =>
renderGroup(group, `${section.key || sectionIndex}-${groupIdx}`, 0)
)}
</div>
</div>
);
})
.filter(Boolean);
if (!hasVisibleRows) {
return null;
if (renderedSections.length === 0) {
return (
<div className="border-b border-blue-gray-100 px-4 py-4 text-sm text-blue-gray-500">
Für diese Gruppe sind keine Berechtigungen definiert.
</div>
);
}
return (
<div key={group.key || `${section.key || sectionIndex}-${groupIdx}`} className="border-t border-blue-gray-100">
<div className="border-b border-blue-gray-100 px-4 py-3">
<Typography className="text-sm font-semibold uppercase tracking-wide text-blue-gray-600">
{group.title}
</Typography>
</div>
{groupItems.map((row, rowIndex) =>
renderPermissionRow(row, group.key || `${section.key || sectionIndex}-${groupIdx}`, {
addTopBorder: rowIndex !== 0
})
)}
</div>
);
})}
</div>
);
})
return <div className="flex flex-col gap-4">{renderedSections}</div>;
})()
))}
</div>
</>
)}
</>
+4 -1
View File
@@ -12,9 +12,12 @@ nav:
- Wartung: user-guide/maintenance.md
- Benutzer: user-guide/users.md
- Benutzergruppen & Rechte: user-guide/groups.md
- Technik:
- Getting Started:
- Getting Started: getting-started.md
- Umgebungsvariablen: environment.md
- Technik:
- Architektur: architecture.md
- Backend & API: backend.md
- Frontend: frontend.md
- Lokale Entwicklung: local-dev.md
- Operations & Wartung: operations.md