Compare commits
87 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9cbdff5185 | |||
| 354093b900 | |||
| dff1265435 | |||
| 7c58826668 | |||
| 4edc829b3f | |||
| 8f10be39bd | |||
| 27212a91e1 | |||
| 7b72306133 | |||
| e16e6255d0 | |||
| 4beb0c4d0e | |||
| 175d3ec6a2 | |||
| e63cd03267 | |||
| 4d878c8737 | |||
| 5f817ec4f6 | |||
| 490807cef6 | |||
| 06d7dfb212 | |||
| ba508b8fc4 | |||
| b928fff4a5 | |||
| df6a5967ea | |||
| 415e32cdc9 | |||
| aa32e7a353 | |||
| 67bfe5ff5c | |||
| c08f3fbdbe | |||
| 02dc79b739 | |||
| d1097c2dbf | |||
| 5fa14ab353 | |||
| acd53c297b | |||
| fd66b119b3 | |||
| 21026cc992 | |||
| 95096562e4 | |||
| 62baf2bd5e | |||
| d2da09406a | |||
| a975192dfb | |||
| 2c80d0451e | |||
| d4a4e9a355 | |||
| a08c0fc368 | |||
| d0a6794576 | |||
| 00c270b0d4 | |||
| 7f115e0d6e | |||
| 9d02f504fd | |||
| 65c25067ec | |||
| 4c6324c4c2 | |||
| ca4832bcc3 | |||
| d6c8a8bbfc | |||
| 3d91572b59 | |||
| f0f3bd51a4 | |||
| 6e9c97593c | |||
| a5398e8409 | |||
| bb1df5f3f6 | |||
| 1bf5c2431c | |||
| 08a3009ba8 | |||
| b3f1f65b2f | |||
| 74a5bb0a01 | |||
| 9249f151a8 | |||
| dd782fbea0 | |||
| 0a52a8effa | |||
| 902f206e32 | |||
| 4344d33349 | |||
| 8ac890a1c6 | |||
| 6189f2b983 | |||
| 4296eb97fb | |||
| 87bf1a321f | |||
| 37af076456 | |||
| 0476fd1076 | |||
| 6a47358771 | |||
| f7d415e013 | |||
| 2a60029104 | |||
| 76c7a5bc1f | |||
| d64db24240 | |||
| 9bd07e1928 | |||
| 40b0a2d220 | |||
| fb3d262eb7 | |||
| 0f88a9b19f | |||
| d3860ff5a2 | |||
| 7165bdf79b | |||
| eb218dc7f4 | |||
| c02efe5430 | |||
| daf0ebbff5 | |||
| fc8a9eebac | |||
| 07bcfa084a | |||
| 1755cedd88 | |||
| 07a99a76ce | |||
| c3d3989cdc | |||
| 2012a34938 | |||
| 7603dfeb29 | |||
| 9ad0b24580 | |||
| acc9d8fac1 |
@@ -0,0 +1,20 @@
|
||||
name: Dual Approval (CTO + QA)
|
||||
|
||||
# Calls the shared dual-approval-check workflow.
|
||||
# Passes when both privilegedescalation-cto and privilegedescalation-qa
|
||||
# have approved the PR. Add "Dual Approval (CTO + QA)" to required_status_checks
|
||||
# in branch protection to enforce this gate.
|
||||
|
||||
on:
|
||||
pull_request_review:
|
||||
types: [submitted, dismissed]
|
||||
pull_request:
|
||||
branches: [main]
|
||||
types: [opened, reopened, synchronize]
|
||||
|
||||
jobs:
|
||||
dual-approval:
|
||||
uses: privilegedescalation/.github/.github/workflows/dual-approval-check.yaml@main
|
||||
secrets: inherit
|
||||
with:
|
||||
pr_number: ${{ github.event.pull_request.number }}
|
||||
+55
-35
@@ -7,53 +7,61 @@ on:
|
||||
branches: [main]
|
||||
workflow_dispatch:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
# Only one E2E run at a time: the shared E2E_RELEASE (headlamp-e2e) in
|
||||
# privilegedescalation-dev cannot be shared across concurrent runs.
|
||||
# cancel-in-progress: false (queue, don't cancel) — cancelling in-flight
|
||||
# runs may skip the if: always() teardown, leaving dangling cluster resources.
|
||||
concurrency:
|
||||
group: e2e-${{ github.repository }}
|
||||
cancel-in-progress: false
|
||||
|
||||
env:
|
||||
E2E_NAMESPACE: privilegedescalation-dev
|
||||
E2E_RELEASE: headlamp-e2e
|
||||
# Pin to a known-good Headlamp version. Using :latest is risky because
|
||||
# the tag can change between CI runs, causing flaky failures when a newer
|
||||
# image is pulled on some nodes but not others (IfNotPresent pull policy).
|
||||
# Update this when Headlamp is upgraded in production (kube-system).
|
||||
HEADLAMP_VERSION: v0.40.1
|
||||
|
||||
jobs:
|
||||
e2e:
|
||||
runs-on: local-ubuntu-latest
|
||||
runs-on: runners-privilegedescalation
|
||||
timeout-minutes: 15
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@v6
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@v6
|
||||
with:
|
||||
node-version: '22'
|
||||
cache: 'npm'
|
||||
|
||||
- name: Setup kubectl
|
||||
uses: azure/setup-kubectl@v4
|
||||
|
||||
- name: Install dependencies
|
||||
run: npm ci
|
||||
|
||||
- name: Preflight — verify Headlamp connectivity
|
||||
env:
|
||||
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
|
||||
run: |
|
||||
echo "::group::Expected plugin version"
|
||||
EXPECTED=$(node -p "require('./package.json').version")
|
||||
echo "Plugin version in repo: $EXPECTED"
|
||||
echo "::endgroup::"
|
||||
- name: Build plugin
|
||||
run: npx @kinvolk/headlamp-plugin build
|
||||
|
||||
echo "::group::Headlamp connectivity"
|
||||
HTTP_CODE=$(curl -s -o /dev/null -w '%{http_code}' --connect-timeout 10 "$HEADLAMP_URL" || true)
|
||||
if [ "$HTTP_CODE" = "000" ]; then
|
||||
echo "::error::Cannot reach Headlamp at $HEADLAMP_URL — E2E tests will fail"
|
||||
- name: Deploy E2E Headlamp instance
|
||||
run: scripts/deploy-e2e-headlamp.sh
|
||||
|
||||
- name: Load E2E environment
|
||||
run: |
|
||||
if [ -f .env.e2e ]; then
|
||||
cat .env.e2e >> "$GITHUB_ENV"
|
||||
else
|
||||
echo "::error::deploy-e2e-headlamp.sh did not produce .env.e2e"
|
||||
exit 1
|
||||
fi
|
||||
echo "Headlamp responded with HTTP $HTTP_CODE at $HEADLAMP_URL"
|
||||
echo "::endgroup::"
|
||||
|
||||
echo "::group::Installed plugins"
|
||||
# Headlamp serves plugin metadata at /plugins — no auth required
|
||||
curl -sf --connect-timeout 10 "$HEADLAMP_URL/plugins" 2>/dev/null \
|
||||
| node -e "
|
||||
const d = require('fs').readFileSync(0,'utf8');
|
||||
try {
|
||||
const plugins = JSON.parse(d);
|
||||
for (const p of plugins) console.log(' ' + p.name + '@' + (p.version||'unknown'));
|
||||
} catch { console.log(' (could not parse plugin list)'); }
|
||||
" || echo " (plugin list endpoint not available — tests will validate at runtime)"
|
||||
echo "::endgroup::"
|
||||
|
||||
- name: Install Playwright browsers
|
||||
run: npx playwright install --with-deps chromium
|
||||
@@ -61,13 +69,25 @@ jobs:
|
||||
- name: Run E2E tests
|
||||
run: npm run e2e
|
||||
env:
|
||||
HEADLAMP_URL: ${{ secrets.HEADLAMP_URL || 'http://headlamp.kube-system.svc.cluster.local' }}
|
||||
HEADLAMP_TOKEN: ${{ secrets.HEADLAMP_TOKEN }}
|
||||
AUTHENTIK_USERNAME: ${{ secrets.AUTHENTIK_USERNAME }}
|
||||
AUTHENTIK_PASSWORD: ${{ secrets.AUTHENTIK_PASSWORD }}
|
||||
HEADLAMP_URL: ${{ env.HEADLAMP_URL }}
|
||||
HEADLAMP_TOKEN: ${{ env.HEADLAMP_TOKEN }}
|
||||
|
||||
- name: Collect deployment diagnostics on failure
|
||||
if: failure()
|
||||
run: |
|
||||
echo "=== Pod state ==="
|
||||
kubectl get pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
|
||||
echo "=== Pod describe ==="
|
||||
kubectl describe pods -n "$E2E_NAMESPACE" -l "app.kubernetes.io/instance=$E2E_RELEASE" 2>&1 || true
|
||||
echo "=== Recent namespace events ==="
|
||||
kubectl get events -n "$E2E_NAMESPACE" --sort-by='.lastTimestamp' 2>&1 | tail -20 || true
|
||||
|
||||
- name: Teardown E2E instance
|
||||
if: always()
|
||||
run: scripts/teardown-e2e-headlamp.sh
|
||||
|
||||
- name: Upload Playwright report
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: playwright-report
|
||||
@@ -75,7 +95,7 @@ jobs:
|
||||
retention-days: 7
|
||||
|
||||
- name: Upload test results
|
||||
uses: actions/upload-artifact@v4
|
||||
uses: actions/upload-artifact@v7
|
||||
if: failure()
|
||||
with:
|
||||
name: test-results
|
||||
|
||||
@@ -10,10 +10,14 @@ on:
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
|
||||
jobs:
|
||||
release:
|
||||
uses: privilegedescalation/.github/.github/workflows/plugin-release.yaml@main
|
||||
secrets:
|
||||
RELEASE_APP_ID: ${{ secrets.RELEASE_APP_ID }}
|
||||
RELEASE_APP_PRIVATE_KEY: ${{ secrets.RELEASE_APP_PRIVATE_KEY }}
|
||||
with:
|
||||
version: ${{ inputs.version }}
|
||||
upstream-repo: 'FairwindsOps/polaris'
|
||||
|
||||
@@ -6,5 +6,6 @@ e2e/.auth/
|
||||
test-results/
|
||||
.playwright-mcp/
|
||||
.env
|
||||
.env.e2e
|
||||
.env.local
|
||||
.eslintcache
|
||||
|
||||
+27
-1
@@ -7,6 +7,31 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [1.0.0] - 2026-03-22
|
||||
|
||||
First stable release. The plugin API (routes, sidebar entries, settings schema, and app bar action) is
|
||||
now frozen — no breaking changes without a new major version.
|
||||
|
||||
### Security
|
||||
- Patched 8 of 9 npm audit vulnerabilities via `pnpm.overrides` (#92)
|
||||
|
||||
### Added
|
||||
- **Dual-approval CI check**: PRs now require approval from both CTO and QA before merging (#98, #76)
|
||||
- **ExemptionManager test suite**: Full coverage of annotation-based exemption flows, exemption creation, and inline feedback (#82)
|
||||
- **RBAC preflight check**: `deploy-e2e-headlamp.sh` now verifies runner RBAC before attempting E2E deploy (#80)
|
||||
|
||||
### Fixed
|
||||
- **E2E infrastructure overhaul**: Replaced Dockerfile.e2e with ConfigMap volume mount for plugin loading; tests now run in the `privilegedescalation-dev` namespace (#73, #89, #94)
|
||||
- **E2E token auth**: Workflow uses GitHub App token auth and handles the `/token` redirect correctly (#97)
|
||||
- **E2E HTTP readiness**: `deploy-e2e-headlamp.sh` waits for HTTP reachability after rollout before running tests (#104)
|
||||
- **E2E runner label**: Updated to `runners-privilegedescalation` for self-hosted ARC runners (#71)
|
||||
- **Direct devDependencies**: Added `typescript`, `eslint`, `prettier`, and `@headlamp-k8s/eslint-config` as explicit direct devDependencies to prevent phantom-dep failures in clean installs (#95, #102)
|
||||
|
||||
### Changed
|
||||
- **pnpm version pinned**: `packageManager` field in `package.json` pins the pnpm version used in CI (#103)
|
||||
- **GitHub Actions SHA pinning**: Renovate `pinDigests` enabled to SHA-pin all GitHub Actions (#105)
|
||||
- **ArtifactHub metadata polish**: Improved `install` instructions and `changes` section formatting (#82)
|
||||
|
||||
## [0.6.0] - 2026-03-04
|
||||
|
||||
### Fixed
|
||||
@@ -270,7 +295,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
- Automated release workflow
|
||||
- Basic CI/CD pipeline
|
||||
|
||||
[Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.6.0...HEAD
|
||||
[Unreleased]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v1.0.0...HEAD
|
||||
[1.0.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/compare/v0.7.2...v1.0.0
|
||||
[0.6.0]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.6.0
|
||||
[0.3.5]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.5
|
||||
[0.3.4]: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/tag/v0.3.4
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
# Installation Policy
|
||||
|
||||
## Approved Installation Method
|
||||
|
||||
**The ONLY approved method for installing this plugin is via [Artifact Hub](https://artifacthub.io/) using the Headlamp plugin installer.**
|
||||
|
||||
No other installation method is acceptable. This includes but is not limited to:
|
||||
|
||||
- Direct installation from GitHub release assets
|
||||
- Manual npm pack / tarball extraction
|
||||
- initContainer workarounds that bypass Artifact Hub
|
||||
- Direct file copy or sidecar injection
|
||||
|
||||
## Enforcement
|
||||
|
||||
All deployment configurations, CI/CD pipelines, and documentation MUST reference Artifact Hub as the sole plugin distribution channel. Any pull request that introduces an alternative installation method will be rejected.
|
||||
|
||||
## Rationale
|
||||
|
||||
Artifact Hub provides verified checksums, consistent versioning, and a standard discovery mechanism for the CNCF ecosystem. Bypassing it introduces security and integrity risks.
|
||||
|
||||
---
|
||||
|
||||
*This policy is set by the CTO and approved by the CEO of Privileged Escalation.*
|
||||
@@ -48,9 +48,14 @@ Polaris must be deployed in the `polaris` namespace with the dashboard component
|
||||
|
||||
## Installing
|
||||
|
||||
### Option 1: Headlamp Plugin Manager (Recommended)
|
||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Install via the Headlamp UI:
|
||||
|
||||
The plugin is published on [Artifact Hub](https://artifacthub.io/packages/headlamp/polaris/headlamp-polaris-plugin). Configure Headlamp via Helm:
|
||||
1. Go to **Settings → Plugins**
|
||||
2. Click **Catalog** tab
|
||||
3. Search for "Polaris"
|
||||
4. Click **Install**
|
||||
|
||||
Or configure Headlamp via Helm:
|
||||
|
||||
```yaml
|
||||
config:
|
||||
@@ -62,56 +67,6 @@ pluginsManager:
|
||||
url: https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
```
|
||||
|
||||
Or install via the Headlamp UI:
|
||||
|
||||
1. Go to **Settings → Plugins**
|
||||
2. Click **Catalog** tab
|
||||
3. Search for "Polaris"
|
||||
4. Click **Install**
|
||||
|
||||
### Option 2: Sidecar Container (Alternative)
|
||||
|
||||
For detailed sidecar installation instructions, see [docs/DEPLOYMENT.md#installation-method-2-sidecar-container](docs/DEPLOYMENT.md#installation-method-2-sidecar-container).
|
||||
|
||||
```yaml
|
||||
sidecars:
|
||||
- name: headlamp-plugin
|
||||
image: node:lts-alpine
|
||||
command: ['/bin/sh']
|
||||
args:
|
||||
- -c
|
||||
- |
|
||||
npm install -g @kinvolk/headlamp-plugin
|
||||
headlamp-plugin install --config /config/plugin.yml
|
||||
tail -f /dev/null
|
||||
volumeMounts:
|
||||
- name: plugins
|
||||
mountPath: /headlamp/plugins
|
||||
- name: plugin-config
|
||||
mountPath: /config
|
||||
```
|
||||
|
||||
### Option 3: Manual Tarball Install
|
||||
|
||||
Download the `.tar.gz` from the [GitHub releases page](https://github.com/privilegedescalation/headlamp-polaris-plugin/releases), then extract into Headlamp's plugin directory:
|
||||
|
||||
```bash
|
||||
wget https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.3.10/polaris-0.3.10.tar.gz
|
||||
tar xzf polaris-0.3.10.tar.gz -C /headlamp/plugins/
|
||||
```
|
||||
|
||||
### Option 4: Build from Source
|
||||
|
||||
```bash
|
||||
git clone https://github.com/privilegedescalation/headlamp-polaris-plugin.git
|
||||
cd headlamp-polaris-plugin
|
||||
npm install
|
||||
npm run build
|
||||
npx @kinvolk/headlamp-plugin extract . /headlamp/plugins
|
||||
```
|
||||
|
||||
For complete installation instructions including Helm integration, FluxCD examples, and production deployment checklist, see **[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md)**.
|
||||
|
||||
## RBAC / Security Setup
|
||||
|
||||
The plugin fetches audit data through the Kubernetes API server's **service proxy** sub-resource. The identity making the request (Headlamp's service account, or the user's own token in token-auth mode) must be granted:
|
||||
|
||||
+46
-4
@@ -1,4 +1,4 @@
|
||||
version: "0.7.0"
|
||||
version: "1.0.0"
|
||||
name: headlamp-polaris
|
||||
displayName: Polaris
|
||||
createdAt: "2026-02-05T19:00:00Z"
|
||||
@@ -11,6 +11,7 @@ description: >-
|
||||
`polaris-dashboard` service in the `polaris` namespace.
|
||||
license: Apache-2.0
|
||||
homeURL: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||
appVersion: "10.1.6"
|
||||
category: security
|
||||
keywords:
|
||||
- polaris
|
||||
@@ -24,11 +25,52 @@ links:
|
||||
url: "https://github.com/privilegedescalation/headlamp-polaris-plugin"
|
||||
- name: Polaris
|
||||
url: "https://polaris.docs.fairwinds.com/"
|
||||
install: |
|
||||
## Installation
|
||||
|
||||
### Prerequisites
|
||||
|
||||
1. [Headlamp](https://headlamp.dev) v0.26.0 or later
|
||||
2. [Fairwinds Polaris](https://polaris.docs.fairwinds.com/) installed and the dashboard running in your cluster
|
||||
|
||||
### Install via Headlamp Plugin Catalog
|
||||
|
||||
1. Open Headlamp and navigate to **Settings → Plugin Catalog**
|
||||
2. Search for **"Polaris"**
|
||||
3. Click **Install** and restart Headlamp when prompted
|
||||
|
||||
The plugin is sourced directly from [ArtifactHub](https://artifacthub.io/packages/headlamp/headlamp/headlamp-polaris).
|
||||
|
||||
## Usage
|
||||
|
||||
After installation, the Polaris plugin adds:
|
||||
- A **cluster score badge** in the Headlamp app bar
|
||||
- A **Polaris** section in the sidebar with the full dashboard and namespace drill-downs
|
||||
- An **inline audit panel** on Deployment, StatefulSet, DaemonSet, Job, and CronJob detail pages
|
||||
|
||||
For more information, see the [README](https://github.com/privilegedescalation/headlamp-polaris-plugin/blob/main/README.md).
|
||||
changes:
|
||||
- kind: security
|
||||
description: Patched 8 npm audit vulnerabilities via pnpm.overrides
|
||||
- kind: added
|
||||
description: Dual-approval required CI check — PRs must be approved by both CTO and QA before merge
|
||||
- kind: added
|
||||
description: ExemptionManager test suite — full coverage of annotation-based exemption flows
|
||||
- kind: fixed
|
||||
description: E2E infrastructure overhauled — ConfigMap volume mount replaces Dockerfile-based approach, tests run in privilegedescalation-dev namespace
|
||||
- kind: fixed
|
||||
description: E2E workflow uses token auth and waits for HTTP reachability before running tests
|
||||
- kind: fixed
|
||||
description: Added explicit direct devDependencies (typescript, eslint, prettier, @headlamp-k8s/eslint-config) to prevent phantom dep failures
|
||||
- kind: changed
|
||||
description: pnpm version pinned via packageManager field; GitHub Actions SHA-pinned via Renovate pinDigests
|
||||
- kind: changed
|
||||
description: v1.0.0 stable release — plugin API (routes, sidebar, settings schema, app bar action) is stable and will not change without a major version bump
|
||||
maintainers:
|
||||
- name: privilegedescalation
|
||||
email: "chris@farhood.org"
|
||||
annotations:
|
||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v0.7.0/headlamp-polaris-0.7.0.tar.gz"
|
||||
headlamp/plugin/archive-url: "https://github.com/privilegedescalation/headlamp-polaris-plugin/releases/download/v1.0.0/headlamp-polaris-1.0.0.tar.gz"
|
||||
headlamp/plugin/version-compat: ">=0.26"
|
||||
headlamp/plugin/archive-checksum: sha256:c271590b71424b7f3e70e51309074f64531bb55063fcd9b8c18663579916cb97
|
||||
headlamp/plugin/distro-compat: in-cluster
|
||||
headlamp/plugin/archive-checksum: sha256:a165e871b40f11a44950aa9f10eb7f7883276f749026ae7a4f886278ecd9bd7d
|
||||
headlamp/plugin/distro-compat: "in-cluster,web,desktop"
|
||||
|
||||
@@ -0,0 +1,46 @@
|
||||
---
|
||||
# RBAC for the GitHub Actions CI runner to manage the E2E Headlamp instance.
|
||||
# CI-only test fixture — NOT for production use.
|
||||
#
|
||||
# Grants the ARC runner service account permissions in the privilegedescalation-dev
|
||||
# namespace to deploy and tear down a dedicated Headlamp instance via Helm.
|
||||
# E2E resources run in `privilegedescalation-dev` — nothing persists beyond a test run.
|
||||
#
|
||||
# Plugin is loaded via ConfigMap volume mount — no custom Docker images.
|
||||
#
|
||||
# Prerequisites:
|
||||
# kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: Role
|
||||
metadata:
|
||||
name: e2e-ci-runner
|
||||
namespace: privilegedescalation-dev
|
||||
rules:
|
||||
# Helm needs to manage these resources for the Headlamp chart
|
||||
- apiGroups: ["apps"]
|
||||
resources: ["deployments"]
|
||||
verbs: ["get", "list", "create", "update", "patch", "delete", "watch"]
|
||||
- apiGroups: [""]
|
||||
resources: ["services", "serviceaccounts", "configmaps", "secrets"]
|
||||
verbs: ["get", "list", "watch", "create", "update", "patch", "delete"]
|
||||
- apiGroups: [""]
|
||||
resources: ["pods"]
|
||||
verbs: ["get", "list", "watch"]
|
||||
# Token creation for E2E test auth
|
||||
- apiGroups: [""]
|
||||
resources: ["serviceaccounts/token"]
|
||||
verbs: ["create"]
|
||||
---
|
||||
apiVersion: rbac.authorization.k8s.io/v1
|
||||
kind: RoleBinding
|
||||
metadata:
|
||||
name: e2e-ci-runner-binding
|
||||
namespace: privilegedescalation-dev
|
||||
subjects:
|
||||
- kind: ServiceAccount
|
||||
name: runners-privilegedescalation-gha-rs-no-permission
|
||||
namespace: arc-runners
|
||||
roleRef:
|
||||
kind: Role
|
||||
name: e2e-ci-runner
|
||||
apiGroup: rbac.authorization.k8s.io
|
||||
@@ -1,83 +0,0 @@
|
||||
---
|
||||
# Custom Headlamp values for static plugin installation
|
||||
# This disables the plugin manager and uses an init container instead
|
||||
|
||||
# Disable the plugin manager sidecar
|
||||
pluginsManager:
|
||||
enabled: false
|
||||
|
||||
# Use an init container to install plugins to /headlamp/static-plugins
|
||||
initContainers:
|
||||
- name: install-plugins
|
||||
image: node:lts-alpine
|
||||
command:
|
||||
- /bin/sh
|
||||
- -c
|
||||
- |
|
||||
set -e
|
||||
echo "Installing plugins to /headlamp/static-plugins..."
|
||||
|
||||
# Create plugins directory
|
||||
mkdir -p /headlamp/static-plugins
|
||||
|
||||
# Set up npm cache
|
||||
export NPM_CONFIG_CACHE=/tmp/npm-cache
|
||||
export NPM_CONFIG_USERCONFIG=/tmp/npm-userconfig
|
||||
mkdir -p /tmp/npm-cache /tmp/npm-userconfig
|
||||
|
||||
# Install polaris plugin
|
||||
echo "Installing polaris plugin..."
|
||||
cd /headlamp/static-plugins
|
||||
npm pack headlamp-polaris-plugin@0.3.0
|
||||
tar -xzf headlamp-polaris-plugin-0.3.0.tgz
|
||||
mv package headlamp-polaris-plugin
|
||||
rm headlamp-polaris-plugin-0.3.0.tgz
|
||||
|
||||
# Install other plugins
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_flux \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-trivy/headlamp_trivy \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_cert-manager \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
npx --yes @headlamp-k8s/plugin@latest install \
|
||||
--source https://artifacthub.io/packages/headlamp/headlamp-plugins/headlamp_ai_assistant \
|
||||
--folderName /headlamp/static-plugins
|
||||
|
||||
echo "All plugins installed successfully"
|
||||
ls -la /headlamp/static-plugins
|
||||
securityContext:
|
||||
runAsUser: 100
|
||||
runAsGroup: 101
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
resources:
|
||||
requests:
|
||||
cpu: 100m
|
||||
memory: 256Mi
|
||||
limits:
|
||||
memory: 512Mi
|
||||
volumeMounts:
|
||||
- name: static-plugins
|
||||
mountPath: /headlamp/static-plugins
|
||||
|
||||
# Configure headlamp to use static plugins
|
||||
config:
|
||||
pluginsDir: /headlamp/static-plugins
|
||||
|
||||
# Add volume for static plugins
|
||||
volumes:
|
||||
- name: static-plugins
|
||||
emptyDir: {}
|
||||
|
||||
# Add volume mount to main container
|
||||
volumeMounts:
|
||||
- name: static-plugins
|
||||
mountPath: /headlamp/static-plugins
|
||||
readOnly: true
|
||||
@@ -19,7 +19,7 @@ Helm provides the easiest way to deploy and manage the plugin in production. Thi
|
||||
|
||||
```bash
|
||||
# Add Headlamp Helm repository
|
||||
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
|
||||
helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/
|
||||
helm repo update
|
||||
```
|
||||
|
||||
@@ -210,7 +210,7 @@ metadata:
|
||||
namespace: flux-system
|
||||
spec:
|
||||
interval: 1h
|
||||
url: https://headlamp-k8s.github.io/headlamp/
|
||||
url: https://kubernetes-sigs.github.io/headlamp/
|
||||
```
|
||||
|
||||
### HelmRelease
|
||||
|
||||
@@ -84,7 +84,7 @@ kubectl -n kube-system get deployment headlamp -o jsonpath='{.spec.template.spec
|
||||
|
||||
```bash
|
||||
# Add Headlamp Helm repository
|
||||
helm repo add headlamp https://headlamp-k8s.github.io/headlamp/
|
||||
helm repo add headlamp https://kubernetes-sigs.github.io/headlamp/
|
||||
helm repo update
|
||||
|
||||
# Install Headlamp
|
||||
|
||||
+33
-24
@@ -4,7 +4,16 @@ Playwright-based smoke tests that validate the Polaris plugin against a live Hea
|
||||
|
||||
## CI
|
||||
|
||||
E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`) uses either Authentik OIDC or token-based authentication via repository secrets.
|
||||
E2E tests run automatically in GitHub Actions on pushes to `main` and pull requests. The workflow (`.github/workflows/e2e.yaml`):
|
||||
|
||||
1. Builds the plugin (`npm run build`)
|
||||
2. Creates a ConfigMap from the built `dist/` output
|
||||
3. Deploys a stock Headlamp instance via Helm with the plugin mounted as a ConfigMap volume
|
||||
4. Generates a ServiceAccount token for test auth
|
||||
5. Runs Playwright tests against the E2E instance
|
||||
6. Tears down the E2E instance
|
||||
|
||||
This approach uses the stock `ghcr.io/headlamp-k8s/headlamp` image with no custom Docker builds. The plugin is loaded via `HEADLAMP_PLUGINS_DIR` volume mount.
|
||||
|
||||
### Required GitHub Secrets
|
||||
|
||||
@@ -12,12 +21,12 @@ Configure these in GitHub repository settings (Settings → Secrets and variable
|
||||
|
||||
| Secret | Required | Description |
|
||||
| -------------------- | -------- | -------------------------------------------------------------- |
|
||||
| `HEADLAMP_URL` | Optional | Headlamp instance URL (defaults to `https://headlamp.animaniacs.farh.net`) |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | Authentik email or username for a CI user with Headlamp access |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | Password for that user |
|
||||
| `HEADLAMP_TOKEN` | Token | Kubernetes service account token (alternative to OIDC) |
|
||||
|
||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` **or** `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
||||
Token-based auth is auto-generated by the deploy script. OIDC secrets are only needed if testing against the shared Headlamp instance.
|
||||
|
||||
No `GHCR_TOKEN` or Docker registry secrets are needed — the stock Headlamp image is public.
|
||||
|
||||
## Running Locally
|
||||
|
||||
@@ -47,12 +56,12 @@ HEADLAMP_URL=http://localhost:4466 npm run e2e:headed
|
||||
|
||||
| Variable | Required | Default | Description |
|
||||
| -------------------- | -------- | -------------------------------------- | --------------------------------------- |
|
||||
| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password |
|
||||
| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (fallback auth) |
|
||||
| `HEADLAMP_URL` | No | `https://headlamp.animaniacs.farh.net` | Base URL of the Headlamp instance |
|
||||
| `AUTHENTIK_USERNAME` | OIDC | — | Authentik email/username |
|
||||
| `AUTHENTIK_PASSWORD` | OIDC | — | Authentik password |
|
||||
| `HEADLAMP_TOKEN` | Token | — | Kubernetes bearer token (auto-generated in CI) |
|
||||
|
||||
Set either `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` or `HEADLAMP_TOKEN`. OIDC takes priority if both are set.
|
||||
In CI, `HEADLAMP_URL` and `HEADLAMP_TOKEN` are set automatically by the deploy script. For local runs, set either OIDC credentials or a token manually.
|
||||
|
||||
## What the Tests Validate
|
||||
|
||||
@@ -249,25 +258,25 @@ test('plugin UI adapts to dark mode', async ({ page }) => {
|
||||
|
||||
Tests run automatically in GitHub Actions on pushes to `main` and pull requests. See `.github/workflows/e2e.yaml` for workflow configuration.
|
||||
|
||||
### Required Secrets
|
||||
### Architecture
|
||||
|
||||
Configure these in GitHub repository settings (Settings → Secrets and variables → Actions):
|
||||
The E2E workflow deploys a **dedicated Headlamp instance** for each test run:
|
||||
|
||||
- `HEADLAMP_URL` (optional): Headlamp instance URL
|
||||
- `AUTHENTIK_USERNAME` + `AUTHENTIK_PASSWORD` (for OIDC auth)
|
||||
- OR `HEADLAMP_TOKEN` (for token-based auth)
|
||||
1. Build plugin (`npm run build`)
|
||||
2. Create ConfigMap from `dist/` output (`scripts/deploy-e2e-headlamp.sh`)
|
||||
3. Deploy stock Headlamp via Helm with ConfigMap volume mount
|
||||
4. Run Playwright tests against the E2E instance
|
||||
5. Tear down (`scripts/teardown-e2e-headlamp.sh`)
|
||||
|
||||
### Workflow Overview
|
||||
No custom Docker images, no PVCs, no kubectl exec/cp, no patching of existing deployments. The plugin is mounted from a ConfigMap into the stock Headlamp image.
|
||||
|
||||
1. Checkout code
|
||||
2. Setup Node.js 20 with npm cache
|
||||
3. Install dependencies (`npm ci`)
|
||||
4. Install Playwright browsers (`chromium` only)
|
||||
5. Run auth setup (creates session in `e2e/.auth/state.json`)
|
||||
6. Run all E2E tests
|
||||
7. Upload artifacts on failure:
|
||||
- `playwright-report/` - HTML test report
|
||||
- `test-results/` - Screenshots, traces, videos
|
||||
### Cluster Prerequisites
|
||||
|
||||
One-time setup by a cluster admin:
|
||||
|
||||
```bash
|
||||
kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
|
||||
```
|
||||
|
||||
### Manual Trigger
|
||||
|
||||
|
||||
+6
-10
@@ -47,16 +47,12 @@ test.describe('Polaris app bar badge', () => {
|
||||
window.getComputedStyle(el).backgroundColor
|
||||
);
|
||||
|
||||
if (score >= 80) {
|
||||
// Green: rgb(76, 175, 80) or #4caf50
|
||||
expect(bgColor).toMatch(/rgb\(76,\s*175,\s*80\)/);
|
||||
} else if (score >= 50) {
|
||||
// Orange: rgb(255, 152, 0) or #ff9800
|
||||
expect(bgColor).toMatch(/rgb\(255,\s*152,\s*0\)/);
|
||||
} else {
|
||||
// Red: rgb(244, 67, 54) or #f44336
|
||||
expect(bgColor).toMatch(/rgb\(244,\s*67,\s*54\)/);
|
||||
}
|
||||
// Verify that the badge has a non-default background color applied
|
||||
// (theme-dependent RGB values vary across Headlamp versions, so we
|
||||
// only assert that a real color is set rather than transparent/default)
|
||||
expect(bgColor).not.toBe('rgba(0, 0, 0, 0)');
|
||||
expect(bgColor).not.toBe('transparent');
|
||||
expect(bgColor).toMatch(/^rgb/);
|
||||
});
|
||||
|
||||
test('badge updates when navigating between clusters', async ({ page }) => {
|
||||
|
||||
+25
-9
@@ -12,12 +12,21 @@ async function authenticateWithOIDC(page: Page, username: string, password: stri
|
||||
await page.getByRole('button', { name: /sign in/i }).click();
|
||||
const popup = await popupPromise;
|
||||
|
||||
// Authentik step 1: fill username
|
||||
await popup.getByRole('textbox', { name: /email or username/i }).fill(username);
|
||||
// Wait for the Authentik popup to fully load before interacting
|
||||
await popup.waitForLoadState('domcontentloaded');
|
||||
await popup.waitForLoadState('networkidle');
|
||||
|
||||
// Authentik step 1: fill username — wait for the form to render
|
||||
const usernameField = popup.getByRole('textbox', { name: /email or username/i });
|
||||
await usernameField.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
await usernameField.fill(username);
|
||||
await popup.getByRole('button', { name: /log in/i }).click();
|
||||
|
||||
// Authentik step 2: fill password
|
||||
await popup.getByRole('textbox', { name: /password/i }).fill(password);
|
||||
// Authentik step 2: fill password — wait for the next step to load
|
||||
await popup.waitForLoadState('networkidle');
|
||||
const passwordField = popup.getByRole('textbox', { name: /password/i });
|
||||
await passwordField.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
await passwordField.fill(password);
|
||||
await popup.getByRole('button', { name: /continue|log in/i }).click();
|
||||
|
||||
// Wait for the popup to close (Authentik redirects back, Headlamp processes callback)
|
||||
@@ -30,13 +39,20 @@ async function authenticateWithOIDC(page: Page, username: string, password: stri
|
||||
}
|
||||
|
||||
async function authenticateWithToken(page: Page, token: string): Promise<void> {
|
||||
// Navigate to login — Headlamp redirects / to /c/main/login
|
||||
await page.goto('/');
|
||||
await page.waitForURL('**/login');
|
||||
// Headlamp goes to /token directly when no OIDC is configured,
|
||||
// or through /login when OIDC is configured
|
||||
await page.waitForURL(/\/(login|token)$/);
|
||||
|
||||
// Click the token auth option
|
||||
await page.getByRole('button', { name: /use a token/i }).click();
|
||||
await page.waitForURL('**/token');
|
||||
if (page.url().includes('/login')) {
|
||||
// OIDC login page — click "use a token" to reach token auth.
|
||||
// Wait explicitly before clicking so failures surface at 15 s
|
||||
// with a clear message rather than silently timing out at 60 s.
|
||||
const useTokenBtn = page.getByRole('button', { name: /use a token/i });
|
||||
await useTokenBtn.waitFor({ state: 'visible', timeout: 15_000 });
|
||||
await useTokenBtn.click();
|
||||
await page.waitForURL('**/token');
|
||||
}
|
||||
|
||||
// Fill the "ID token" field and submit
|
||||
await page.getByRole('textbox', { name: /id token/i }).fill(token);
|
||||
|
||||
+2
-2
@@ -17,7 +17,7 @@ test.describe('Polaris plugin smoke tests', () => {
|
||||
|
||||
// "Cluster Score" section exists with a percentage
|
||||
await expect(page.getByText('Cluster Score')).toBeVisible();
|
||||
await expect(page.getByText(/%/)).toBeVisible();
|
||||
await expect(page.locator('main').getByText(/%/).first()).toBeVisible();
|
||||
});
|
||||
|
||||
test('namespaces page renders table with namespace buttons', async ({ page }) => {
|
||||
@@ -55,7 +55,7 @@ test.describe('Polaris plugin smoke tests', () => {
|
||||
await expect(page.getByText('Namespace Score')).toBeVisible();
|
||||
|
||||
// Resources table should exist in drawer
|
||||
await expect(page.getByText('Resources')).toBeVisible();
|
||||
await expect(page.getByRole('heading', { name: 'Resources' })).toBeVisible();
|
||||
|
||||
// URL hash should be updated with namespace name
|
||||
await expect(page).toHaveURL(/\/polaris\/namespaces#/);
|
||||
|
||||
+27
-25
@@ -1,23 +1,34 @@
|
||||
import { test, expect } from '@playwright/test';
|
||||
import { test, expect, Page } from '@playwright/test';
|
||||
|
||||
/** Navigate to the Polaris plugin settings page and wait for settings to render. */
|
||||
async function goToPolarisSettings(page: Page) {
|
||||
// Headlamp's plugin settings page is a HOME-context route at /settings/plugins,
|
||||
// not an in-cluster route (/c/main/settings/plugins would 404). Headlamp loads
|
||||
// plugin scripts asynchronously on SPA init. When registerPluginSettings() fires,
|
||||
// it dispatches a Redux action — PluginSettings uses useTypedSelector so it
|
||||
// re-renders automatically once the plugin registers. No preloading needed.
|
||||
await page.goto('/settings/plugins');
|
||||
|
||||
// Wait for the plugin to appear in the settings list. The timeout covers
|
||||
// async plugin script loading + registration.
|
||||
const pluginEntry = page.locator('text=headlamp-polaris').first();
|
||||
await expect(pluginEntry).toBeVisible({ timeout: 30_000 });
|
||||
await pluginEntry.click();
|
||||
|
||||
// Wait for the PolarisSettings component to render
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
}
|
||||
|
||||
test.describe('Polaris plugin settings', () => {
|
||||
test('settings page shows configuration options', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
await goToPolarisSettings(page);
|
||||
|
||||
// Find Polaris plugin in the list
|
||||
const pluginCard = page.locator('text=polaris').first();
|
||||
await expect(pluginCard).toBeVisible();
|
||||
|
||||
// Click to view settings (if settings are displayed inline, they should already be visible)
|
||||
// Note: Headlamp v0.39.0+ shows settings inline on the plugins page
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
// SectionBox title should be visible
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible();
|
||||
});
|
||||
|
||||
test('refresh interval setting is configurable', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
await goToPolarisSettings(page);
|
||||
|
||||
// Find the refresh interval dropdown
|
||||
const intervalSelect = page.locator('select').filter({ hasText: /minute|second/ });
|
||||
@@ -35,10 +46,7 @@ test.describe('Polaris plugin settings', () => {
|
||||
});
|
||||
|
||||
test('dashboard URL setting is configurable', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
await goToPolarisSettings(page);
|
||||
|
||||
// Find the dashboard URL input
|
||||
const urlInput = page.getByPlaceholder(/polaris-dashboard/);
|
||||
@@ -54,10 +62,7 @@ test.describe('Polaris plugin settings', () => {
|
||||
});
|
||||
|
||||
test('connection test button is available', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
await goToPolarisSettings(page);
|
||||
|
||||
// Find and verify test connection button
|
||||
const testButton = page.getByRole('button', { name: /test connection/i });
|
||||
@@ -66,10 +71,7 @@ test.describe('Polaris plugin settings', () => {
|
||||
});
|
||||
|
||||
test('connection test works with valid URL', async ({ page }) => {
|
||||
await page.goto('/c/main/settings/plugins');
|
||||
|
||||
// Navigate to Polaris settings
|
||||
await expect(page.getByText('Polaris Settings')).toBeVisible({ timeout: 15_000 });
|
||||
await goToPolarisSettings(page);
|
||||
|
||||
// Click test connection
|
||||
const testButton = page.getByRole('button', { name: /test connection/i });
|
||||
|
||||
Generated
+980
-555
File diff suppressed because it is too large
Load Diff
+19
-1
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "headlamp-polaris",
|
||||
"version": "0.7.0",
|
||||
"version": "1.0.0",
|
||||
"description": "Headlamp plugin for Fairwinds Polaris audit results",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -12,6 +12,7 @@
|
||||
"homepage": "https://github.com/privilegedescalation/headlamp-polaris-plugin#readme",
|
||||
"author": "privilegedescalation",
|
||||
"license": "Apache-2.0",
|
||||
"packageManager": "pnpm@10.32.1",
|
||||
"scripts": {
|
||||
"start": "headlamp-plugin start",
|
||||
"build": "headlamp-plugin build",
|
||||
@@ -30,6 +31,14 @@
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"tar": "^7.5.11",
|
||||
"undici": "^7.24.3",
|
||||
"flatted": "^3.4.2",
|
||||
"lodash": ">=4.18.0"
|
||||
}
|
||||
},
|
||||
"devDependencies": {
|
||||
"@kinvolk/headlamp-plugin": "^0.13.0",
|
||||
"@mui/material": "^5.15.14",
|
||||
@@ -37,10 +46,19 @@
|
||||
"@testing-library/jest-dom": "^6.4.8",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.5.2",
|
||||
"@types/react": "^19.2.14",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitest/coverage-v8": "^3.2.4",
|
||||
"@headlamp-k8s/eslint-config": "^0.6.0",
|
||||
"eslint": "^8.57.0",
|
||||
"jsdom": "^24.0.0",
|
||||
"prettier": "^2.8.8",
|
||||
"react": "^18.3.1",
|
||||
"react-dom": "^18.3.1",
|
||||
"react-router-dom": "^5.3.0",
|
||||
"tar": "^7.5.11",
|
||||
"typescript": "~5.6.2",
|
||||
"undici": "^7.24.3",
|
||||
"vitest": "^3.0.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,9 +11,10 @@ export default defineConfig({
|
||||
use: {
|
||||
baseURL: process.env.HEADLAMP_URL || 'https://headlamp.animaniacs.farh.net',
|
||||
trace: 'on-first-retry',
|
||||
screenshot: 'only-on-failure',
|
||||
},
|
||||
projects: [
|
||||
{ name: 'setup', testMatch: /auth\.setup\.ts/ },
|
||||
{ name: 'setup', testMatch: /auth\.setup\.ts/, timeout: 60_000 },
|
||||
{
|
||||
name: 'chromium',
|
||||
use: {
|
||||
|
||||
Generated
+251
-69
@@ -4,16 +4,25 @@ settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
overrides:
|
||||
tar: ^7.5.11
|
||||
undici: ^7.24.3
|
||||
flatted: ^3.4.2
|
||||
lodash: '>=4.18.0'
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@headlamp-k8s/eslint-config':
|
||||
specifier: ^0.6.0
|
||||
version: 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||
'@kinvolk/headlamp-plugin':
|
||||
specifier: ^0.13.0
|
||||
version: 0.13.1(@swc/core@1.15.18)(@types/debug@4.1.12)(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(csstype@3.2.3)(esbuild@0.25.12)(immer@11.1.4)(openapi-types@12.1.3)(redux@5.0.1)(rollup@4.59.0)(terser@5.46.0)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
|
||||
'@mui/material':
|
||||
specifier: ^5.15.14
|
||||
version: 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@playwright/test':
|
||||
specifier: ^1.58.2
|
||||
version: 1.58.2
|
||||
@@ -22,13 +31,28 @@ importers:
|
||||
version: 6.9.1
|
||||
'@testing-library/react':
|
||||
specifier: ^16.0.0
|
||||
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@18.3.7(@types/react@18.3.28))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
version: 16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@testing-library/user-event':
|
||||
specifier: ^14.5.2
|
||||
version: 14.6.1(@testing-library/dom@10.4.1)
|
||||
'@types/react':
|
||||
specifier: ^19.2.14
|
||||
version: 19.2.14
|
||||
'@types/react-dom':
|
||||
specifier: ^19.2.3
|
||||
version: 19.2.3(@types/react@19.2.14)
|
||||
'@vitest/coverage-v8':
|
||||
specifier: ^3.2.4
|
||||
version: 3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2))
|
||||
eslint:
|
||||
specifier: ^8.57.0
|
||||
version: 8.57.1
|
||||
jsdom:
|
||||
specifier: ^24.0.0
|
||||
version: 24.1.3
|
||||
prettier:
|
||||
specifier: ^2.8.8
|
||||
version: 2.8.8
|
||||
react:
|
||||
specifier: ^18.3.1
|
||||
version: 18.3.1
|
||||
@@ -38,6 +62,15 @@ importers:
|
||||
react-router-dom:
|
||||
specifier: ^5.3.0
|
||||
version: 5.3.4(react@18.3.1)
|
||||
tar:
|
||||
specifier: ^7.5.11
|
||||
version: 7.5.12
|
||||
typescript:
|
||||
specifier: ~5.6.2
|
||||
version: 5.6.2
|
||||
undici:
|
||||
specifier: ^7.24.3
|
||||
version: 7.24.5
|
||||
vitest:
|
||||
specifier: ^3.0.5
|
||||
version: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2)
|
||||
@@ -47,6 +80,10 @@ packages:
|
||||
'@adobe/css-tools@4.4.4':
|
||||
resolution: {integrity: sha512-Elp+iwUx5rN5+Y8xLt5/GRoG20WGoDCQ/1Fb+1LiGtvwbDavuSk0jhD/eZdckHAuzcDzccnkv+rEjyWfRx18gg==}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@apidevtools/json-schema-ref-parser@11.7.2':
|
||||
resolution: {integrity: sha512-4gY54eEGEstClvEkGnwVkTkrx0sqwemEFG5OSRRn3tD91XH0+Q8XIkYIfo7IwEWPpJZwILb9GUXeShtplRc/eA==}
|
||||
engines: {node: '>= 16'}
|
||||
@@ -153,6 +190,10 @@ packages:
|
||||
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
|
||||
engines: {node: '>=6.9.0'}
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2':
|
||||
resolution: {integrity: sha512-6zABk/ECA/QYSCQ1NGiVwwbQerUCZ+TQbp64Q3AgmfNvurHH0j8TtXa1qbShXA6qqkpAj4V5W8pP6mLe1mcMqA==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==}
|
||||
|
||||
@@ -1465,6 +1506,11 @@ packages:
|
||||
peerDependencies:
|
||||
'@types/react': ^18.0.0
|
||||
|
||||
'@types/react-dom@19.2.3':
|
||||
resolution: {integrity: sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==}
|
||||
peerDependencies:
|
||||
'@types/react': ^19.2.0
|
||||
|
||||
'@types/react-redux@7.1.34':
|
||||
resolution: {integrity: sha512-GdFaVjEbYv4Fthm2ZLvj1VSCedV7TqE5y1kNwnjSdBOTXuRSgowux6J8TAct15T3CKBr63UMk+2CO7ilRhyrAQ==}
|
||||
|
||||
@@ -1485,6 +1531,9 @@ packages:
|
||||
'@types/react@18.3.28':
|
||||
resolution: {integrity: sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==}
|
||||
|
||||
'@types/react@19.2.14':
|
||||
resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
|
||||
|
||||
'@types/resolve@1.20.6':
|
||||
resolution: {integrity: sha512-A4STmOXPhMUtHH+S6ymgE2GiBSMqf4oTvcQZMcHzokuTLVYzXTB8ttjcgxOVaAp2lGwEdzZ0J+cRbbeevQj1UQ==}
|
||||
|
||||
@@ -1585,6 +1634,15 @@ packages:
|
||||
peerDependencies:
|
||||
vitest: 3.2.4
|
||||
|
||||
'@vitest/coverage-v8@3.2.4':
|
||||
resolution: {integrity: sha512-EyF9SXU6kS5Ku/U82E259WSnvg6c8KTjppUncuNdm5QHpe17mwREHnjDzozC8x9MZ0xfBUFSaLkRv4TMA75ALQ==}
|
||||
peerDependencies:
|
||||
'@vitest/browser': 3.2.4
|
||||
vitest: 3.2.4
|
||||
peerDependenciesMeta:
|
||||
'@vitest/browser':
|
||||
optional: true
|
||||
|
||||
'@vitest/expect@3.2.4':
|
||||
resolution: {integrity: sha512-Io0yyORnB6sikFlt8QW5K7slY4OjqNX9jmJQ02QDda8lyM6B5oNgVWoSoKPac8/kgnCUzuHQKrSLtu/uOqqrig==}
|
||||
|
||||
@@ -1831,6 +1889,9 @@ packages:
|
||||
resolution: {integrity: sha512-6t10qk83GOG8p0vKmaCr8eiilZwO171AvbROMtvvNiwrTly62t+7XkA8RdIIVbpMhCASAsxgAzdRSwh6nw/5Dg==}
|
||||
engines: {node: '>=4'}
|
||||
|
||||
ast-v8-to-istanbul@0.3.12:
|
||||
resolution: {integrity: sha512-BRRC8VRZY2R4Z4lFIL35MwNXmwVqBityvOIwETtsCSwvjl0IdgFsy9NhdaA6j74nUdtJJlIypeRhpDam19Wq3g==}
|
||||
|
||||
astral-regex@2.0.0:
|
||||
resolution: {integrity: sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==}
|
||||
engines: {node: '>=8'}
|
||||
@@ -2810,8 +2871,8 @@ packages:
|
||||
resolution: {integrity: sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==}
|
||||
engines: {node: ^10.12.0 || >=12.0.0}
|
||||
|
||||
flatted@3.4.0:
|
||||
resolution: {integrity: sha512-kC6Bb+ooptOIvWj5B63EQWkF0FEnNjV2ZNkLMLZRDDduIiWeFF4iKnslwhiWxjAdbg4NzTNo6h0qLuvFrcx+Sw==}
|
||||
flatted@3.4.2:
|
||||
resolution: {integrity: sha512-PjDse7RzhcPkIJwy5t7KPWQSZ9cAbzQXcafsetQoD7sOJRQlGikNbx7yZp2OotDnJyrDcbyRq3Ttb18iYOqkxA==}
|
||||
|
||||
for-each@0.3.5:
|
||||
resolution: {integrity: sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==}
|
||||
@@ -3376,6 +3437,9 @@ packages:
|
||||
js-base64@3.7.8:
|
||||
resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==}
|
||||
|
||||
js-tokens@10.0.0:
|
||||
resolution: {integrity: sha512-lM/UBzQmfJRo9ABXbPWemivdCW8V2G8FHaHdypQaIy523snUjog0W71ayWXTjiR+ixeMyVHN2XcpnTd/liPg/Q==}
|
||||
|
||||
js-tokens@4.0.0:
|
||||
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
|
||||
|
||||
@@ -3490,8 +3554,8 @@ packages:
|
||||
lodash.truncate@4.4.2:
|
||||
resolution: {integrity: sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==}
|
||||
|
||||
lodash@4.17.23:
|
||||
resolution: {integrity: sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==}
|
||||
lodash@4.18.1:
|
||||
resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==}
|
||||
|
||||
longest-streak@3.1.0:
|
||||
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
|
||||
@@ -4713,8 +4777,8 @@ packages:
|
||||
resolution: {integrity: sha512-g9ljZiwki/LfxmQADO3dEY1CbpmXT5Hm2fJ+QaGKwSXUylMybePR7/67YW7jOrrvjEgL1Fmz5kzyAjWVWLlucg==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
tar@7.5.10:
|
||||
resolution: {integrity: sha512-8mOPs1//5q/rlkNSPcCegA6hiHJYDmSLEI8aMH/CdSQJNWztHC9WHNam5zdQlfpTwB9Xp7IBEsHfV5LKMJGVAw==}
|
||||
tar@7.5.12:
|
||||
resolution: {integrity: sha512-9TsuLcdhOn4XztcQqhNyq1KOwOOED/3k58JAvtULiYqbO8B/0IBAAIE1hj0Svmm58k27TmcigyDI0deMlgG3uw==}
|
||||
engines: {node: '>=18'}
|
||||
|
||||
teex@1.0.1:
|
||||
@@ -4898,8 +4962,8 @@ packages:
|
||||
undici-types@6.21.0:
|
||||
resolution: {integrity: sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==}
|
||||
|
||||
undici@7.22.0:
|
||||
resolution: {integrity: sha512-RqslV2Us5BrllB+JeiZnK4peryVTndy9Dnqq62S3yYRRTj0tFQCwEniUy2167skdGOy3vqRzEvl1Dm4sV2ReDg==}
|
||||
undici@7.24.5:
|
||||
resolution: {integrity: sha512-3IWdCpjgxp15CbJnsi/Y9TCDE7HWVN19j1hmzVhoAkY/+CJx449tVxT5wZc1Gwg8J+P0LWvzlBzxYRnHJ+1i7Q==}
|
||||
engines: {node: '>=20.18.1'}
|
||||
|
||||
unicorn-magic@0.1.0:
|
||||
@@ -5298,6 +5362,11 @@ snapshots:
|
||||
|
||||
'@adobe/css-tools@4.4.4': {}
|
||||
|
||||
'@ampproject/remapping@2.3.0':
|
||||
dependencies:
|
||||
'@jridgewell/gen-mapping': 0.3.13
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
|
||||
'@apidevtools/json-schema-ref-parser@11.7.2':
|
||||
dependencies:
|
||||
'@jsdevtools/ono': 7.1.3
|
||||
@@ -5441,6 +5510,8 @@ snapshots:
|
||||
'@babel/helper-string-parser': 7.27.1
|
||||
'@babel/helper-validator-identifier': 7.28.5
|
||||
|
||||
'@bcoe/v8-coverage@1.0.2': {}
|
||||
|
||||
'@bundled-es-modules/cookie@2.0.1':
|
||||
dependencies:
|
||||
cookie: 0.7.2
|
||||
@@ -5532,7 +5603,7 @@ snapshots:
|
||||
|
||||
'@emotion/sheet@1.4.0': {}
|
||||
|
||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)':
|
||||
'@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@emotion/babel-plugin': 11.13.5
|
||||
@@ -5703,7 +5774,7 @@ snapshots:
|
||||
js-yaml: 4.1.1
|
||||
semver: 7.7.4
|
||||
table: 6.9.0
|
||||
tar: 7.5.10
|
||||
tar: 7.5.12
|
||||
tmp: 0.2.5
|
||||
yargs: 17.7.2
|
||||
|
||||
@@ -5821,19 +5892,19 @@ snapshots:
|
||||
dependencies:
|
||||
'@apidevtools/swagger-parser': 10.1.1(openapi-types@12.1.3)
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@headlamp-k8s/eslint-config': 0.6.0(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint-config-prettier@9.1.2(eslint@8.57.1))(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint-plugin-jsx-a11y@6.10.2(eslint@8.57.1))(eslint-plugin-react-hooks@4.6.2(eslint@8.57.1))(eslint-plugin-react@7.35.0(eslint@8.57.1))(eslint-plugin-simple-import-sort@12.1.1(eslint@8.57.1))(eslint-plugin-unused-imports@4.4.1(@typescript-eslint/eslint-plugin@8.56.1(@typescript-eslint/parser@8.56.1(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1)(typescript@5.6.2))(eslint@8.57.1))(eslint@8.57.1)
|
||||
'@headlamp-k8s/pluginctl': 0.1.1
|
||||
'@iconify/icons-mdi': 1.2.48
|
||||
'@iconify/react': 3.2.2(react@18.3.1)
|
||||
'@monaco-editor/react': 4.7.0(monaco-editor@0.52.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/lab': 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/x-tree-view': 6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
|
||||
'@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)
|
||||
'@mui/lab': 5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/x-tree-view': 6.17.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@reduxjs/toolkit': 2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@18.3.1)(redux@5.0.1))(react@18.3.1)
|
||||
'@storybook/addon-docs': 9.1.20(@types/react@18.3.28)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
|
||||
'@storybook/addon-links': 9.1.20(react@18.3.1)(storybook@9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2)))
|
||||
'@storybook/addon-webpack5-compiler-swc': 3.0.0(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
|
||||
@@ -5885,8 +5956,8 @@ snapshots:
|
||||
js-yaml: 4.1.1
|
||||
jsdom: 24.1.3
|
||||
jsonpath-plus: 10.4.0
|
||||
lodash: 4.17.23
|
||||
material-react-table: 2.13.3(93149b7a28d7dcf9399e2d03ebc8c990)
|
||||
lodash: 4.18.1
|
||||
material-react-table: 2.13.3(330725fe5432f245d076f0c0dda1a7a7)
|
||||
monaco-editor: 0.52.2
|
||||
msw: 2.4.9(typescript@5.6.2)
|
||||
msw-storybook-addon: 2.0.3(msw@2.4.9(typescript@5.6.2))
|
||||
@@ -5911,7 +5982,7 @@ snapshots:
|
||||
spacetime: 7.12.0
|
||||
storybook: 9.1.20(@testing-library/dom@10.4.1)(msw@2.4.9(typescript@5.6.2))(prettier@2.8.8)(vite@6.4.1(@types/node@20.19.37)(terser@5.46.0)(yaml@2.8.2))
|
||||
table: 6.9.0
|
||||
tar: 7.5.10
|
||||
tar: 7.5.12
|
||||
ts-loader: 9.5.4(typescript@5.6.2)(webpack@5.105.4(@swc/core@1.15.18)(esbuild@0.25.12))
|
||||
typescript: 5.6.2
|
||||
validate-npm-package-name: 3.0.0
|
||||
@@ -6024,20 +6095,20 @@ snapshots:
|
||||
|
||||
'@mui/core-downloads-tracker@5.18.0': {}
|
||||
|
||||
'@mui/icons-material@5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)':
|
||||
'@mui/icons-material@5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.28
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/lab@5.0.0-alpha.177(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/base': 5.0.0-beta.40-1(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@18.3.28)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.28)(react@18.3.1)
|
||||
clsx: 2.1.1
|
||||
@@ -6046,14 +6117,14 @@ snapshots:
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/core-downloads-tracker': 5.18.0
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@18.3.28)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.28)(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
@@ -6067,9 +6138,30 @@ snapshots:
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/core-downloads-tracker': 5.18.0
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@19.2.14)
|
||||
'@mui/utils': 5.17.1(@types/react@19.2.14)(react@18.3.1)
|
||||
'@popperjs/core': 2.11.8
|
||||
'@types/react-transition-group': 4.4.12(@types/react@19.2.14)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.2.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-is: 19.2.4
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/private-theming@5.17.1(@types/react@18.3.28)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
@@ -6079,7 +6171,16 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/private-theming@5.17.1(@types/react@19.2.14)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/utils': 5.17.1(@types/react@19.2.14)(react@18.3.1)
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/styled-engine@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@emotion/cache': 11.14.0
|
||||
@@ -6089,13 +6190,13 @@ snapshots:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
|
||||
'@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)':
|
||||
'@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/private-theming': 5.17.1(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(react@18.3.1)
|
||||
'@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@18.3.28)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.28)(react@18.3.1)
|
||||
clsx: 2.1.1
|
||||
@@ -6104,13 +6205,33 @@ snapshots:
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/private-theming': 5.17.1(@types/react@19.2.14)(react@18.3.1)
|
||||
'@mui/styled-engine': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(react@18.3.1)
|
||||
'@mui/types': 7.2.24(@types/react@19.2.14)
|
||||
'@mui/utils': 5.17.1(@types/react@19.2.14)(react@18.3.1)
|
||||
clsx: 2.1.1
|
||||
csstype: 3.2.3
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/types@7.2.24(@types/react@18.3.28)':
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@mui/types@7.2.24(@types/react@19.2.14)':
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/types@7.4.12(@types/react@18.3.28)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
@@ -6129,6 +6250,18 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@mui/utils@5.17.1(@types/react@19.2.14)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/types': 7.2.24(@types/react@19.2.14)
|
||||
'@types/prop-types': 15.7.15
|
||||
clsx: 2.1.1
|
||||
prop-types: 15.8.1
|
||||
react: 18.3.1
|
||||
react-is: 19.2.4
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@mui/utils@6.4.9(@types/react@18.3.28)(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
@@ -6153,11 +6286,11 @@ snapshots:
|
||||
optionalDependencies:
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@mui/x-date-pickers@7.29.4(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/x-date-pickers@7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/utils': 7.3.9(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/x-internals': 7.29.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@types/react-transition-group': 4.4.12(@types/react@18.3.28)
|
||||
@@ -6168,7 +6301,7 @@ snapshots:
|
||||
react-transition-group: 4.4.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
@@ -6180,14 +6313,14 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- '@types/react'
|
||||
|
||||
'@mui/x-tree-view@6.17.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
'@mui/x-tree-view@6.17.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/base': 5.0.0-beta.70(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/system': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/utils': 5.17.1(@types/react@18.3.28)(react@18.3.1)
|
||||
'@types/react-transition-group': 4.4.12(@types/react@18.3.28)
|
||||
clsx: 2.1.1
|
||||
@@ -6228,7 +6361,7 @@ snapshots:
|
||||
|
||||
'@popperjs/core@2.11.8': {}
|
||||
|
||||
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@18.3.28)(react@18.3.1)(redux@5.0.1))(react@18.3.1)':
|
||||
'@reduxjs/toolkit@2.11.2(react-redux@9.2.0(@types/react@19.2.14)(react@18.3.1)(redux@5.0.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@standard-schema/spec': 1.1.0
|
||||
'@standard-schema/utils': 0.3.0
|
||||
@@ -6689,6 +6822,16 @@ snapshots:
|
||||
'@types/react': 18.3.28
|
||||
'@types/react-dom': 18.3.7(@types/react@18.3.28)
|
||||
|
||||
'@testing-library/react@16.3.2(@testing-library/dom@10.4.1)(@types/react-dom@19.2.3(@types/react@19.2.14))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)':
|
||||
dependencies:
|
||||
'@babel/runtime': 7.28.6
|
||||
'@testing-library/dom': 10.4.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
optionalDependencies:
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-dom': 19.2.3(@types/react@19.2.14)
|
||||
|
||||
'@testing-library/user-event@14.6.1(@testing-library/dom@10.4.1)':
|
||||
dependencies:
|
||||
'@testing-library/dom': 10.4.1
|
||||
@@ -6792,9 +6935,9 @@ snapshots:
|
||||
|
||||
'@types/history@4.7.11': {}
|
||||
|
||||
'@types/hoist-non-react-statics@3.3.7(@types/react@18.3.28)':
|
||||
'@types/hoist-non-react-statics@3.3.7(@types/react@19.2.14)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.28
|
||||
'@types/react': 19.2.14
|
||||
hoist-non-react-statics: 3.3.2
|
||||
|
||||
'@types/html-minifier-terser@6.1.0': {}
|
||||
@@ -6841,37 +6984,49 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@types/react-dom@19.2.3(@types/react@19.2.14)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@types/react-redux@7.1.34':
|
||||
dependencies:
|
||||
'@types/hoist-non-react-statics': 3.3.7(@types/react@18.3.28)
|
||||
'@types/react': 18.3.28
|
||||
'@types/hoist-non-react-statics': 3.3.7(@types/react@19.2.14)
|
||||
'@types/react': 19.2.14
|
||||
hoist-non-react-statics: 3.3.2
|
||||
redux: 4.2.1
|
||||
|
||||
'@types/react-router-dom@5.3.3':
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.3.28
|
||||
'@types/react': 19.2.14
|
||||
'@types/react-router': 5.1.20
|
||||
|
||||
'@types/react-router@5.1.20':
|
||||
dependencies:
|
||||
'@types/history': 4.7.11
|
||||
'@types/react': 18.3.28
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@types/react-transition-group@4.4.12(@types/react@18.3.28)':
|
||||
dependencies:
|
||||
'@types/react': 18.3.28
|
||||
|
||||
'@types/react-transition-group@4.4.12(@types/react@19.2.14)':
|
||||
dependencies:
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@types/react-window@1.8.8':
|
||||
dependencies:
|
||||
'@types/react': 18.3.28
|
||||
'@types/react': 19.2.14
|
||||
|
||||
'@types/react@18.3.28':
|
||||
dependencies:
|
||||
'@types/prop-types': 15.7.15
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/react@19.2.14':
|
||||
dependencies:
|
||||
csstype: 3.2.3
|
||||
|
||||
'@types/resolve@1.20.6': {}
|
||||
|
||||
'@types/semver@7.7.1': {}
|
||||
@@ -7051,6 +7206,25 @@ snapshots:
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/coverage-v8@3.2.4(vitest@3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2))':
|
||||
dependencies:
|
||||
'@ampproject/remapping': 2.3.0
|
||||
'@bcoe/v8-coverage': 1.0.2
|
||||
ast-v8-to-istanbul: 0.3.12
|
||||
debug: 4.4.3
|
||||
istanbul-lib-coverage: 3.2.2
|
||||
istanbul-lib-report: 3.0.1
|
||||
istanbul-lib-source-maps: 5.0.6
|
||||
istanbul-reports: 3.2.0
|
||||
magic-string: 0.30.21
|
||||
magicast: 0.3.5
|
||||
std-env: 3.10.0
|
||||
test-exclude: 7.0.2
|
||||
tinyrainbow: 2.0.0
|
||||
vitest: 3.2.4(@types/debug@4.1.12)(@types/node@20.19.37)(jsdom@24.1.3)(msw@2.4.9(typescript@5.6.2))(terser@5.46.0)(yaml@2.8.2)
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
'@vitest/expect@3.2.4':
|
||||
dependencies:
|
||||
'@types/chai': 5.2.3
|
||||
@@ -7370,6 +7544,12 @@ snapshots:
|
||||
dependencies:
|
||||
tslib: 2.8.1
|
||||
|
||||
ast-v8-to-istanbul@0.3.12:
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.31
|
||||
estree-walker: 3.0.3
|
||||
js-tokens: 10.0.0
|
||||
|
||||
astral-regex@2.0.0: {}
|
||||
|
||||
async-function@1.0.0: {}
|
||||
@@ -7620,7 +7800,7 @@ snapshots:
|
||||
parse5: 7.3.0
|
||||
parse5-htmlparser2-tree-adapter: 7.1.0
|
||||
parse5-parser-stream: 7.1.2
|
||||
undici: 7.22.0
|
||||
undici: 7.24.5
|
||||
whatwg-mimetype: 4.0.0
|
||||
|
||||
chokidar@3.6.0:
|
||||
@@ -8534,11 +8714,11 @@ snapshots:
|
||||
|
||||
flat-cache@3.2.0:
|
||||
dependencies:
|
||||
flatted: 3.4.0
|
||||
flatted: 3.4.2
|
||||
keyv: 4.5.4
|
||||
rimraf: 3.0.2
|
||||
|
||||
flatted@3.4.0: {}
|
||||
flatted@3.4.2: {}
|
||||
|
||||
for-each@0.3.5:
|
||||
dependencies:
|
||||
@@ -8873,7 +9053,7 @@ snapshots:
|
||||
dependencies:
|
||||
'@types/html-minifier-terser': 6.1.0
|
||||
html-minifier-terser: 6.1.0
|
||||
lodash: 4.17.23
|
||||
lodash: 4.18.1
|
||||
pretty-error: 4.0.0
|
||||
tapable: 2.3.0
|
||||
optionalDependencies:
|
||||
@@ -9208,6 +9388,8 @@ snapshots:
|
||||
|
||||
js-base64@3.7.8: {}
|
||||
|
||||
js-tokens@10.0.0: {}
|
||||
|
||||
js-tokens@4.0.0: {}
|
||||
|
||||
js-tokens@9.0.1: {}
|
||||
@@ -9326,7 +9508,7 @@ snapshots:
|
||||
|
||||
lodash.truncate@4.4.2: {}
|
||||
|
||||
lodash@4.17.23: {}
|
||||
lodash@4.18.1: {}
|
||||
|
||||
longest-streak@3.1.0: {}
|
||||
|
||||
@@ -9371,13 +9553,13 @@ snapshots:
|
||||
'@types/minimatch': 3.0.5
|
||||
minimatch: 3.1.5
|
||||
|
||||
material-react-table@2.13.3(93149b7a28d7dcf9399e2d03ebc8c990):
|
||||
material-react-table@2.13.3(330725fe5432f245d076f0c0dda1a7a7):
|
||||
dependencies:
|
||||
'@emotion/react': 11.14.0(@types/react@18.3.28)(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1))(@types/react@18.3.28)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@emotion/styled': 11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@18.3.28)(react@18.3.1)
|
||||
'@mui/icons-material': 5.18.0(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@types/react@19.2.14)(react@18.3.1)
|
||||
'@mui/material': 5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@mui/x-date-pickers': 7.29.4(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@mui/material@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@mui/system@5.18.0(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@emotion/styled@11.14.1(@emotion/react@11.14.0(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react@18.3.1))(@types/react@19.2.14)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tanstack/match-sorter-utils': 8.19.4
|
||||
'@tanstack/react-table': 8.20.5(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
'@tanstack/react-virtual': 3.10.6(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
|
||||
@@ -10055,7 +10237,7 @@ snapshots:
|
||||
|
||||
pretty-error@4.0.0:
|
||||
dependencies:
|
||||
lodash: 4.17.23
|
||||
lodash: 4.18.1
|
||||
renderkid: 3.0.0
|
||||
|
||||
pretty-format@27.5.1:
|
||||
@@ -10316,7 +10498,7 @@ snapshots:
|
||||
dependencies:
|
||||
clsx: 2.1.1
|
||||
eventemitter3: 4.0.7
|
||||
lodash: 4.17.23
|
||||
lodash: 4.18.1
|
||||
react: 18.3.1
|
||||
react-dom: 18.3.1(react@18.3.1)
|
||||
react-is: 18.3.1
|
||||
@@ -10392,7 +10574,7 @@ snapshots:
|
||||
css-select: 4.3.0
|
||||
dom-converter: 0.2.0
|
||||
htmlparser2: 6.1.0
|
||||
lodash: 4.17.23
|
||||
lodash: 4.18.1
|
||||
strip-ansi: 6.0.1
|
||||
|
||||
replace-ext@2.0.0: {}
|
||||
@@ -10877,7 +11059,7 @@ snapshots:
|
||||
|
||||
tapable@2.3.0: {}
|
||||
|
||||
tar@7.5.10:
|
||||
tar@7.5.12:
|
||||
dependencies:
|
||||
'@isaacs/fs-minipass': 4.0.1
|
||||
chownr: 3.0.0
|
||||
@@ -11084,7 +11266,7 @@ snapshots:
|
||||
|
||||
undici-types@6.21.0: {}
|
||||
|
||||
undici@7.22.0: {}
|
||||
undici@7.24.5: {}
|
||||
|
||||
unicorn-magic@0.1.0: {}
|
||||
|
||||
|
||||
+2
-16
@@ -1,19 +1,5 @@
|
||||
{
|
||||
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||
"extends": ["config:recommended"],
|
||||
"baseBranches": ["main"],
|
||||
"schedule": ["every weekend"],
|
||||
"prConcurrentLimit": 10,
|
||||
"packageRules": [
|
||||
{
|
||||
"matchManagers": ["npm"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"groupName": "npm minor and patch"
|
||||
},
|
||||
{
|
||||
"matchManagers": ["github-actions"],
|
||||
"matchUpdateTypes": ["minor", "patch"],
|
||||
"groupName": "github-actions minor and patch"
|
||||
}
|
||||
]
|
||||
"extends": ["github>privilegedescalation/.github:renovate-config"]
|
||||
}
|
||||
|
||||
|
||||
Executable
+210
@@ -0,0 +1,210 @@
|
||||
#!/usr/bin/env bash
|
||||
# deploy-e2e-headlamp.sh
|
||||
#
|
||||
# Deploys a stock Headlamp instance with the polaris plugin loaded via
|
||||
# a ConfigMap volume mount. No custom Docker images — the plugin is built
|
||||
# in CI and injected as a ConfigMap.
|
||||
#
|
||||
# E2E resources are deployed to the `privilegedescalation-dev` namespace. Nothing
|
||||
# persists beyond the test run — teardown cleans up all created resources.
|
||||
#
|
||||
# Prerequisites:
|
||||
# - Plugin built (dist/ exists with plugin-main.js + package.json)
|
||||
# - kubectl configured with cluster access
|
||||
# - RBAC applied: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml
|
||||
#
|
||||
# Environment:
|
||||
# E2E_NAMESPACE — namespace for E2E Headlamp (default: privilegedescalation-dev)
|
||||
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
|
||||
# HEADLAMP_VERSION — Headlamp image tag (default: v0.40.1, pinned to match production)
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
DIST_DIR="$REPO_ROOT/dist"
|
||||
|
||||
E2E_NAMESPACE="${E2E_NAMESPACE:-privilegedescalation-dev}"
|
||||
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}"
|
||||
HEADLAMP_VERSION="${HEADLAMP_VERSION:-v0.40.1}"
|
||||
|
||||
if [ ! -d "$DIST_DIR" ]; then
|
||||
echo "ERROR: dist/ not found. Run 'npm run build' first." >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# --- Preflight: verify RBAC before touching the cluster ---
|
||||
echo "Checking RBAC permissions in namespace '${E2E_NAMESPACE}'..."
|
||||
if ! kubectl auth can-i delete configmaps -n "$E2E_NAMESPACE" --quiet 2>/dev/null; then
|
||||
echo "ERROR: Missing RBAC — cannot delete configmaps in namespace '${E2E_NAMESPACE}'." >&2
|
||||
echo " Apply RBAC first: kubectl apply -f deployment/e2e-ci-runner-rbac.yaml" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "=== E2E Headlamp Deployment ==="
|
||||
echo " Image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}"
|
||||
echo " Namespace: $E2E_NAMESPACE"
|
||||
echo " Release: $E2E_RELEASE"
|
||||
|
||||
# --- Create ConfigMap from built plugin ---
|
||||
echo ""
|
||||
echo "Creating ConfigMap with plugin files..."
|
||||
|
||||
# Delete existing ConfigMap if present (idempotent redeploy)
|
||||
kubectl delete configmap headlamp-polaris-plugin \
|
||||
-n "$E2E_NAMESPACE" --ignore-not-found
|
||||
|
||||
# Create ConfigMap from dist/ contents and package.json
|
||||
kubectl create configmap headlamp-polaris-plugin \
|
||||
-n "$E2E_NAMESPACE" \
|
||||
--from-file="$DIST_DIR" \
|
||||
--from-file=package.json="$REPO_ROOT/package.json"
|
||||
|
||||
# --- Tear down any existing E2E deployment for a clean start ---
|
||||
# kubectl apply without prior deletion only patches in-place: if the pod spec is
|
||||
# unchanged between runs, no new rollout is triggered and a degraded pod keeps
|
||||
# serving. Delete first to guarantee a fresh pod regardless of prior state.
|
||||
echo ""
|
||||
echo "Removing any existing E2E deployment (clean-start)..."
|
||||
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
|
||||
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
|
||||
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found --wait
|
||||
|
||||
# --- Deploy Headlamp via kubectl apply ---
|
||||
echo ""
|
||||
echo "Deploying Headlamp E2E instance..."
|
||||
|
||||
kubectl apply -f - <<EOF
|
||||
apiVersion: v1
|
||||
kind: ServiceAccount
|
||||
metadata:
|
||||
name: ${E2E_RELEASE}
|
||||
namespace: ${E2E_NAMESPACE}
|
||||
---
|
||||
apiVersion: apps/v1
|
||||
kind: Deployment
|
||||
metadata:
|
||||
name: ${E2E_RELEASE}
|
||||
namespace: ${E2E_NAMESPACE}
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||
spec:
|
||||
replicas: 1
|
||||
selector:
|
||||
matchLabels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||
template:
|
||||
metadata:
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||
spec:
|
||||
serviceAccountName: ${E2E_RELEASE}
|
||||
automountServiceAccountToken: true
|
||||
securityContext: {}
|
||||
containers:
|
||||
- name: headlamp
|
||||
image: ghcr.io/headlamp-k8s/headlamp:${HEADLAMP_VERSION}
|
||||
imagePullPolicy: IfNotPresent
|
||||
securityContext:
|
||||
runAsNonRoot: true
|
||||
privileged: false
|
||||
runAsUser: 100
|
||||
runAsGroup: 101
|
||||
args:
|
||||
- "-in-cluster"
|
||||
- "-in-cluster-context-name=main"
|
||||
- "-plugins-dir=/headlamp/plugins"
|
||||
ports:
|
||||
- name: http
|
||||
containerPort: 4466
|
||||
protocol: TCP
|
||||
readinessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 5
|
||||
periodSeconds: 5
|
||||
failureThreshold: 6
|
||||
livenessProbe:
|
||||
httpGet:
|
||||
path: /
|
||||
port: http
|
||||
initialDelaySeconds: 10
|
||||
periodSeconds: 10
|
||||
volumeMounts:
|
||||
- name: polaris-plugin
|
||||
mountPath: /headlamp/plugins/headlamp-polaris
|
||||
readOnly: true
|
||||
volumes:
|
||||
- name: polaris-plugin
|
||||
configMap:
|
||||
name: headlamp-polaris-plugin
|
||||
---
|
||||
apiVersion: v1
|
||||
kind: Service
|
||||
metadata:
|
||||
name: ${E2E_RELEASE}
|
||||
namespace: ${E2E_NAMESPACE}
|
||||
labels:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||
spec:
|
||||
type: ClusterIP
|
||||
selector:
|
||||
app.kubernetes.io/name: headlamp
|
||||
app.kubernetes.io/instance: ${E2E_RELEASE}
|
||||
ports:
|
||||
- name: http
|
||||
port: 80
|
||||
targetPort: http
|
||||
protocol: TCP
|
||||
EOF
|
||||
|
||||
echo "Waiting for rollout..."
|
||||
kubectl rollout status "deployment/${E2E_RELEASE}" \
|
||||
-n "$E2E_NAMESPACE" --timeout=120s
|
||||
|
||||
# --- Generate a service URL for tests ---
|
||||
SVC_URL="http://${E2E_RELEASE}.${E2E_NAMESPACE}.svc.cluster.local"
|
||||
|
||||
# --- Wait for DNS and HTTP reachability ---
|
||||
# rollout status only confirms the pod is ready per readinessProbe.
|
||||
# Kubernetes Service DNS may still be propagating to the runner pod.
|
||||
# Poll until the service is reachable over HTTP before handing off.
|
||||
echo ""
|
||||
echo "Waiting for ${SVC_URL} to be reachable..."
|
||||
ATTEMPTS=0
|
||||
MAX_ATTEMPTS=24 # 24 × 5s = 120s max
|
||||
until curl -sf --max-time 5 "${SVC_URL}" -o /dev/null 2>/dev/null; do
|
||||
ATTEMPTS=$((ATTEMPTS + 1))
|
||||
if [ "$ATTEMPTS" -ge "$MAX_ATTEMPTS" ]; then
|
||||
echo "ERROR: ${SVC_URL} not reachable after $((MAX_ATTEMPTS * 5))s" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo " [${ATTEMPTS}/${MAX_ATTEMPTS}] not yet reachable, retrying in 5s..."
|
||||
sleep 5
|
||||
done
|
||||
echo ""
|
||||
echo "E2E Headlamp is ready at: ${SVC_URL}"
|
||||
echo " export HEADLAMP_URL=${SVC_URL}"
|
||||
|
||||
# --- Generate a token for test auth ---
|
||||
echo ""
|
||||
echo "Creating service account token for E2E auth..."
|
||||
kubectl create serviceaccount headlamp-e2e-test \
|
||||
-n "$E2E_NAMESPACE" --dry-run=client -o yaml | kubectl apply -f -
|
||||
|
||||
TOKEN=$(kubectl create token headlamp-e2e-test -n "$E2E_NAMESPACE" --duration=1h 2>/dev/null || echo "")
|
||||
if [ -n "$TOKEN" ]; then
|
||||
echo " export HEADLAMP_TOKEN=<generated>"
|
||||
echo ""
|
||||
echo "HEADLAMP_URL=${SVC_URL}" > "$REPO_ROOT/.env.e2e"
|
||||
echo "HEADLAMP_TOKEN=${TOKEN}" >> "$REPO_ROOT/.env.e2e"
|
||||
echo "Wrote .env.e2e with HEADLAMP_URL and HEADLAMP_TOKEN"
|
||||
else
|
||||
echo " WARNING: Could not generate token. Set HEADLAMP_TOKEN manually or use OIDC."
|
||||
fi
|
||||
|
||||
echo ""
|
||||
echo "E2E deployment complete."
|
||||
Executable
+34
@@ -0,0 +1,34 @@
|
||||
#!/usr/bin/env bash
|
||||
# teardown-e2e-headlamp.sh
|
||||
#
|
||||
# Tears down the dedicated E2E Headlamp instance deployed by deploy-e2e-headlamp.sh.
|
||||
#
|
||||
# Environment:
|
||||
# E2E_NAMESPACE — namespace to clean up (default: privilegedescalation-dev)
|
||||
# E2E_RELEASE — release/resource name prefix (default: headlamp-e2e)
|
||||
set -euo pipefail
|
||||
|
||||
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
|
||||
E2E_NAMESPACE="${E2E_NAMESPACE:-privilegedescalation-dev}"
|
||||
E2E_RELEASE="${E2E_RELEASE:-headlamp-e2e}"
|
||||
|
||||
echo "=== E2E Headlamp Teardown ==="
|
||||
echo " Namespace: $E2E_NAMESPACE"
|
||||
echo " Release: $E2E_RELEASE"
|
||||
|
||||
echo "Removing Headlamp Deployment, Service, and ServiceAccount..."
|
||||
kubectl delete deployment "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
|
||||
kubectl delete service "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
|
||||
kubectl delete serviceaccount "${E2E_RELEASE}" -n "$E2E_NAMESPACE" --ignore-not-found
|
||||
|
||||
echo "Cleaning up ConfigMap..."
|
||||
kubectl delete configmap headlamp-polaris-plugin -n "$E2E_NAMESPACE" --ignore-not-found
|
||||
|
||||
echo "Cleaning up test service account..."
|
||||
kubectl delete serviceaccount headlamp-e2e-test -n "$E2E_NAMESPACE" --ignore-not-found
|
||||
|
||||
# Clean up local env file
|
||||
rm -f "$REPO_ROOT/.env.e2e"
|
||||
|
||||
echo "Teardown complete."
|
||||
@@ -7,6 +7,7 @@ import { makeAuditData, makeResult } from '../test-utils';
|
||||
// Mock Headlamp lib
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: vi.fn() },
|
||||
K8s: {},
|
||||
}));
|
||||
|
||||
vi.mock('@mui/material/styles', () => ({
|
||||
@@ -24,6 +25,15 @@ vi.mock('react-router-dom', () => ({
|
||||
useHistory: () => ({ push: mockPush }),
|
||||
}));
|
||||
|
||||
// Set window.location.pathname for cluster extraction
|
||||
beforeEach(() => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { pathname: '/c/test-cluster/some-page' },
|
||||
writable: true,
|
||||
});
|
||||
mockPush.mockClear();
|
||||
});
|
||||
|
||||
const mockUsePolarisDataContext = vi.fn();
|
||||
vi.mock('../api/PolarisDataContext', () => ({
|
||||
usePolarisDataContext: () => mockUsePolarisDataContext(),
|
||||
@@ -90,7 +100,34 @@ describe('AppBarScoreBadge', () => {
|
||||
expect(button.style.backgroundColor).toBe('rgb(244, 67, 54)');
|
||||
});
|
||||
|
||||
it('navigates to /polaris on click', async () => {
|
||||
it('navigates to /c/<cluster>/polaris on click', async () => {
|
||||
const user = userEvent.setup();
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
Results: {
|
||||
c1: {
|
||||
ID: 'c1',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: true,
|
||||
Severity: 'warning',
|
||||
Category: 'X',
|
||||
},
|
||||
},
|
||||
}),
|
||||
]);
|
||||
mockUsePolarisDataContext.mockReturnValue({ data, loading: false });
|
||||
|
||||
render(<AppBarScoreBadge />);
|
||||
await user.click(screen.getByRole('button'));
|
||||
expect(mockPush).toHaveBeenCalledWith('/c/test-cluster/polaris');
|
||||
});
|
||||
|
||||
it('navigates to /polaris when no cluster in URL', async () => {
|
||||
Object.defineProperty(window, 'location', {
|
||||
value: { pathname: '/settings' },
|
||||
writable: true,
|
||||
});
|
||||
const user = userEvent.setup();
|
||||
const data = makeAuditData([
|
||||
makeResult({
|
||||
|
||||
@@ -4,6 +4,18 @@ import { useHistory } from 'react-router-dom';
|
||||
import { computeScore, countResults } from '../api/polaris';
|
||||
import { usePolarisDataContext } from '../api/PolarisDataContext';
|
||||
|
||||
/**
|
||||
* Extract the cluster name from the current browser URL.
|
||||
* Headlamp cluster routes follow the pattern /c/<cluster>/...
|
||||
* We read window.location.pathname directly because the AppBar renders
|
||||
* outside the cluster route context, so useCluster() returns null and
|
||||
* React Router's useLocation() may not reflect the cluster prefix.
|
||||
*/
|
||||
function getClusterFromUrl(): string | null {
|
||||
const match = window.location.pathname.match(/\/c\/([^/]+)/);
|
||||
return match ? match[1] : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* App bar badge showing cluster Polaris score
|
||||
* Clicking navigates to the overview dashboard
|
||||
@@ -34,7 +46,9 @@ export default function AppBarScoreBadge() {
|
||||
};
|
||||
|
||||
const handleClick = () => {
|
||||
history.push('/polaris');
|
||||
const cluster = getClusterFromUrl();
|
||||
const prefix = cluster ? `/c/${cluster}` : '';
|
||||
history.push(`${prefix}/polaris`);
|
||||
};
|
||||
|
||||
return (
|
||||
|
||||
@@ -0,0 +1,432 @@
|
||||
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
||||
import React from 'react';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { makeResult } from '../test-utils';
|
||||
|
||||
const { mockApiRequest } = vi.hoisted(() => ({ mockApiRequest: vi.fn() }));
|
||||
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib', () => ({
|
||||
ApiProxy: { request: mockApiRequest },
|
||||
}));
|
||||
|
||||
vi.mock('@mui/material/styles', () => ({
|
||||
useTheme: () => ({
|
||||
palette: {
|
||||
primary: { main: '#1976d2', contrastText: '#fff' },
|
||||
action: { disabledBackground: '#e0e0e0', disabled: '#9e9e9e' },
|
||||
divider: '#e0e0e0',
|
||||
},
|
||||
}),
|
||||
}));
|
||||
|
||||
vi.mock('@kinvolk/headlamp-plugin/lib/CommonComponents', () => ({
|
||||
SectionBox: ({ title, children }: { title?: string; children?: React.ReactNode }) => (
|
||||
<div data-testid="section-box" data-title={title}>
|
||||
{children}
|
||||
</div>
|
||||
),
|
||||
StatusLabel: ({ status, children }: { status: string; children?: React.ReactNode }) => (
|
||||
<span data-testid="status-label" data-status={status}>
|
||||
{children}
|
||||
</span>
|
||||
),
|
||||
Dialog: ({
|
||||
open,
|
||||
children,
|
||||
title,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose?: () => void;
|
||||
title?: string;
|
||||
children?: React.ReactNode;
|
||||
}) =>
|
||||
open ? (
|
||||
<div data-testid="dialog" data-title={title}>
|
||||
{children}
|
||||
</div>
|
||||
) : null,
|
||||
}));
|
||||
|
||||
import ExemptionManager from './ExemptionManager';
|
||||
|
||||
const defaultProps = {
|
||||
workloadResult: makeResult(),
|
||||
namespace: 'default',
|
||||
kind: 'Deployment',
|
||||
name: 'my-deploy',
|
||||
};
|
||||
|
||||
const resultWithPodFailures = makeResult({
|
||||
PodResult: {
|
||||
Name: 'pod',
|
||||
Results: {
|
||||
hostIPCSet: {
|
||||
ID: 'hostIPCSet',
|
||||
Message: 'Host IPC is set',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'danger',
|
||||
Category: 'Security',
|
||||
},
|
||||
hostPIDSet: {
|
||||
ID: 'hostPIDSet',
|
||||
Message: 'Host PID is set',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'danger',
|
||||
Category: 'Security',
|
||||
},
|
||||
},
|
||||
ContainerResults: [],
|
||||
},
|
||||
});
|
||||
|
||||
const resultWithContainerFailures = makeResult({
|
||||
PodResult: {
|
||||
Name: 'pod',
|
||||
Results: {},
|
||||
ContainerResults: [
|
||||
{
|
||||
Name: 'container-1',
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: 'CPU requests missing',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
const resultWithIgnoredFailures = makeResult({
|
||||
PodResult: {
|
||||
Name: 'pod',
|
||||
Results: {
|
||||
hostIPCSet: {
|
||||
ID: 'hostIPCSet',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'ignore',
|
||||
Category: 'Security',
|
||||
},
|
||||
},
|
||||
ContainerResults: [],
|
||||
},
|
||||
});
|
||||
|
||||
describe('ExemptionManager', () => {
|
||||
describe('rendering failing checks', () => {
|
||||
it('shows disabled Add Exemption button when no failing checks', () => {
|
||||
render(<ExemptionManager {...defaultProps} />);
|
||||
const btn = screen.getByRole('button', { name: /add exemption/i });
|
||||
expect(btn).toBeDisabled();
|
||||
});
|
||||
|
||||
it('shows enabled Add Exemption button when there are failing checks', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
const btn = screen.getByRole('button', { name: /add exemption/i });
|
||||
expect(btn).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('does not include ignored-severity checks as failing', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithIgnoredFailures} />);
|
||||
const btn = screen.getByRole('button', { name: /add exemption/i });
|
||||
expect(btn).toBeDisabled();
|
||||
});
|
||||
|
||||
it('collects failing checks from pod-level results', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
expect(screen.getByText('Host IPC')).toBeInTheDocument();
|
||||
expect(screen.getByText('Host PID')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('collects failing checks from container-level results', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithContainerFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
expect(screen.getByText('CPU Requests')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('deduplicates checks that appear in multiple containers', () => {
|
||||
const resultWithDuplicate = makeResult({
|
||||
PodResult: {
|
||||
Name: 'pod',
|
||||
Results: {},
|
||||
ContainerResults: [
|
||||
{
|
||||
Name: 'container-1',
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: 'container-2',
|
||||
Results: {
|
||||
cpuRequestsMissing: {
|
||||
ID: 'cpuRequestsMissing',
|
||||
Message: '',
|
||||
Details: [],
|
||||
Success: false,
|
||||
Severity: 'warning',
|
||||
Category: 'Efficiency',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
});
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithDuplicate} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
const items = screen.getAllByText('CPU Requests');
|
||||
expect(items).toHaveLength(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('dialog interactions', () => {
|
||||
it('opens dialog when Add Exemption button is clicked', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
expect(screen.getByTestId('dialog')).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('closes dialog when Cancel button is clicked', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
expect(screen.getByTestId('dialog')).toBeInTheDocument();
|
||||
fireEvent.click(screen.getByRole('button', { name: /cancel/i }));
|
||||
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('toggles individual check selection', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
|
||||
// Find the checkbox next to "Host IPC"
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
// First checkbox is "Exempt from all checks", rest are individual checks
|
||||
const hostIPCCheckbox = checkboxes[1];
|
||||
expect(hostIPCCheckbox).not.toBeChecked();
|
||||
fireEvent.click(hostIPCCheckbox);
|
||||
expect(hostIPCCheckbox).toBeChecked();
|
||||
fireEvent.click(hostIPCCheckbox);
|
||||
expect(hostIPCCheckbox).not.toBeChecked();
|
||||
});
|
||||
|
||||
it('hides individual checks list when exempt-all is toggled', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
expect(screen.getByText('Host IPC')).toBeInTheDocument();
|
||||
|
||||
const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i });
|
||||
fireEvent.click(exemptAllCheckbox);
|
||||
expect(screen.queryByText('Host IPC')).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it('Apply button is disabled when no checks selected and exemptAll is false', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
expect(screen.getByRole('button', { name: /apply/i })).toBeDisabled();
|
||||
});
|
||||
|
||||
it('Apply button is enabled when exemptAll is checked', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
const exemptAllCheckbox = screen.getByRole('checkbox', { name: /exempt from all checks/i });
|
||||
fireEvent.click(exemptAllCheckbox);
|
||||
expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled();
|
||||
});
|
||||
|
||||
it('Apply button is enabled when at least one individual check is selected', () => {
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
const checkboxes = screen.getAllByRole('checkbox');
|
||||
fireEvent.click(checkboxes[1]); // select first individual check
|
||||
expect(screen.getByRole('button', { name: /apply/i })).not.toBeDisabled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('ApiProxy.request calls', () => {
|
||||
it('patches with exempt-all annotation when exemptAll is selected', async () => {
|
||||
mockApiRequest.mockResolvedValue({});
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||
'/apis/apps/v1/namespaces/default/deployments/my-deploy',
|
||||
expect.objectContaining({
|
||||
method: 'PATCH',
|
||||
headers: { 'Content-Type': 'application/strategic-merge-patch+json' },
|
||||
body: JSON.stringify({
|
||||
metadata: {
|
||||
annotations: { 'polaris.fairwinds.com/exempt': 'true' },
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('patches with per-check annotations when individual checks selected', async () => {
|
||||
mockApiRequest.mockResolvedValue({});
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
// Select first check (hostIPCSet)
|
||||
fireEvent.click(screen.getAllByRole('checkbox')[1]);
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||
'/apis/apps/v1/namespaces/default/deployments/my-deploy',
|
||||
expect.objectContaining({
|
||||
method: 'PATCH',
|
||||
body: JSON.stringify({
|
||||
metadata: {
|
||||
annotations: { 'polaris.fairwinds.com/hostIPCSet-exempt': 'true' },
|
||||
},
|
||||
}),
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses core API path for Pod kind (no api group)', async () => {
|
||||
mockApiRequest.mockResolvedValue({});
|
||||
render(
|
||||
<ExemptionManager {...defaultProps} kind="Pod" workloadResult={resultWithPodFailures} />
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||
'/api/v1/namespaces/default/pods/my-deploy',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses batch API group for Job kind', async () => {
|
||||
mockApiRequest.mockResolvedValue({});
|
||||
render(
|
||||
<ExemptionManager {...defaultProps} kind="Job" workloadResult={resultWithPodFailures} />
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||
'/apis/batch/v1/namespaces/default/jobs/my-deploy',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses batch API group for CronJob kind', async () => {
|
||||
mockApiRequest.mockResolvedValue({});
|
||||
render(
|
||||
<ExemptionManager {...defaultProps} kind="CronJob" workloadResult={resultWithPodFailures} />
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||
'/apis/batch/v1/namespaces/default/cronjobs/my-deploy',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('uses apps API group for StatefulSet kind', async () => {
|
||||
mockApiRequest.mockResolvedValue({});
|
||||
render(
|
||||
<ExemptionManager
|
||||
{...defaultProps}
|
||||
kind="StatefulSet"
|
||||
workloadResult={resultWithPodFailures}
|
||||
/>
|
||||
);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(mockApiRequest).toHaveBeenCalledWith(
|
||||
'/apis/apps/v1/namespaces/default/statefulsets/my-deploy',
|
||||
expect.anything()
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('feedback states', () => {
|
||||
it('shows success feedback and closes dialog after successful apply', async () => {
|
||||
mockApiRequest.mockResolvedValue({});
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||
const label = screen.getByTestId('status-label');
|
||||
expect(label).toHaveAttribute('data-status', 'success');
|
||||
expect(label).toHaveTextContent('Exemptions applied successfully');
|
||||
});
|
||||
});
|
||||
|
||||
it('shows error feedback and keeps dialog closed after failed apply', async () => {
|
||||
mockApiRequest.mockRejectedValue(new Error('403 Forbidden'));
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
await waitFor(() => {
|
||||
const label = screen.getByTestId('status-label');
|
||||
expect(label).toHaveAttribute('data-status', 'error');
|
||||
expect(label).toHaveTextContent(/failed to apply exemptions/i);
|
||||
});
|
||||
});
|
||||
|
||||
it('shows "Applying..." text on Apply button while in-flight', async () => {
|
||||
let resolveRequest!: () => void;
|
||||
mockApiRequest.mockReturnValue(
|
||||
new Promise<void>(res => {
|
||||
resolveRequest = res;
|
||||
})
|
||||
);
|
||||
|
||||
render(<ExemptionManager {...defaultProps} workloadResult={resultWithPodFailures} />);
|
||||
fireEvent.click(screen.getByRole('button', { name: /add exemption/i }));
|
||||
fireEvent.click(screen.getByRole('checkbox', { name: /exempt from all checks/i }));
|
||||
fireEvent.click(screen.getByRole('button', { name: /apply/i }));
|
||||
|
||||
expect(screen.getByRole('button', { name: /applying/i })).toBeInTheDocument();
|
||||
resolveRequest();
|
||||
await waitFor(() => {
|
||||
expect(screen.queryByTestId('dialog')).not.toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
+1
-1
@@ -99,7 +99,7 @@ registerRoute({
|
||||
});
|
||||
|
||||
// Register plugin settings
|
||||
registerPluginSettings('polaris', PolarisSettings, true);
|
||||
registerPluginSettings('headlamp-polaris', PolarisSettings, true);
|
||||
|
||||
// Register details view section for supported controller types
|
||||
registerDetailsViewSection(({ resource }) => {
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"extends": "@kinvolk/headlamp-plugin/config/plugins-tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"types": ["vite/client", "vite-plugin-svgr/client", "vitest/globals", "@testing-library/jest-dom"]
|
||||
"types": ["vitest/globals", "@testing-library/jest-dom"]
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
|
||||
@@ -9,5 +9,16 @@ export default defineConfig({
|
||||
environment: 'jsdom',
|
||||
setupFiles: ['./vitest.setup.ts'],
|
||||
exclude: ['e2e/**', 'node_modules/**'],
|
||||
coverage: {
|
||||
provider: 'v8',
|
||||
include: ['src/**/*.{ts,tsx}'],
|
||||
exclude: ['src/**/*.test.{ts,tsx}', 'src/test-utils.tsx', 'src/index.tsx'],
|
||||
thresholds: {
|
||||
lines: 80,
|
||||
functions: 80,
|
||||
branches: 80,
|
||||
statements: 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user