Update
This commit is contained in:
1 parent
fc3ed57a40
commit
f109423f54
39 files changed
+2914
-2958
No files matched your search
@@ -3,7 +3,7 @@
|
|||||||
# 📦 StackPulse 
|
# 📦 StackPulse 
|
||||||
|
|
||||||
**StackPulse** ist eine kleine Web-App, die über die Portainer-API deine Docker-Stacks verwaltet und aktualisiert.
|
**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)**.
|
Sie besteht aus einem **Backend (Node.js/Express)** und einem **Frontend (React/Tailwind)**.
|
||||||
|
|
||||||
Ziel:
|
Ziel:
|
||||||
@@ -124,9 +124,8 @@ Ziel:
|
|||||||
- Notifications (z. B. via Webhooks oder Mail)
|
- Notifications (z. B. via Webhooks oder Mail)
|
||||||
- Monitoring (Status, CPU/RAM)
|
- Monitoring (Status, CPU/RAM)
|
||||||
- Verbesserte UI/UX
|
- Verbesserte UI/UX
|
||||||
- Erweiterte Filterungen udn Sortierungen
|
- Erweiterte Filterungen und Sortierungen
|
||||||
- Multi-Server Verwaltung
|
- Multi-Server Verwaltung
|
||||||
- Integration Community Edition
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -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"]
|
|
||||||
@@ -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.
|
|
||||||
@@ -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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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}`);
|
|
||||||
});
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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;
|
|
||||||
};
|
|
||||||
@@ -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
@@ -69,6 +69,21 @@ CREATE TABLE user_group_memberships (
|
|||||||
FOREIGN KEY (group_id) REFERENCES user_groups(id) ON DELETE CASCADE
|
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 (
|
CREATE TABLE permissions (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
key TEXT NOT NULL UNIQUE,
|
key TEXT NOT NULL UNIQUE,
|
||||||
@@ -100,13 +115,15 @@ CREATE TABLE permission_sections (
|
|||||||
CREATE TABLE permission_groups (
|
CREATE TABLE permission_groups (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
section_id INTEGER NOT NULL,
|
section_id INTEGER NOT NULL,
|
||||||
|
parent_group_id INTEGER,
|
||||||
key TEXT NOT NULL,
|
key TEXT NOT NULL,
|
||||||
title TEXT NOT NULL,
|
title TEXT NOT NULL,
|
||||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
UNIQUE(section_id, key),
|
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 (
|
CREATE TABLE permission_items (
|
||||||
@@ -118,6 +135,7 @@ CREATE TABLE permission_items (
|
|||||||
sort_order INTEGER NOT NULL DEFAULT 0,
|
sort_order INTEGER NOT NULL DEFAULT 0,
|
||||||
default_level TEXT NOT NULL,
|
default_level TEXT NOT NULL,
|
||||||
available_levels TEXT NOT NULL,
|
available_levels TEXT NOT NULL,
|
||||||
|
is_global_scope INTEGER NOT NULL DEFAULT 1,
|
||||||
is_required INTEGER NOT NULL DEFAULT 0,
|
is_required INTEGER NOT NULL DEFAULT 0,
|
||||||
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||||
updated_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
|
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 INDEX idx_server_update_scripts_server ON server_update_scripts (server_id);
|
||||||
|
|
||||||
CREATE TABLE user_settings (
|
CREATE TABLE user_settings (
|
||||||
|
|||||||
+234
-233
@@ -2,7 +2,6 @@ import fs from 'fs';
|
|||||||
import path from 'path';
|
import path from 'path';
|
||||||
import { fileURLToPath } from 'url';
|
import { fileURLToPath } from 'url';
|
||||||
import { db } from './index.js';
|
import { db } from './index.js';
|
||||||
import crypto from 'crypto';
|
|
||||||
|
|
||||||
const BLUEPRINT_FILENAME = 'dbs';
|
const BLUEPRINT_FILENAME = 'dbs';
|
||||||
const PERMISSION_BLUEPRINT = [
|
const PERMISSION_BLUEPRINT = [
|
||||||
@@ -16,6 +15,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'stacks-redeploy-single',
|
key: 'stacks-redeploy-single',
|
||||||
label: 'Redeploy einzeln',
|
label: 'Redeploy einzeln',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: false,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: []
|
dependencies: []
|
||||||
@@ -24,6 +24,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'stacks-redeploy-selection',
|
key: 'stacks-redeploy-selection',
|
||||||
label: 'Redeploy Auswahl',
|
label: 'Redeploy Auswahl',
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
|
global: false,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: []
|
dependencies: []
|
||||||
@@ -32,6 +33,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'stacks-redeploy-all',
|
key: 'stacks-redeploy-all',
|
||||||
label: 'Redeploy Alle',
|
label: 'Redeploy Alle',
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
|
global: false,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: []
|
dependencies: []
|
||||||
@@ -48,6 +50,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'logs-access',
|
key: 'logs-access',
|
||||||
label: 'Bereich & Navigation',
|
label: 'Bereich & Navigation',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
isRequired: 1,
|
isRequired: 1,
|
||||||
@@ -57,6 +60,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'logs-export',
|
key: 'logs-export',
|
||||||
label: 'Logs Exportieren',
|
label: 'Logs Exportieren',
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -67,6 +71,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'logs-delete',
|
key: 'logs-delete',
|
||||||
label: 'Logs löschen',
|
label: 'Logs löschen',
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -85,6 +90,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'users-access',
|
key: 'users-access',
|
||||||
label: 'Bereich & Navigation',
|
label: 'Bereich & Navigation',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
isRequired: 1,
|
isRequired: 1,
|
||||||
@@ -94,6 +100,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'users-edit',
|
key: 'users-edit',
|
||||||
label: 'Benutzer bearbeiten',
|
label: 'Benutzer bearbeiten',
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'read',
|
defaultLevel: 'read',
|
||||||
levels: ['full', 'read'],
|
levels: ['full', 'read'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -104,6 +111,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'users-delete',
|
key: 'users-delete',
|
||||||
label: 'Benutzer löschen',
|
label: 'Benutzer löschen',
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -114,6 +122,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'users-security-phrase',
|
key: 'users-security-phrase',
|
||||||
label: 'Sicherheitsschlüssel',
|
label: 'Sicherheitsschlüssel',
|
||||||
sortOrder: 3,
|
sortOrder: 3,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -132,6 +141,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'user-groups-access',
|
key: 'user-groups-access',
|
||||||
label: 'Bereich & Navigation',
|
label: 'Bereich & Navigation',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
isRequired: 1,
|
isRequired: 1,
|
||||||
@@ -141,6 +151,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'user-groups-edit',
|
key: 'user-groups-edit',
|
||||||
label: 'Benutzergruppen bearbeiten',
|
label: 'Benutzergruppen bearbeiten',
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'read',
|
defaultLevel: 'read',
|
||||||
levels: ['full', 'read'],
|
levels: ['full', 'read'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -151,6 +162,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'user-groups-delete',
|
key: 'user-groups-delete',
|
||||||
label: 'Benutzergruppen löschen',
|
label: 'Benutzergruppen löschen',
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -169,6 +181,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'maintenance-access',
|
key: 'maintenance-access',
|
||||||
label: 'Bereich & Navigation',
|
label: 'Bereich & Navigation',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
isRequired: 1,
|
isRequired: 1,
|
||||||
@@ -185,8 +198,9 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'maintenance-server-manage',
|
key: 'maintenance-server-manage',
|
||||||
label: 'Server-Sektion',
|
label: 'Server-Sektion',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'read', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
|
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
|
||||||
]
|
]
|
||||||
@@ -195,8 +209,9 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'maintenance-server-edit',
|
key: 'maintenance-server-edit',
|
||||||
label: 'Server bearbeiten',
|
label: 'Server bearbeiten',
|
||||||
sortOrder: 1,
|
sortOrder: 1,
|
||||||
|
global: false,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'read', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
|
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' },
|
||||||
{ dependsOnKey: 'maintenance-server-manage', requiredLevel: '!=none' }
|
{ dependsOnKey: 'maintenance-server-manage', requiredLevel: '!=none' }
|
||||||
@@ -206,6 +221,7 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
key: 'maintenance-server-delete',
|
key: 'maintenance-server-delete',
|
||||||
label: 'Server löschen',
|
label: 'Server löschen',
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
|
global: false,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -214,6 +230,58 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
{ dependsOnKey: 'maintenance-server-edit', requiredLevel: '=full' }
|
{ 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',
|
key: 'maintenance-superuser-delete',
|
||||||
label: 'Superuser löschen',
|
label: 'Superuser löschen',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
@@ -234,58 +303,20 @@ const PERMISSION_BLUEPRINT = [
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'maintenance-portainer-group',
|
key: 'maintenance-mtls-group',
|
||||||
title: 'Portainer',
|
title: 'mTLS',
|
||||||
sortOrder: 2,
|
sortOrder: 2,
|
||||||
items: [
|
items: [
|
||||||
{
|
{
|
||||||
key: 'maintenance-portainer',
|
key: 'maintenance-mtls',
|
||||||
label: 'Portainer-Sektion',
|
label: 'mTLS Sektion',
|
||||||
sortOrder: 0,
|
sortOrder: 0,
|
||||||
|
global: true,
|
||||||
defaultLevel: 'none',
|
defaultLevel: 'none',
|
||||||
levels: ['full', 'none'],
|
levels: ['full', 'none'],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
{ dependsOnKey: 'maintenance-access', requiredLevel: '!=none' }
|
{ 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 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) => {
|
deprecatedTables.forEach((table) => {
|
||||||
if (tableExists(table)) {
|
if (tableExists(table)) {
|
||||||
db.exec(`DROP TABLE IF EXISTS ${table}`);
|
db.exec(`DROP TABLE IF EXISTS ${table}`);
|
||||||
@@ -453,20 +490,24 @@ const ensurePermissionSeeds = () => {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const selectGroup = db.prepare(`
|
const selectGroup = db.prepare(`
|
||||||
SELECT id, title, sort_order
|
SELECT id, title, sort_order, parent_group_id
|
||||||
FROM permission_groups
|
FROM permission_groups
|
||||||
WHERE section_id = ? AND key = ?
|
WHERE section_id = ? AND key = ?
|
||||||
LIMIT 1
|
LIMIT 1
|
||||||
`);
|
`);
|
||||||
const insertGroup = db.prepare(`
|
const insertGroup = db.prepare(`
|
||||||
INSERT INTO permission_groups (section_id, key, title, sort_order, created_at, updated_at)
|
INSERT INTO permission_groups (section_id, parent_group_id, key, title, sort_order, created_at, updated_at)
|
||||||
VALUES (?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
VALUES (?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)
|
||||||
`);
|
`);
|
||||||
const updateGroup = db.prepare(`
|
const updateGroup = db.prepare(`
|
||||||
UPDATE permission_groups
|
UPDATE permission_groups
|
||||||
SET title = ?, sort_order = ?, updated_at = CURRENT_TIMESTAMP
|
SET title = ?, sort_order = ?, parent_group_id = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?
|
WHERE id = ?
|
||||||
`);
|
`);
|
||||||
|
const deleteUnusedGroups = db.prepare(`
|
||||||
|
DELETE FROM permission_groups
|
||||||
|
WHERE key NOT IN (SELECT value FROM json_each(?))
|
||||||
|
`);
|
||||||
|
|
||||||
const selectItem = db.prepare(`
|
const selectItem = db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
@@ -477,6 +518,7 @@ const ensurePermissionSeeds = () => {
|
|||||||
sort_order,
|
sort_order,
|
||||||
default_level,
|
default_level,
|
||||||
available_levels,
|
available_levels,
|
||||||
|
is_global_scope,
|
||||||
is_required
|
is_required
|
||||||
FROM permission_items
|
FROM permission_items
|
||||||
WHERE key = ?
|
WHERE key = ?
|
||||||
@@ -491,11 +533,12 @@ const ensurePermissionSeeds = () => {
|
|||||||
sort_order,
|
sort_order,
|
||||||
default_level,
|
default_level,
|
||||||
available_levels,
|
available_levels,
|
||||||
|
is_global_scope,
|
||||||
is_required,
|
is_required,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
) VALUES (
|
) VALUES (
|
||||||
?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
?, ?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP
|
||||||
)
|
)
|
||||||
`);
|
`);
|
||||||
const updateItem = db.prepare(`
|
const updateItem = db.prepare(`
|
||||||
@@ -507,10 +550,24 @@ const ensurePermissionSeeds = () => {
|
|||||||
sort_order = ?,
|
sort_order = ?,
|
||||||
default_level = ?,
|
default_level = ?,
|
||||||
available_levels = ?,
|
available_levels = ?,
|
||||||
|
is_global_scope = ?,
|
||||||
is_required = ?,
|
is_required = ?,
|
||||||
updated_at = CURRENT_TIMESTAMP
|
updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?
|
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(`
|
const selectDependency = db.prepare(`
|
||||||
SELECT id, required_level
|
SELECT id, required_level
|
||||||
@@ -536,6 +593,100 @@ const ensurePermissionSeeds = () => {
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const itemKeyToId = new Map();
|
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) => {
|
PERMISSION_BLUEPRINT.forEach((section, sectionIndex) => {
|
||||||
const existingSection = selectSection.get(section.key);
|
const existingSection = selectSection.get(section.key);
|
||||||
@@ -568,190 +719,41 @@ const ensurePermissionSeeds = () => {
|
|||||||
|
|
||||||
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
||||||
sectionItems.forEach((item, itemIdx) => {
|
sectionItems.forEach((item, itemIdx) => {
|
||||||
const existingItem = selectItem.get(item.key);
|
upsertItem({ item, sectionId, groupId: null, sortOrder: typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx });
|
||||||
const sortOrder = typeof item.sortOrder === 'number' ? item.sortOrder : itemIdx;
|
});
|
||||||
const levelOptions = Array.isArray(item.levels) && item.levels.length
|
|
||||||
? item.levels
|
processGroups(section.groups, sectionId, null);
|
||||||
: ['full', 'read', 'none'];
|
});
|
||||||
const availableLevels = JSON.stringify(levelOptions);
|
|
||||||
const isRequired = item.isRequired ? 1 : 0;
|
dependencyQueue.forEach(({ itemKey, dependencies }) => {
|
||||||
if (!existingItem) {
|
const permissionId = itemKeyToId.get(itemKey);
|
||||||
const result = insertItem.run(
|
if (!permissionId) return;
|
||||||
sectionId,
|
dependencies.forEach((dependency) => {
|
||||||
null,
|
const dependsId = itemKeyToId.get(dependency.dependsOnKey);
|
||||||
item.key,
|
if (!dependsId) return;
|
||||||
item.label,
|
const existing = selectDependency.get(permissionId, dependsId);
|
||||||
sortOrder,
|
const requiredLevel = dependency.requiredLevel ?? null;
|
||||||
item.defaultLevel || 'none',
|
if (!existing) {
|
||||||
availableLevels,
|
insertDependency.run(permissionId, dependsId, requiredLevel);
|
||||||
isRequired
|
console.log(`ℹ️ Abhängigkeit ${itemKey} -> ${dependency.dependsOnKey} angelegt`);
|
||||||
);
|
} else if (existing.required_level !== requiredLevel) {
|
||||||
itemKeyToId.set(item.key, Number(result.lastInsertRowid));
|
updateDependency.run(requiredLevel, existing.id);
|
||||||
console.log(`ℹ️ Berechtigung ${item.key} angelegt`);
|
console.log(`ℹ️ Abhängigkeit ${itemKey} -> ${dependency.dependsOnKey} aktualisiert`);
|
||||||
} 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`);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
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`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
PERMISSION_BLUEPRINT.forEach((section) => {
|
// Cleanup obsolete entries
|
||||||
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
if (itemKeys.size > 0) {
|
||||||
sectionItems.forEach((item) => {
|
const itemKeyJson = JSON.stringify(Array.from(itemKeys));
|
||||||
const permissionId = itemKeyToId.get(item.key);
|
deleteOrphanDependencies.run();
|
||||||
if (!permissionId) return;
|
deleteOrphanPermissionValues.run();
|
||||||
(item.dependencies || []).forEach((dependency) => {
|
deleteUnusedItems.run(itemKeyJson);
|
||||||
const dependsId = itemKeyToId.get(dependency.dependsOnKey);
|
}
|
||||||
if (!dependsId) return;
|
if (groupKeys.size > 0) {
|
||||||
const existing = selectDependency.get(permissionId, dependsId);
|
const groupKeyJson = JSON.stringify(Array.from(groupKeys));
|
||||||
const requiredLevel = dependency.requiredLevel ?? null;
|
deleteUnusedGroups.run(groupKeyJson);
|
||||||
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`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
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`
|
|
||||||
);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
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 = () => {
|
export const ensureDatabaseSchema = () => {
|
||||||
@@ -762,7 +764,6 @@ export const ensureDatabaseSchema = () => {
|
|||||||
|
|
||||||
dropDeprecatedArtifacts();
|
dropDeprecatedArtifacts();
|
||||||
ensureTablesAndColumns(tableDefinitions);
|
ensureTablesAndColumns(tableDefinitions);
|
||||||
ensureServerAgentEntries();
|
|
||||||
ensureIndexes(indexDefinitions);
|
ensureIndexes(indexDefinitions);
|
||||||
ensurePermissionSeeds();
|
ensurePermissionSeeds();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
+539
-176
File diff suppressed because it is too large.
Load diff
@@ -13,7 +13,7 @@ const selectSections = db.prepare(`
|
|||||||
`);
|
`);
|
||||||
|
|
||||||
const selectGroups = 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
|
FROM permission_groups
|
||||||
ORDER BY section_id ASC, sort_order ASC, id ASC
|
ORDER BY section_id ASC, sort_order ASC, id ASC
|
||||||
`);
|
`);
|
||||||
@@ -28,6 +28,7 @@ const selectItems = db.prepare(`
|
|||||||
sort_order,
|
sort_order,
|
||||||
default_level,
|
default_level,
|
||||||
available_levels,
|
available_levels,
|
||||||
|
is_global_scope,
|
||||||
is_required
|
is_required
|
||||||
FROM permission_items
|
FROM permission_items
|
||||||
ORDER BY section_id ASC, COALESCE(group_id, 0) ASC, sort_order ASC, id ASC
|
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(`
|
const selectPermissionItemsForMap = db.prepare(`
|
||||||
SELECT id, key, available_levels, default_level
|
SELECT id, key, available_levels, default_level, is_global_scope
|
||||||
FROM permission_items
|
FROM permission_items
|
||||||
`);
|
`);
|
||||||
|
|
||||||
@@ -81,6 +82,8 @@ const getLevelPriority = (level) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
let cachedPermissionItems = null;
|
let cachedPermissionItems = null;
|
||||||
|
let cachedServerScopedKeys = null;
|
||||||
|
let cachedScopeByKey = null;
|
||||||
|
|
||||||
const getPermissionItems = () => {
|
const getPermissionItems = () => {
|
||||||
if (!cachedPermissionItems) {
|
if (!cachedPermissionItems) {
|
||||||
@@ -91,6 +94,24 @@ const getPermissionItems = () => {
|
|||||||
|
|
||||||
const resetPermissionItemsCache = () => {
|
const resetPermissionItemsCache = () => {
|
||||||
cachedPermissionItems = null;
|
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() {
|
export function getPermissionStructure() {
|
||||||
@@ -109,10 +130,23 @@ export function getPermissionStructure() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const groupsBySectionId = new Map();
|
const groupsBySectionId = new Map();
|
||||||
groups.forEach((group) => {
|
const groupsByParentId = new Map();
|
||||||
const list = groupsBySectionId.get(group.section_id) || [];
|
const sortedGroups = [...groups].sort((a, b) => {
|
||||||
list.push(group);
|
const sortDiff = (a.sort_order ?? 0) - (b.sort_order ?? 0);
|
||||||
groupsBySectionId.set(group.section_id, list);
|
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();
|
const itemsBySectionId = new Map();
|
||||||
@@ -154,18 +188,26 @@ export function getPermissionStructure() {
|
|||||||
defaultLevel: item.default_level || 'none',
|
defaultLevel: item.default_level || 'none',
|
||||||
availableLevels,
|
availableLevels,
|
||||||
isRequired: Boolean(item.is_required),
|
isRequired: Boolean(item.is_required),
|
||||||
|
isGlobal: item.is_global_scope !== 0,
|
||||||
dependencies: formattedDependencies
|
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 formattedSections = sections.map((section) => {
|
||||||
const sectionItems = (itemsBySectionId.get(section.id) || []).map(formatItem);
|
const sectionItems = (itemsBySectionId.get(section.id) || []).map(formatItem);
|
||||||
const sectionGroups = (groupsBySectionId.get(section.id) || []).map((group) => ({
|
const rootGroups = groupsBySectionId.get(section.id) || [];
|
||||||
key: group.key,
|
const sectionGroups = rootGroups.map(buildGroupTree);
|
||||||
title: group.title,
|
|
||||||
sortOrder: group.sort_order ?? 0,
|
|
||||||
items: (itemsByGroupId.get(group.id) || []).map(formatItem)
|
|
||||||
}));
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
key: section.key,
|
key: section.key,
|
||||||
@@ -270,13 +312,24 @@ export function saveGroupPermissionValues(groupId, values = {}) {
|
|||||||
const serverEditLevel = getSubmittedLevel('maintenance-server-edit');
|
const serverEditLevel = getSubmittedLevel('maintenance-server-edit');
|
||||||
const serverDeleteLevel = getSubmittedLevel('maintenance-server-delete');
|
const serverDeleteLevel = getSubmittedLevel('maintenance-server-delete');
|
||||||
|
|
||||||
if (getLevelPriority(serverDeleteLevel) >= getLevelPriority('full') &&
|
// Enforce dependency: "server löschen" darf maximal so hoch sein wie manage/edit.
|
||||||
(getLevelPriority(serverManageLevel) < getLevelPriority('full') ||
|
const deletePriority = getLevelPriority(serverDeleteLevel);
|
||||||
getLevelPriority(serverEditLevel) < getLevelPriority('full'))) {
|
const allowedDeletePriority = Math.min(
|
||||||
const error = new Error('PERMISSION_DEPENDENCY_LEVEL');
|
getLevelPriority(serverManageLevel),
|
||||||
error.code = 'PERMISSION_DEPENDENCY_LEVEL';
|
getLevelPriority(serverEditLevel)
|
||||||
error.permissionKey = 'maintenance-server-delete';
|
);
|
||||||
throw error;
|
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(() => {
|
const runSave = db.transaction(() => {
|
||||||
@@ -324,6 +377,32 @@ export function getSuperuserPermissionMap() {
|
|||||||
return result;
|
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) {
|
export function getEffectivePermissionsForGroup(groupId) {
|
||||||
const defaults = getDefaultPermissionMap();
|
const defaults = getDefaultPermissionMap();
|
||||||
const numericId = Number(groupId);
|
const numericId = Number(groupId);
|
||||||
|
|||||||
@@ -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 +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
|
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -34,8 +34,8 @@
|
|||||||
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
|
<script defer data-site="YOUR_DOMAIN_HERE" src="https://api.nepcha.com/js/nepcha-analytics.js"></script>
|
||||||
|
|
||||||
|
|
||||||
<script type="module" crossorigin src="/assets/index-027ca62d.js"></script>
|
<script type="module" crossorigin src="/assets/index-c3010de9.js"></script>
|
||||||
<link rel="stylesheet" href="/assets/index-4d48f088.css">
|
<link rel="stylesheet" href="/assets/index-c7ee792b.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<div id="root"></div>
|
||||||
|
|||||||
+6
-61
@@ -14,17 +14,6 @@ const updateServer = db.prepare(`
|
|||||||
SET name = ?, url = ?, updated_at = CURRENT_TIMESTAMP
|
SET name = ?, url = ?, updated_at = CURRENT_TIMESTAMP
|
||||||
WHERE id = ?
|
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 deleteServerStmt = db.prepare('DELETE FROM servers WHERE id = ?');
|
||||||
|
|
||||||
const selectApiKeyByServerId = db.prepare('SELECT * FROM server_api_keys WHERE server_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 countServersStmt = db.prepare('SELECT COUNT(*) as count FROM servers');
|
||||||
const selectFirstServerStmt = db.prepare('SELECT * FROM servers ORDER BY id ASC LIMIT 1');
|
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')
|
const API_KEY_SECRET = crypto.createHash('sha256')
|
||||||
.update(process.env.PORTAINER_API_SECRET || process.env.PORTAINER_API_KEY || 'stackpulse-portainer-api-key')
|
.update(process.env.PORTAINER_API_SECRET || process.env.PORTAINER_API_KEY || 'stackpulse-portainer-api-key')
|
||||||
.digest();
|
.digest();
|
||||||
@@ -136,49 +129,6 @@ function ensureServer({ name, url }) {
|
|||||||
return created;
|
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) {
|
function getServerById(serverId) {
|
||||||
const id = Number(serverId);
|
const id = Number(serverId);
|
||||||
if (!Number.isFinite(id)) {
|
if (!Number.isFinite(id)) {
|
||||||
@@ -371,15 +321,12 @@ function ensureDefaultsFromEnv() {
|
|||||||
function getServerStatus(serverId) {
|
function getServerStatus(serverId) {
|
||||||
const server = getServerById(serverId);
|
const server = getServerById(serverId);
|
||||||
const apiKeyMeta = selectApiKeyByServerId.get(server.id) || null;
|
const apiKeyMeta = selectApiKeyByServerId.get(server.id) || null;
|
||||||
const agent = getAgentConfig(server.id, { autoCreate: true });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
server,
|
server,
|
||||||
apiKey: {
|
apiKey: {
|
||||||
hasKey: Boolean(apiKeyMeta),
|
hasKey: Boolean(apiKeyMeta),
|
||||||
updatedAt: apiKeyMeta?.updated_at ?? null
|
updatedAt: apiKeyMeta?.updated_at ?? null
|
||||||
},
|
}
|
||||||
agent
|
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -484,10 +431,8 @@ export {
|
|||||||
ensureDefaultsFromEnv,
|
ensureDefaultsFromEnv,
|
||||||
ensureServer,
|
ensureServer,
|
||||||
getServerById,
|
getServerById,
|
||||||
|
listServers,
|
||||||
setServerApiKey,
|
setServerApiKey,
|
||||||
getAgentConfig,
|
|
||||||
setAgentConfig,
|
|
||||||
generateAgentToken,
|
|
||||||
getServerConnection,
|
getServerConnection,
|
||||||
updateServerDetails,
|
updateServerDetails,
|
||||||
getServerStatus,
|
getServerStatus,
|
||||||
|
|||||||
@@ -75,6 +75,48 @@ const insertMembershipForUser = db.prepare(`
|
|||||||
VALUES (?, ?)
|
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(`
|
const selectSecurityPhraseByUsername = db.prepare(`
|
||||||
SELECT
|
SELECT
|
||||||
id,
|
id,
|
||||||
@@ -883,6 +925,176 @@ export function updateUserDetails(userId, { username, email, password, avatarCol
|
|||||||
return updatedUser;
|
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(`
|
const deleteMembershipsStatement = db.prepare(`
|
||||||
DELETE FROM user_group_memberships
|
DELETE FROM user_group_memberships
|
||||||
WHERE user_id = ?
|
WHERE user_id = ?
|
||||||
|
|||||||
@@ -8,13 +8,18 @@ StackPulse folgt einer klassischen Client/Server-Architektur, bei der das Backen
|
|||||||
│ (React) │ ◀───── Auth-Cookie + JSON ─ │ (Express) │ ◀─────────────── │ Business Ed. │
|
│ (React) │ ◀───── Auth-Cookie + JSON ─ │ (Express) │ ◀─────────────── │ Business Ed. │
|
||||||
└────────────┘ Redeploy Events └───────────────┘ (Axios Proxy) └────────────────┘
|
└────────────┘ Redeploy Events └───────────────┘ (Axios Proxy) └────────────────┘
|
||||||
│
|
│
|
||||||
|
mTLS (optional)│
|
||||||
|
▼
|
||||||
|
StackPulse Agent
|
||||||
|
│
|
||||||
▼
|
▼
|
||||||
SQLite Datenbank
|
SQLite Datenbank
|
||||||
```
|
```
|
||||||
|
|
||||||
## Hauptschichten
|
## Hauptschichten
|
||||||
- **Frontend** (Vite + Material Tailwind): stellt alle Views, Formulare, Tabellen und Echtzeit-Widgets bereit. Die `AuthProvider`-Komponente verwaltet Session, Permissions und Socket-Verbindungen.
|
- **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 und stößt Redeploy- sowie Wartungsprozesse an.
|
- **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.
|
- **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
|
## 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.
|
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`.
|
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.
|
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
|
## Verzeichnis-Referenz
|
||||||
| Pfad | Inhalt |
|
| Pfad | Inhalt |
|
||||||
|
|||||||
+10
-9
@@ -11,10 +11,10 @@ Das Backend implementiert alle Anwendungsfälle über Express-Routen, Socket.IO
|
|||||||
| Modul | Pfad | Aufgabe |
|
| Modul | Pfad | Aufgabe |
|
||||||
|-------|------|---------|
|
|-------|------|---------|
|
||||||
| Auth/Superuser | `auth/superuser.js` | Registrieren/Verifizieren von Superusern, Login, Passwortvalidierung, Sicherheitsphrasen. |
|
| 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. |
|
| 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). |
|
| 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. |
|
| Portainer Proxy | Funktionen in `index.js` | Kommunikation mit Portainer (`axiosInstance` + dynamische `baseURL`), Environment-Erkennung und Redeploy-Strecke. |
|
||||||
|
|
||||||
## API-Überblick (Auszug)
|
## 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. |
|
| 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/:id/redeploy` | Redeploy eines einzelnen Stacks. |
|
||||||
| PUT | `/api/stacks/redeploy-selection` | Redeploy einer Liste übermittelter `stackIds`. |
|
| 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`.
|
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
|
## Datenbank & Schema
|
||||||
- `ensureDatabaseSchema()` erzeugt Tabellen bei jedem Start, inklusive Default-Gruppen, Permissions, Settings, Server-Einträge und Trigger.
|
- `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:
|
- Events werden in `event_logs` gespeichert. Relevante Felder:
|
||||||
- `category`, `event_type`, `action`, `status`, `severity`
|
- `category`, `event_type`, `action`, `status`, `severity`
|
||||||
- `entity_type`, `entity_id`, `entity_name`
|
- `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.
|
- Filter-Parameter (`ids`, `categories`, `eventTypes`, `status`, `stackIds`, Zeitfenster) werden in `buildEventLogFilter` zentral verarbeitet.
|
||||||
|
|
||||||
## Berechtigungssystem
|
## 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.
|
- Superuser erhalten alle Berechtigungen und können nicht über das UI entzogen werden.
|
||||||
- `PermissionGate` im Frontend spiegelt die gleichen Keys. Wichtigste Bereiche:
|
- `PermissionGate` im Frontend spiegelt die gleichen Keys. Wichtigste Bereiche:
|
||||||
- `stacks-*` (Redeploy)
|
- `Redeploy einzeln/Auswahl/Alle`
|
||||||
- `logs-*`
|
- `Bereich & Navigation (Logs)`, `Logs Exportieren`, `Logs löschen`
|
||||||
- `users-*` & `user-groups-*`
|
- `Bereich & Navigation (Benutzer)`, `Benutzer bearbeiten/löschen`, `Sicherheitsschlüssel`
|
||||||
- `maintenance-*` (inkl. Unterrechte `maintenance-server-*`, `maintenance-ssh-update`, `maintenance-update`)
|
- `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
|
## Redeploy-Workflow
|
||||||
1. Eingehender Request wird mit `maintenanceGuard` geprüft (Maintenance-Modus blockiert optional Redeploys).
|
1. Eingehender Request wird mit `maintenanceGuard` geprüft (Maintenance-Modus blockiert optional Redeploys).
|
||||||
|
|||||||
@@ -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
@@ -1,80 +1,61 @@
|
|||||||
# Getting Started
|
# 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
|
## Voraussetzungen
|
||||||
- Node.js **>= 20** und npm für lokale Entwicklung
|
- Docker & Docker Compose
|
||||||
- Docker & Docker Compose (optional aber empfohlen für Deployment)
|
- Portainer-Instanz (Business Edition empfohlen; bei Community/Edge zusätzlich Agent einsetzen)
|
||||||
- Zugriff auf eine **Portainer Business Edition** samt API-Key
|
|
||||||
- Shell-Zugang zum Host, auf dem StackPulse laufen soll
|
|
||||||
|
|
||||||
## Repository & Abhängigkeiten
|
## Zentrale Umgebungsvariablen (Kurzfassung)
|
||||||
```bash
|
| Variable | Erforderlich | Beschreibung |
|
||||||
# Repository klonen
|
|----------|--------------|--------------|
|
||||||
# git clone <repo-url>
|
| `PORTAINER_URL` | Nein | Basis-URL zu Portainer; kann im Setup-UI hinterlegt werden, falls nicht gesetzt. |
|
||||||
cd stackpulse
|
| `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
|
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.
|
||||||
chmod +x scripts/start-dev.sh
|
|
||||||
```
|
|
||||||
|
|
||||||
## Lokale Dev-Umgebung
|
**Hinweis zu Secrets:**
|
||||||
Das Skript `scripts/start-dev.sh` richtet Backend und Frontend automatisch ein:
|
- `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.
|
||||||
1. Backend: `npm install`, `npm run migrate` (erstellt/aktualisiert `backend/data/stackpulse.db`), `npm start`
|
- Eine vollständige Übersicht aller Variablen (inkl. Agent) findest du unter „Technik → Umgebungsvariablen“.
|
||||||
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.
|
|
||||||
|
|
||||||
## Ersteinrichtung im UI
|
## Ersteinrichtung im UI
|
||||||
1. Öffne das Frontend (`/setup` führt dich automatisch durch den Wizard).
|
1. Öffne das Frontend.
|
||||||
2. Lege den Portainer-Server und den zugehörigen API-Key an – StackPulse testet die Verbindung per `/api/setup/test-portainer`.
|
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.
|
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.
|
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
|
```yaml
|
||||||
# docker-compose.yml (Auszug)
|
# docker-compose.yml (Auszug)
|
||||||
services:
|
services:
|
||||||
app:
|
app:
|
||||||
build:
|
image: ghcr.io/mboehmlaender/stackpulse:latest
|
||||||
context: .
|
|
||||||
dockerfile: Dockerfile
|
|
||||||
ports:
|
ports:
|
||||||
- "4001:4001"
|
- "4001:4001"
|
||||||
volumes:
|
volumes:
|
||||||
@@ -91,13 +72,8 @@ volumes:
|
|||||||
stackpulse_data:
|
stackpulse_data:
|
||||||
```
|
```
|
||||||
|
|
||||||
1. `docker compose up -d --build`
|
1. `docker compose up -d`
|
||||||
2. Das Frontend ist anschließend unter Port 4001 (via Backend `public/`) erreichbar.
|
2. Das Frontend ist anschließend unter Port 4001 erreichbar.
|
||||||
3. Datenbank & Einstellungen liegen im benannten Volume `stackpulse_data`.
|
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.
|
Damit bist du bereit für die tieferen Kapitel zu Architektur sowie Backend- und Frontend-Details.
|
||||||
@@ -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
|
- **Auditierbare Historie** durch strukturierte Event-Logs mit Export-, Lösch- und Filterfunktionen
|
||||||
- **Feingranulare Rechteverwaltung** mit Gruppenrechten, Superuser-Handling und Sicherheitsphrasen
|
- **Feingranulare Rechteverwaltung** mit Gruppenrechten, Superuser-Handling und Sicherheitsphrasen
|
||||||
- **Wartungsautomatisierung** (SSH Update-Skripte, Maintenance-Mode) ohne direkten Portainer-Zugang
|
- **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`
|
- **Bereitstellung als Docker-Image** sowie als lokale Dev-Umgebung via `scripts/start-dev.sh`
|
||||||
|
|
||||||
## Technische Eckdaten
|
## Technische Eckdaten
|
||||||
|
|||||||
@@ -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
@@ -2,6 +2,10 @@
|
|||||||
|
|
||||||
Dieser Abschnitt deckt typische Aufgaben für Betriebsteams ab – von Backups über Wartungsmodus bis zu Störungssuche.
|
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
|
## 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.
|
- **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.
|
- **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.
|
3. **Restore**: Ersetze die Datei im Volume und starte die App neu. Beim ersten Start führt `ensureDatabaseSchema` automatisch eventuelle Nachmigrationen aus.
|
||||||
|
|
||||||
## Benutzer- & Rechteverwaltung
|
## 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.
|
- 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.
|
- 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
|
## 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`).
|
- 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-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.
|
- Update anstoßen: `POST /api/maintenance/portainer-update` triggert die SSH-Kommandos. Status & Logausgaben können über `/api/maintenance/update-status` überwacht werden.
|
||||||
|
|||||||
@@ -1,6 +1,11 @@
|
|||||||
# Benutzergruppen & Rechte
|
# 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
|
## Gruppen anlegen
|
||||||
1. Button **Gruppe erstellen** wählen.
|
1. Button **Gruppe erstellen** wählen.
|
||||||
@@ -23,7 +28,7 @@ Der Rechte-Dialog zeigt die gesamte Permissionstruktur in Kategorien (Stacks, Lo
|
|||||||
- **none** – kein Zugriff
|
- **none** – kein Zugriff
|
||||||
- **read** – Zugriff auf Ansicht/Lesen
|
- **read** – Zugriff auf Ansicht/Lesen
|
||||||
- **full** – volle Bearbeitungsrechte
|
- **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.
|
4. Speichern bestätigt die Anpassungen. StackPulse aktualisiert sofort die effektiven Rechte aller Benutzer in dieser Gruppe.
|
||||||
|
|
||||||
## Benutzer zu Gruppen hinzufügen
|
## 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.
|
- Löschen entfernt keine Benutzer, aber deren effektive Rechte können sich dadurch verringern.
|
||||||
|
|
||||||
## Best Practices
|
## 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.
|
- 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.
|
- Nutze sprechende Beschreibungen, damit Kolleg:innen sofort erkennen, wofür eine Gruppe gedacht ist.
|
||||||
@@ -1,43 +1,62 @@
|
|||||||
# Wartungsbereich
|
# 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
|
**Relevante Rechte**
|
||||||
- Klick auf **Status prüfen**, um eine Live-Abfrage gegen Portainer durchzuführen. Ergebnis zeigt API-Verfügbarkeit, Endpoint-IDs und Auth-Status.
|
- `Bereich & Navigation (Wartung)`
|
||||||
- Häufige Fehler werden direkt erklärt (z. B. fehlender API-Key oder Netzwerkprobleme).
|
- `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
|
## Maintenance-Mode
|
||||||
1. Schalter **Wartungsmodus aktivieren** toggeln.
|
1. Schalter **Wartungsmodus aktivieren** umlegen.
|
||||||
2. Optionale Nachricht eingeben (z. B. „Upgrade um 22:00 Uhr“); sie erscheint im gesamten UI als Banner.
|
2. Jede Aktivierung/Deaktivierung wird in den Logs protokolliert.
|
||||||
3. Aktivieren/Deaktivieren protokolliert automatisch Events in den Logs.
|
|
||||||
|
|
||||||
## Server & API-Key verwalten
|
## Serververwaltung & API-Key
|
||||||
- Register „Server“ listet alle Portainer-Instanzen.
|
- Liste zeigt alle eingetragenen Portainer-Server.
|
||||||
- Über **Server hinzufügen** kannst du Name + URL ergänzen und anschließend API-Key speichern.
|
- **Server hinzufügen**/**bearbeiten**/**löschen** nur mit `Server bearbeiten` (Vollzugriff); löschen zusätzlich `Server löschen`.
|
||||||
- Buttons in der Tabelle:
|
- Im Server-Detail:
|
||||||
- **API-Key aktualisieren** – neues Token einsetzen.
|
- **Server-URL/Name** und **Portainer API-Key**: editierbar nur mit `Server bearbeiten` Vollzugriff; bei Leserecht sichtbar, aber gesperrt.
|
||||||
- **Server löschen** – nur möglich, wenn mindestens ein weiterer Server existiert oder du sofort neue Daten einträgst.
|
- **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
|
## Agent-Integration (Portainer CE / Edge)
|
||||||
- Feld „Eigener Stack“ akzeptiert die Stack-ID, in der StackPulse selbst läuft.
|
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.
|
||||||
- Nach dem Speichern sind Redeploy-Aktionen für diesen Stack im Dashboard deaktiviert.
|
|
||||||
|
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
|
## SSH-Konfiguration
|
||||||
Erforderliche Permission: `maintenance-ssh-update`.
|
Erfordert `SSH/Update-Skript` (sichtbar/bearbeitbar nur bei Vollzugriff auf Server-Edit).
|
||||||
1. Host, Port, Benutzername eintragen. Optional Passwort und zusätzliche SSH-Argumente (z. B. `-o StrictHostKeyChecking=no`).
|
1. Host, Port, Benutzername und Passwort sowie eventuell notwendige Extra-Argumente eintragen.
|
||||||
2. Passwort wird verschlüsselt gespeichert; alternativ kannst du Public-Key-Auth nutzen und das Feld leer lassen.
|
2. Passwort wird verschlüsselt gespeichert.
|
||||||
3. **Verbindung testen**: StackPulse baut eine SSH-Verbindung auf und zeigt das Ergebnis in Echtzeit.
|
3. **Verbindung testen** prüft Erreichbarkeit und meldet Ergebnis direkt.
|
||||||
|
|
||||||
## Update-Skript
|
## Update-Skript & Portainer-Update
|
||||||
- Standard-Skript wird angezeigt und kann als Ausgangspunkt genutzt werden.
|
- Standard-Skript ist sichtbar und kann überschrieben werden (Recht `SSH/Update-Skript` + Server-Edit Vollzugriff). **Zurücksetzen** stellt den Standard wieder her.
|
||||||
- Eigenes Skript: Inhalt in das Editorfeld kopieren und speichern. StackPulse prüft auf leere Zeilen und Basisbefehle.
|
- Start eines Updates (`Update durchführen` + Server-Edit Vollzugriff):
|
||||||
- Skript zurücksetzen entfernt die benutzerdefinierte Variante und stellt den Default wieder her.
|
1) Wartungsmodus wird automatisch aktiviert.
|
||||||
|
2) SSH-Konfiguration und Skript prüfen.
|
||||||
## Portainer-Update auslösen
|
3) **Update starten** klicken. Der Fortschritt und die Logausgaben erscheinen im Fenster.
|
||||||
1. Stelle sicher, dass Wartungsmodus aktiv ist und SSH-Konfiguration + Skript korrekt sind.
|
4) Während ein Update läuft, sind Änderungen am System gesperrt.
|
||||||
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.
|
|
||||||
|
|
||||||
## Fehlerbehandlung
|
## Fehlerbehandlung
|
||||||
- Bei SSH-Fehlern zeigt StackPulse die genaue Ausgabe an. Passe Host/Port/Benutzer oder Skript an.
|
- SSH-/Update-Fehler werden mit Klartextausgabe angezeigt. Bei Bedarf Host/Port/User, Passwort oder Skript anpassen.
|
||||||
- Falls das Update-Skript Probleme verursacht, kannst du es jederzeit stoppen, indem du Portainer manuell prüfst und ggf. das Skript anpasst.
|
- Agent offline: URL/Token prüfen, Bootstrap-Token ggf. neu erzeugen und den Agent neu starten.
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
# Setup-Assistent
|
# 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
|
## Voraussetzungen
|
||||||
- Portainer Business Edition mit gültigem API-Key
|
- 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
|
## Setup wiederholen oder ändern
|
||||||
- Über das Dashboard → **Wartung** → Abschnitt *Serververwaltung* kannst du zusätzliche Portainer-Server hinzufügen oder entfernen.
|
- Ü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.
|
- Der Self-Stack (StackPulse-eigener Stack) kann in der Wartung gepflegt werden, damit er nicht versehentlich redeployed wird.
|
||||||
@@ -2,6 +2,11 @@
|
|||||||
|
|
||||||
Die Stacks-Ansicht ist der Startpunkt nach dem Login und liefert einen Überblick über alle Stacks der angebundenen Portainer-Instanz.
|
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
|
## Aufbau
|
||||||
- **Header** mit Suchfeld, Filterchips für Status sowie Buttons für Redeploy-Aktionen.
|
- **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.
|
- **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
|
## Redeploy-Aktionen
|
||||||
| Aktion | Voraussetzung | Vorgehen |
|
| Aktion | Voraussetzung | Vorgehen |
|
||||||
|--------|---------------|----------|
|
|--------|---------------|----------|
|
||||||
| **Einzelner Stack** | Permission `stacks-redeploy-single` | Button **Redeploy** in der jeweiligen Zeile klicken und bestätigen. |
|
| **Einzelner Stack** | Recht `Redeploy einzeln` | 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**. |
|
| **Mehrere Stacks** | Recht `Redeploy Auswahl` | 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. |
|
| **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.
|
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.
|
||||||
|
|
||||||
|
|||||||
@@ -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 }) {
|
export function AuthProvider({ children }) {
|
||||||
const [state, setState] = useState({
|
const [state, setState] = useState({
|
||||||
user: null,
|
user: null,
|
||||||
permissions: {},
|
permissions: {},
|
||||||
|
serverPermissions: {},
|
||||||
|
serverAssignments: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
lastErrorCode: null,
|
lastErrorCode: null,
|
||||||
@@ -36,9 +78,13 @@ export function AuthProvider({ children }) {
|
|||||||
const setSession = useCallback((payload) => {
|
const setSession = useCallback((payload) => {
|
||||||
const user = payload?.user ?? null;
|
const user = payload?.user ?? null;
|
||||||
const permissions = normalizePermissions(payload?.permissions);
|
const permissions = normalizePermissions(payload?.permissions);
|
||||||
|
const serverPermissions = normalizeServerPermissions(payload?.serverPermissions);
|
||||||
|
const serverAssignments = normalizeServerAssignments(payload?.serverAssignments ?? user?.serverAssignments);
|
||||||
setState({
|
setState({
|
||||||
user,
|
user,
|
||||||
permissions,
|
permissions,
|
||||||
|
serverPermissions,
|
||||||
|
serverAssignments,
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
lastErrorCode: null,
|
lastErrorCode: null,
|
||||||
@@ -50,6 +96,8 @@ export function AuthProvider({ children }) {
|
|||||||
setState((prev) => ({
|
setState((prev) => ({
|
||||||
user: null,
|
user: null,
|
||||||
permissions: {},
|
permissions: {},
|
||||||
|
serverPermissions: {},
|
||||||
|
serverAssignments: [],
|
||||||
loading: false,
|
loading: false,
|
||||||
error: null,
|
error: null,
|
||||||
lastErrorCode: null,
|
lastErrorCode: null,
|
||||||
@@ -80,6 +128,8 @@ export function AuthProvider({ children }) {
|
|||||||
error: payload?.error ?? null,
|
error: payload?.error ?? null,
|
||||||
lastErrorCode: payload?.error ?? null,
|
lastErrorCode: payload?.error ?? null,
|
||||||
initialized: true,
|
initialized: true,
|
||||||
|
serverPermissions: {},
|
||||||
|
serverAssignments: []
|
||||||
});
|
});
|
||||||
return { ok: false, status: response.status, error: payload?.error ?? null };
|
return { ok: false, status: response.status, error: payload?.error ?? null };
|
||||||
}
|
}
|
||||||
@@ -89,9 +139,11 @@ export function AuthProvider({ children }) {
|
|||||||
} catch (err) {
|
} catch (err) {
|
||||||
const message = err?.message || "NETWORK_ERROR";
|
const message = err?.message || "NETWORK_ERROR";
|
||||||
setState({
|
setState({
|
||||||
user: null,
|
user: null,
|
||||||
permissions: {},
|
permissions: {},
|
||||||
loading: false,
|
serverPermissions: {},
|
||||||
|
serverAssignments: [],
|
||||||
|
loading: false,
|
||||||
error: message,
|
error: message,
|
||||||
lastErrorCode: "NETWORK_ERROR",
|
lastErrorCode: "NETWORK_ERROR",
|
||||||
initialized: true,
|
initialized: true,
|
||||||
@@ -140,10 +192,36 @@ export function AuthProvider({ children }) {
|
|||||||
[state.permissions, state.user]
|
[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(
|
const value = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
user: state.user,
|
user: state.user,
|
||||||
permissions: state.permissions,
|
permissions: state.permissions,
|
||||||
|
serverPermissions: state.serverPermissions,
|
||||||
loading: state.loading,
|
loading: state.loading,
|
||||||
error: state.error,
|
error: state.error,
|
||||||
lastErrorCode: state.lastErrorCode,
|
lastErrorCode: state.lastErrorCode,
|
||||||
@@ -154,8 +232,10 @@ export function AuthProvider({ children }) {
|
|||||||
clearSession,
|
clearSession,
|
||||||
logout,
|
logout,
|
||||||
hasPermission,
|
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>;
|
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
||||||
|
|||||||
File diff suppressed because it is too large.
Load diff
File diff suppressed because it is too large.
Load diff
@@ -14,7 +14,6 @@ import {
|
|||||||
} from "@material-tailwind/react";
|
} from "@material-tailwind/react";
|
||||||
|
|
||||||
import { useToast } from "@/components/ToastProvider.jsx";
|
import { useToast } from "@/components/ToastProvider.jsx";
|
||||||
import { platformSettingsData } from "@/data";
|
|
||||||
import { AVATAR_COLORS } from "@/data/avatarColors.js";
|
import { AVATAR_COLORS } from "@/data/avatarColors.js";
|
||||||
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
import { useMaintenance } from "@/components/MaintenanceProvider.jsx";
|
||||||
import { useAuth } from "@/components/AuthProvider.jsx";
|
import { useAuth } from "@/components/AuthProvider.jsx";
|
||||||
@@ -69,6 +68,47 @@ const mapUser = (item) => ({
|
|||||||
securityPhraseDownloadedAt: item?.securityPhraseDownloadedAt || null
|
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) => {
|
const extractPrimaryGroupId = (user) => {
|
||||||
if (!user || !Array.isArray(user.groups) || user.groups.length === 0) {
|
if (!user || !Array.isArray(user.groups) || user.groups.length === 0) {
|
||||||
return null;
|
return null;
|
||||||
@@ -123,6 +163,11 @@ export function UserDetails() {
|
|||||||
const [securityPhraseLoading, setSecurityPhraseLoading] = useState(false);
|
const [securityPhraseLoading, setSecurityPhraseLoading] = useState(false);
|
||||||
const [securityPhraseError, setSecurityPhraseError] = useState("");
|
const [securityPhraseError, setSecurityPhraseError] = useState("");
|
||||||
const [renewingSecurityPhrase, setRenewingSecurityPhrase] = useState(false);
|
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 maintenanceActive = Boolean(maintenanceMeta?.active);
|
||||||
const maintenanceMessage = maintenanceMeta?.message;
|
const maintenanceMessage = maintenanceMeta?.message;
|
||||||
@@ -172,11 +217,22 @@ export function UserDetails() {
|
|||||||
if (!item.id) {
|
if (!item.id) {
|
||||||
throw new Error("USER_NOT_FOUND");
|
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);
|
setUser(item);
|
||||||
setSecurityPhraseWords([]);
|
setSecurityPhraseWords([]);
|
||||||
setSecurityPhraseDownloadedAt(item.securityPhraseDownloadedAt || null);
|
setSecurityPhraseDownloadedAt(item.securityPhraseDownloadedAt || null);
|
||||||
setSecurityPhraseError("");
|
setSecurityPhraseError("");
|
||||||
const initialValues = buildInitialFormValues(item);
|
const initialValues = buildInitialFormValues(item);
|
||||||
initialFormValuesRef.current = { ...initialValues };
|
initialFormValuesRef.current = { ...initialValues };
|
||||||
setFormValues(initialValues);
|
setFormValues(initialValues);
|
||||||
setSaveError("");
|
setSaveError("");
|
||||||
@@ -195,6 +251,9 @@ export function UserDetails() {
|
|||||||
setUser(null);
|
setUser(null);
|
||||||
initialFormValuesRef.current = buildInitialFormValues(null);
|
initialFormValuesRef.current = buildInitialFormValues(null);
|
||||||
setFormValues(buildInitialFormValues(null));
|
setFormValues(buildInitialFormValues(null));
|
||||||
|
setServerAssignments([]);
|
||||||
|
setAvailableServers([]);
|
||||||
|
setAssignmentsError("");
|
||||||
setError(message);
|
setError(message);
|
||||||
showToast({
|
showToast({
|
||||||
variant: "error",
|
variant: "error",
|
||||||
@@ -341,6 +400,208 @@ export function UserDetails() {
|
|||||||
}));
|
}));
|
||||||
}, [canEditUsers]);
|
}, [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 () => {
|
const handleSaveUser = useCallback(async () => {
|
||||||
if (!canEditUsers || !user || !hasChanges) {
|
if (!canEditUsers || !user || !hasChanges) {
|
||||||
return;
|
return;
|
||||||
@@ -775,30 +1036,171 @@ export function UserDetails() {
|
|||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<Typography variant="h6" color="blue-gray" className="mb-3">
|
<Typography variant="h6" color="blue-gray" className="mb-3">
|
||||||
Platform Settings
|
Server-Zuordnungen
|
||||||
</Typography>
|
</Typography>
|
||||||
<div className="flex flex-col gap-12">
|
{isSuperuserUser ? (
|
||||||
{platformSettingsData.map(({ title, options }) => (
|
<div className="mb-4 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-sm text-amber-800">
|
||||||
<div key={title}>
|
Für den Superuser gelten immer alle Rechte und es können keine server-spezifischen Zuordnungen gesetzt werden.
|
||||||
<Typography className="mb-4 block text-xs font-semibold uppercase text-blue-gray-500">
|
</div>
|
||||||
{title}
|
) : (
|
||||||
</Typography>
|
<>
|
||||||
<div className="flex flex-col gap-6">
|
<Typography className="mb-4 text-sm text-blue-gray-500">
|
||||||
{options.map(({ checked, label }) => (
|
Ohne Zuordnungen hat der Benutzer Zugriff auf alle Server mit seinen globalen Rechten.
|
||||||
<Switch
|
Lege hier Server-spezifische Gruppen fest oder markiere den Server explizit zur Nutzung der globalen Gruppe.
|
||||||
key={label}
|
</Typography>
|
||||||
id={label}
|
{assignmentsError && (
|
||||||
label={label}
|
<div className="mb-4 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-800">
|
||||||
defaultChecked={checked}
|
{assignmentsError}
|
||||||
labelProps={{
|
|
||||||
className: "text-sm font-normal text-blue-gray-500",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</div>
|
</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
|
||||||
|
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-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>
|
||||||
))}
|
<div className="mt-4 flex flex-wrap items-center gap-3">
|
||||||
</div>
|
<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>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -78,8 +78,14 @@ const LEVEL_PRIORITY = {
|
|||||||
full: 2
|
full: 2
|
||||||
};
|
};
|
||||||
|
|
||||||
const normalizePermissionLevel = (key, value) =>
|
const isDependencySatisfied = (value, requirement) => {
|
||||||
key === "maintenance-server-delete" && value !== "full" ? "none" : value;
|
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() {
|
export function UserGroupDetail() {
|
||||||
const { groupId } = useParams();
|
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 normalizedSections = rawSections.map((section, sectionIndex) => {
|
||||||
const sectionItems = Array.isArray(section.items)
|
const sectionItems = Array.isArray(section.items)
|
||||||
? section.items.map((item, itemIdx) => normalizeItem(item, itemIdx)).filter(Boolean)
|
? section.items.map((item, itemIdx) => normalizeItem(item, itemIdx)).filter(Boolean)
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
const sectionGroups = Array.isArray(section.groups)
|
const sectionGroups = Array.isArray(section.groups)
|
||||||
? section.groups.map((group, groupIdx) => {
|
? section.groups.map((group, groupIdx) => normalizeGroup(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
|
|
||||||
};
|
|
||||||
})
|
|
||||||
: [];
|
: [];
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@@ -515,16 +527,52 @@ export function UserGroupDetail() {
|
|||||||
if (!permissionKey || !canAdjustPermissions || isSuperuserGroup) {
|
if (!permissionKey || !canAdjustPermissions || isSuperuserGroup) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const nextValue = normalizePermissionLevel(permissionKey, value);
|
const getValueWithDefaults = (key, selectionMap) =>
|
||||||
setPermissionSelection((prev) => {
|
selectionMap[key] ?? permissionDefaults[key] ?? "none";
|
||||||
const shouldResetDelete =
|
|
||||||
permissionKey === "maintenance-server-manage" && nextValue !== "full";
|
|
||||||
const shouldResetEdit =
|
|
||||||
permissionKey === "maintenance-server-manage" && nextValue === "none";
|
|
||||||
const shouldResetDeleteFromEdit =
|
|
||||||
permissionKey === "maintenance-server-edit" && nextValue !== "full";
|
|
||||||
|
|
||||||
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;
|
return prev;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -533,19 +581,31 @@ export function UserGroupDetail() {
|
|||||||
[permissionKey]: nextValue
|
[permissionKey]: nextValue
|
||||||
};
|
};
|
||||||
|
|
||||||
if (shouldResetDelete) {
|
// Kaskade: Nur bei Downgrade des auslösenden Rechts abhängige Rechte herabstufen.
|
||||||
nextState["maintenance-server-delete"] = "none";
|
if (isDowngrade) {
|
||||||
}
|
let changed = true;
|
||||||
if (shouldResetEdit) {
|
while (changed) {
|
||||||
nextState["maintenance-server-edit"] = "none";
|
changed = false;
|
||||||
}
|
allItems.forEach((item) => {
|
||||||
if (shouldResetDeleteFromEdit) {
|
const dependencies = Array.isArray(item.dependencies) ? item.dependencies : [];
|
||||||
nextState["maintenance-server-delete"] = "none";
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return nextState;
|
return nextState;
|
||||||
});
|
});
|
||||||
}, [canAdjustPermissions, isSuperuserGroup]);
|
}, [canAdjustPermissions, isSuperuserGroup, permissionDefaults, permissionSections]);
|
||||||
|
|
||||||
const getPermissionValue = useCallback(
|
const getPermissionValue = useCallback(
|
||||||
(permissionKey) => {
|
(permissionKey) => {
|
||||||
@@ -559,7 +619,7 @@ export function UserGroupDetail() {
|
|||||||
value = permissionDefaults[permissionKey];
|
value = permissionDefaults[permissionKey];
|
||||||
}
|
}
|
||||||
if (typeof value === "string") {
|
if (typeof value === "string") {
|
||||||
return normalizePermissionLevel(permissionKey, value);
|
return 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(
|
const isPermissionRowVisible = useCallback(
|
||||||
(row) => {
|
(row) => {
|
||||||
@@ -617,9 +671,7 @@ export function UserGroupDetail() {
|
|||||||
|
|
||||||
const rowName = `permission-${ownerKey}-${row.key}`;
|
const rowName = `permission-${ownerKey}-${row.key}`;
|
||||||
const currentValue = getPermissionValue(row.key) ?? "none";
|
const currentValue = getPermissionValue(row.key) ?? "none";
|
||||||
const displayValue = normalizePermissionLevel(row.key, currentValue);
|
const displayValue = currentValue;
|
||||||
const serverManageLevel = getPermissionValue("maintenance-server-manage") ?? "none";
|
|
||||||
const serverEditLevel = getPermissionValue("maintenance-server-edit") ?? "none";
|
|
||||||
const { addTopBorder = true } = options;
|
const { addTopBorder = true } = options;
|
||||||
|
|
||||||
const rowClasses = [
|
const rowClasses = [
|
||||||
@@ -632,11 +684,7 @@ export function UserGroupDetail() {
|
|||||||
const baseLevels = Array.isArray(row.availableLevels) && row.availableLevels.length
|
const baseLevels = Array.isArray(row.availableLevels) && row.availableLevels.length
|
||||||
? row.availableLevels
|
? row.availableLevels
|
||||||
: LEVEL_OPTIONS.map((option) => option.value);
|
: LEVEL_OPTIONS.map((option) => option.value);
|
||||||
const availableLevels = new Set(
|
const availableLevels = new Set(baseLevels);
|
||||||
row.key === "maintenance-server-delete"
|
|
||||||
? baseLevels.filter((level) => level === "full" || level === "none")
|
|
||||||
: baseLevels
|
|
||||||
);
|
|
||||||
const levelOptions = LEVEL_OPTIONS;
|
const levelOptions = LEVEL_OPTIONS;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -648,22 +696,12 @@ export function UserGroupDetail() {
|
|||||||
{levelOptions.map((option) => {
|
{levelOptions.map((option) => {
|
||||||
const optionId = `${rowName}-${option.value}`;
|
const optionId = `${rowName}-${option.value}`;
|
||||||
const checked = displayValue === 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 =
|
const disabled =
|
||||||
!availableLevels.has(option.value) ||
|
!availableLevels.has(option.value) ||
|
||||||
permissionsLoading ||
|
permissionsLoading ||
|
||||||
savingGroup ||
|
savingGroup ||
|
||||||
!canAdjustPermissions ||
|
!canAdjustPermissions ||
|
||||||
isSuperuserGroup ||
|
isSuperuserGroup;
|
||||||
requiresManageFull ||
|
|
||||||
requiresEditFull;
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ListItem
|
<ListItem
|
||||||
@@ -689,7 +727,6 @@ export function UserGroupDetail() {
|
|||||||
className: "p-0"
|
className: "p-0"
|
||||||
}}
|
}}
|
||||||
icon={radioIcon}
|
icon={radioIcon}
|
||||||
checkedIcon={radioCheckedIcon}
|
|
||||||
/>
|
/>
|
||||||
</ListItemPrefix>
|
</ListItemPrefix>
|
||||||
<Typography
|
<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 (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className="mt-12 flex flex-col gap-12">
|
<div className="mt-12 flex flex-col gap-12">
|
||||||
@@ -725,9 +786,6 @@ export function UserGroupDetail() {
|
|||||||
</div>
|
</div>
|
||||||
</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">
|
<Card className="mx-3 -mt-16 mb-6 lg:mx-4 border border-blue-gray-100">
|
||||||
<CardBody className="p-4">
|
<CardBody className="p-4">
|
||||||
<div className="mb-10 flex flex-wrap items-center justify-between gap-6">
|
<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.
|
Die Superuser-Gruppe besitzt automatisch Vollzugriff auf alle Bereiche. Berechtigungen können hier nicht angepasst werden.
|
||||||
</div>
|
</div>
|
||||||
) : !canViewPermissionSettings ? null : (
|
) : !canViewPermissionSettings ? null : (
|
||||||
<div className="rounded-xl border border-blue-gray-100 bg-white shadow-sm">
|
<>
|
||||||
{permissionsLoading && (
|
{permissionsLoading && (
|
||||||
<div className="border-b border-blue-gray-100 px-4 py-4">
|
<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">
|
<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.
|
Für diese Gruppe sind keine Berechtigungen definiert.
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
permissionSections.map((section, sectionIndex) => {
|
(() => {
|
||||||
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
const renderedSections = permissionSections
|
||||||
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
|
.map((section, sectionIndex) => {
|
||||||
|
const sectionItems = Array.isArray(section.items) ? section.items : [];
|
||||||
|
const sectionGroups = Array.isArray(section.groups) ? section.groups : [];
|
||||||
|
|
||||||
return (
|
if (!sectionHasVisibleRows(section)) {
|
||||||
<div
|
return null;
|
||||||
key={section.key || sectionIndex}
|
}
|
||||||
className={sectionIndex === 0 ? "" : "border-t border-blue-gray-100"}
|
|
||||||
>
|
|
||||||
<div className="border-b border-blue-gray-100 px-4 py-4">
|
|
||||||
<Typography variant="h6" className="text-lg font-semibold text-blue-gray-700">
|
|
||||||
{section.title}
|
|
||||||
</Typography>
|
|
||||||
</div>
|
|
||||||
{sectionItems.map((row, rowIndex) =>
|
|
||||||
renderPermissionRow(row, section.key || sectionIndex, {
|
|
||||||
addTopBorder: rowIndex !== 0
|
|
||||||
})
|
|
||||||
)}
|
|
||||||
{sectionGroups.map((group, groupIdx) => {
|
|
||||||
const groupItems = Array.isArray(group.items) ? group.items : [];
|
|
||||||
const hasVisibleRows = groupItems.some(isPermissionRowVisible);
|
|
||||||
|
|
||||||
if (!hasVisibleRows) {
|
const renderGroup = (currentGroup, path = `${section.key || sectionIndex}-group`, depth = 0) => {
|
||||||
|
if (!groupHasVisibleRows(currentGroup)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const groupItems = Array.isArray(currentGroup.items) ? currentGroup.items : [];
|
||||||
|
const childGroups = Array.isArray(currentGroup.groups) ? currentGroup.groups : [];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={group.key || `${section.key || sectionIndex}-${groupIdx}`} className="border-t border-blue-gray-100">
|
<div
|
||||||
<div className="border-b border-blue-gray-100 px-4 py-3">
|
key={currentGroup.key || path}
|
||||||
<Typography className="text-sm font-semibold uppercase tracking-wide text-blue-gray-600">
|
className=""
|
||||||
{group.title}
|
>
|
||||||
|
<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>
|
</Typography>
|
||||||
</div>
|
</div>
|
||||||
{groupItems.map((row, rowIndex) =>
|
<div className="divide-y divide-blue-gray-100">
|
||||||
renderPermissionRow(row, group.key || `${section.key || sectionIndex}-${groupIdx}`, {
|
{groupItems.map((row) =>
|
||||||
addTopBorder: rowIndex !== 0
|
renderPermissionRow(row, `${path}-${row.key}`, {
|
||||||
})
|
addTopBorder: false
|
||||||
)}
|
})
|
||||||
|
)}
|
||||||
|
{childGroups.map((childGroup, childIdx) =>
|
||||||
|
renderGroup(childGroup, `${path}-${childGroup.key || childIdx}`, depth + 1)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
};
|
||||||
</div>
|
|
||||||
);
|
return (
|
||||||
})
|
<div
|
||||||
|
key={section.key || sectionIndex}
|
||||||
|
className="overflow-hidden rounded-xl border border-blue-gray-100 bg-white shadow-sm"
|
||||||
|
>
|
||||||
|
<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>
|
||||||
|
<div className="divide-y divide-blue-gray-100">
|
||||||
|
{sectionItems.map((row) =>
|
||||||
|
renderPermissionRow(row, section.key || sectionIndex, {
|
||||||
|
addTopBorder: false
|
||||||
|
})
|
||||||
|
)}
|
||||||
|
{sectionGroups.map((group, groupIdx) =>
|
||||||
|
renderGroup(group, `${section.key || sectionIndex}-${groupIdx}`, 0)
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.filter(Boolean);
|
||||||
|
|
||||||
|
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 className="flex flex-col gap-4">{renderedSections}</div>;
|
||||||
|
})()
|
||||||
))}
|
))}
|
||||||
</div>
|
</>
|
||||||
)}
|
)}
|
||||||
</>
|
</>
|
||||||
|
|
||||||
|
|||||||
+4
-1
@@ -12,9 +12,12 @@ nav:
|
|||||||
- Wartung: user-guide/maintenance.md
|
- Wartung: user-guide/maintenance.md
|
||||||
- Benutzer: user-guide/users.md
|
- Benutzer: user-guide/users.md
|
||||||
- Benutzergruppen & Rechte: user-guide/groups.md
|
- Benutzergruppen & Rechte: user-guide/groups.md
|
||||||
- Technik:
|
- Getting Started:
|
||||||
- Getting Started: getting-started.md
|
- Getting Started: getting-started.md
|
||||||
|
- Umgebungsvariablen: environment.md
|
||||||
|
- Technik:
|
||||||
- Architektur: architecture.md
|
- Architektur: architecture.md
|
||||||
- Backend & API: backend.md
|
- Backend & API: backend.md
|
||||||
- Frontend: frontend.md
|
- Frontend: frontend.md
|
||||||
|
- Lokale Entwicklung: local-dev.md
|
||||||
- Operations & Wartung: operations.md
|
- Operations & Wartung: operations.md
|
||||||
Reference in new issue
Block a user