7 Commits

21 changed files with 492 additions and 686 deletions

View File

@@ -4,5 +4,5 @@ TRAEFIK_NETWORK=internal
TRAEFIK_CERT_RESOLVER=netcup
# Keep these two values paired when deploying a newer published toolbox release.
TOOLBOX_RELEASE_VERSION=0.4.3
TOOLBOX_RELEASE_SHA256=0c26315c987f7ef6d025c66c21e25999786ede3e971b66495c856117e0b7c522
TOOLBOX_RELEASE_VERSION=0.9.0
TOOLBOX_RELEASE_SHA256=fa3c5b8a4148dbb360acc04fd505a6fa64e06c5c842cf1334af5c7d232208f3c

View File

@@ -2,8 +2,8 @@ ARG NGINX_IMAGE=nginxinc/nginx-unprivileged:1.31.3-alpine@sha256:18d67281256ded3
FROM ${NGINX_IMAGE} AS release
ARG TOOLBOX_RELEASE_VERSION=0.4.3
ARG TOOLBOX_RELEASE_SHA256=0c26315c987f7ef6d025c66c21e25999786ede3e971b66495c856117e0b7c522
ARG TOOLBOX_RELEASE_VERSION=0.9.0
ARG TOOLBOX_RELEASE_SHA256=fa3c5b8a4148dbb360acc04fd505a6fa64e06c5c842cf1334af5c7d232208f3c
ARG TOOLBOX_RELEASE_BASE_URL=https://git.add-ideas.de/zemion/toolbox-portal/releases/download
RUN set -eu; \
@@ -21,6 +21,8 @@ RUN set -eu; \
test -f /tmp/toolbox/apps/pdf/toolbox-app.json; \
test -f /tmp/toolbox/apps/xslt/toolbox-app.json; \
test -f /tmp/toolbox/apps/onenote/toolbox-app.json; \
test -f /tmp/toolbox/apps/av/toolbox-app.json; \
test -f /tmp/toolbox/apps/regex/toolbox-app.json; \
rm "/tmp/${archive}"
FROM ${NGINX_IMAGE}

126
README.md
View File

@@ -1,7 +1,7 @@
# add·ideas Toolbox Portal
`toolbox-portal` is the static launcher and release assembler for the add·ideas
browser toolbox. Version `0.2.3` reads one same-origin catalogue, shows each app
browser toolbox. Version `0.2.8` reads one same-origin catalogue, shows each app
as a compact launch tile, and keeps personal pins, drag-and-drop ordering,
visibility, and appearance in the current browser. Pinned tools have their own
section; search, category, and clickable tag filters stay close to the tool
@@ -36,9 +36,10 @@ the SDK and portal together.
Vite uses `base: './'`, so the built portal works at `/` or a nested static path.
The development catalogue at `public/toolbox.catalog.json` points to assembled
paths (`./apps/pdf/`, `./apps/xslt/`, and `./apps/onenote/`). Those manifests
will correctly appear as unavailable until an assembled release is being
served.
paths (`./apps/pdf/`, `./apps/xslt/`, `./apps/onenote/`, `./apps/av/`, and
`./apps/regex/`).
Those manifests will correctly appear as unavailable until an assembled release
is being served.
## Catalogue and launches
@@ -68,7 +69,7 @@ privacy and executable-code warning. Light, dark, and system modes are supported
## Production deployment behind Traefik
The committed `compose.yaml` is the shortest production path. Its release
container downloads the immutable Toolbox 0.4.3 ZIP during the image build,
container downloads the immutable Toolbox 0.9.0 ZIP during the image build,
verifies SHA-256 before extracting it, and then copies only the verified static
files into the pinned unprivileged nginx image. Node.js and a local portal
assembly are not required on the deployment host.
@@ -77,7 +78,7 @@ Prerequisites:
- Docker with the Compose plugin;
- Traefik attached to the existing external Docker network `internal`;
- a Traefik HTTPS entrypoint named `websecure`;
- Traefik HTTP and HTTPS entrypoints named `web` and `websecure`;
- a certificate resolver named `netcup`;
- DNS for `toolbox.add-ideas.de` pointing to that Traefik instance; and
- outbound HTTPS access to `git.add-ideas.de` while the image is built.
@@ -91,8 +92,9 @@ docker compose up -d --build
docker compose ps
```
No host port is published. Traefik routes
`https://toolbox.add-ideas.de` to nginx on the shared network at port 8080 and
No host port is published. Traefik permanently redirects
`http://toolbox.add-ideas.de` to HTTPS, routes
`https://toolbox.add-ideas.de` to nginx on the shared network at port 8080, and
obtains its TLS certificate through `netcup`. The hostname, network, resolver,
release version, and matching checksum can be overridden through environment
variables; copy `.env.example` to `.env` only when an override is needed. For
@@ -119,9 +121,9 @@ The app release should also publish a matching `.sha256` sidecar. The manifest
must declare the pinned reverse-DNS id and version. Application and portal
versions are independent.
`release/toolbox.lock.json` is the reviewed v0.4.3 lock. Its URLs and checksums
pin the published PDF Tools 0.4.3, XSLT Tools 0.4.2, and OneNote Tools 0.3.3
release bytes. Use
`release/toolbox.lock.json` is the reviewed v0.9.0 lock. Its URLs and checksums
pin the published PDF Tools 0.4.3, XSLT Tools 0.4.2, OneNote Tools 0.3.3,
Audio & Video Tools 0.2.0, and Regex Tools 0.4.0 release bytes. Use
`release/toolbox.lock.example.json` as the template for a future release and
verify every downloaded asset before committing updated values. Artifacts may be:
@@ -142,7 +144,7 @@ npm run assemble -- \
--lock release/toolbox.lock.json \
--portal-dist dist \
--output build/toolbox \
--archive build/add-ideas-toolbox-0.4.3.zip
--archive build/add-ideas-toolbox-0.9.0.zip
```
Existing output is refused. Pass `--force` only when replacing those exact
@@ -160,10 +162,12 @@ build/toolbox/
└── apps/
├── pdf/
├── xslt/
── onenote/
── onenote/
├── av/
└── regex/
build/add-ideas-toolbox-0.4.3.zip
build/add-ideas-toolbox-0.4.3.zip.sha256
build/add-ideas-toolbox-0.9.0.zip
build/add-ideas-toolbox-0.9.0.zip.sha256
```
The assembler verifies SHA-256 before opening an artifact, validates every app
@@ -184,16 +188,17 @@ directory separately:
```sh
npm run package:static -- \
--input build/toolbox \
--output artifacts/add-ideas-toolbox-0.4.3.zip
--output artifacts/add-ideas-toolbox-0.9.0.zip
```
This also emits a `.sha256` sidecar.
## Local assembled container and publication
`Containerfile` serves only the assembled files with unprivileged nginx on port 8080. It adds restrictive browser headers, same-origin isolation, immutable
caching only for Vite-hashed assets, and revalidation for all other files.
Assemble first, then:
`Containerfile` serves only the assembled files with unprivileged nginx on port 8080. It adds restrictive browser headers, same-origin isolation, explicit
`application/wasm`, immutable caching only for Vite-hashed assets and narrowly
matched semver-versioned FFmpeg core files, and revalidation for all other
files. Assemble first, then:
The packaged policy intentionally does not grant CSP `unsafe-eval`. SaxonJS's
`ixsl:eval()` extension is therefore unsupported in this deployment profile;
@@ -201,9 +206,9 @@ normal local XML/XSLT transformations do not require that permission.
```sh
podman build -f Containerfile \
-t git.add-ideas.de/zemion/toolbox:0.4.3 .
-t git.add-ideas.de/zemion/toolbox:0.9.0 .
podman run --rm -p 8080:8080 \
git.add-ideas.de/zemion/toolbox:0.4.3
git.add-ideas.de/zemion/toolbox:0.9.0
```
For a direct-port local deployment after assembly, use the separate example:
@@ -220,8 +225,8 @@ docker login git.add-ideas.de
docker buildx build \
--platform linux/amd64,linux/arm64 \
--file Containerfile.release \
--tag git.add-ideas.de/zemion/toolbox:0.4.3 \
--tag git.add-ideas.de/zemion/toolbox:0.4 \
--tag git.add-ideas.de/zemion/toolbox:0.9.0 \
--tag git.add-ideas.de/zemion/toolbox:0.9 \
--push .
```
@@ -232,9 +237,9 @@ notes.
## Manual Gitea publication checklist
No credentials belong in this repository. Before the first release, create the
`zemion/toolbox-portal` Gitea repository and grant the workstation or runner
permission to push code, releases, and container packages.
No credentials belong in this repository. The publishing workstation or runner
needs permission to push code, releases, and container packages to
`zemion/toolbox-portal`.
1. Run `npm ci`, `npm test`, `npm run lint`, and `npm run build` from a clean
checkout of the intended portal tag.
@@ -243,11 +248,11 @@ permission to push code, releases, and container packages.
3. Verify downloaded app assets with `sha256sum -c <asset>.sha256`; copy those
exact values into a reviewed toolbox lock.
4. Run the assembler and serve `build/toolbox` from a nested test path. Open
all three apps, switch between them, and confirm the encoded `toolbox`
all five apps, switch between them, and confirm the encoded `toolbox`
context.
5. Verify the distribution with
`(cd build && sha256sum -c add-ideas-toolbox-0.4.3.zip.sha256)`.
6. Commit the reviewed lock, tag the toolbox release (for example `v0.4.3`), and
`(cd build && sha256sum -c add-ideas-toolbox-0.9.0.zip.sha256)`.
6. Commit the reviewed lock, tag the toolbox release (for example `v0.9.0`), and
push the branch and tag to Gitea.
7. In Gitea, open **Releases → New release**, select the tag, and upload the
static ZIP plus its `.sha256` file. Do not use a mutable “latest” URL in a
@@ -263,59 +268,66 @@ must not re-resolve app versions or substitute a newer release.
## Existing tools
| Tool | State and boundary | Principal workflows | Important concrete scope | Critical considerations and integrations |
| ------------------- | -------------------------------------------------- | ---------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
| ------------------- | --------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`pdf-tools`** | Existing; dedicated repository | Merge, split, reorder, rotate and export PDF pages | Thumbnail workspace; multi-document operations; ZIP and PDF output; saved local workspace | Workers and IndexedDB; large-document memory control; future signature inspection through `crypto-tools`; metadata and safe-sharing through `privacy-tools` |
| **`xslt-tools`** | Existing; dedicated repository | Develop, test and run XSLT transformations | XML/XSLT editors; transformation results; local files; saved projects; validation and diagnostics | Lazy SaxonJS loading; relocatable assets; useful handoffs to `data-tools`, `schema-tools` and `diff-tools` |
| **`onenote-tools`** | Existing rich-reader release; dedicated repository | Open, browse, inspect and export OneNote sections and packages locally | Desktop/unfragmented FSSHTTPB `.one`/`.onetoc2`; none/LZX/MSZIP/Quantum and multi-cabinet `.onepkg`; trees, rich text, images, tables, ink, attachments and export | Native TypeScript worker with no Wasm; bounded parsing, rendering and export; fragmented FSSHTTP fails safely; no editing or pixel-perfect fidelity |
| **`av-tools`** | Existing local-first v0.2.0 release; dedicated repository | Convert and lightly edit audio and video locally in the browser | Quick conversion; trim, split and crossfade concatenate; crop, resize, normalize, fades, waveform, metadata, chapters, subtitles, scene thumbnails/contact sheets, presets and export | Reviewed ffmpeg.wasm 0.12.10 ST/MT build with verified Opus and bundled label font; bounded queue, temporary storage and resource policy; isolation enables MT with automatic ST fallback; versioned core assets are immutable |
| **`regex-tools`** | Current local-first v0.4.0 release; dedicated repository | Develop, explain, test and apply JavaScript, PCRE2, PHP, Perl, Python, Ruby, Java, C++, Go, .NET, Rust and Scala/JVM-compatible regular expressions | Deterministic syntax trees; engine-native matching, captures and bounded replacement; PCRE2 tracing and C17 generation; comparison; corpus apply; tests; risk/growth/benchmark analysis; generated cases; bounded minimization; validated ECMAScript formatting; project import/export and optional local persistence | Open-source regexpp and pinned local WebAssembly runtimes for PCRE2, PHP preg, legacy Perl, CPython, CRuby, TeaVM Java/Scala compatibility, libc++ C++, Go, .NET and Rust in killable workers; explicit source/licence inventories, resource limits and engine-specific offset semantics |
## Planned tools
| Tool | State and boundary | Principal workflows | Important concrete scope | Critical considerations and integrations |
| -------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`svg-tools`** | Planned; dedicated repository | Source and visual SVG editing | Synchronized DOM tree, code and canvas; optimization; path simplification; symbols; accessibility; animation; sanitization | SVG is potentially active content; previews require sanitization or sandboxing; integrate with `colour-tools`, `token-tools`, `image-tools` and `label-tools` |
| **`regex-tools`** | Planned; dedicated repository | Develop, test and apply regular expressions | Corpus mode; replacement preview; capture table; flavour comparison; performance visualization; generated test cases; failing-input minimization | Never claim exact equivalence across regex engines without qualification; bound execution; integrate with `text-tools`, `log-tools` and `minimize-tools` |
| **`colour-tools`** | Planned; small-to-medium dedicated repository | Colour conversion, calculation and palette work | sRGB, Display P3, Lab, LCH, OKLab and OKLCH; gamut mapping; contrast; colour-vision simulation; gradients; palette extraction; design-token export | Make colour-space assumptions explicit; distinguish mathematical conversion from display simulation; integrate with SVG, image and token tools |
| **`rand-tools`** | Planned; small dedicated repository | Generate random numbers, strings, identifiers and samples | Cryptographically secure generation; deterministic seeded generation; UUIDs, ULIDs, passphrases, dice notation, distributions, weighted selection and shuffling | Clearly separate secure randomness from reproducible pseudorandomness; no misleading “strength” claims; natural foundation for `fixture-tools` |
| **`barcode-tools`** | Planned; dedicated repository | Generate, inspect and decode QR and barcodes | QR, common one- and two-dimensional codes; camera/image decoding; GS1 assistance; CSV batch generation; quiet-zone and print-size checks; structured payload builders | Treat decoded payloads as untrusted; never open links automatically; camera permission must be optional; integrate with `label-tools`, `contact-tools` and `crypto-tools` |
| **`av-tools`** | Planned; large dedicated repository | Light audio and video editing and transcoding | Trim, split, concatenate, crop, resize, normalize, fades, waveform, metadata, chapters, subtitles, thumbnail/contact-sheet generation and export presets | Heavy WASM and worker workloads; temporary-file storage; configurable memory limits; possibly special cross-origin-isolation headers; integrate with `subtitle-tools` and `image-tools` |
| **`crypto-tools`** | Planned; dedicated repository | Inspect keys, certificates and signatures; validate chains | X.509 PEM/DER, CSR, CRL, PKCS #8, encrypted PKCS #8, PKCS #12/PFX, JWK/JWKS; key/certificate matching; path construction; hostname, purpose and time checks; CMS/JWS later | Inspection-first; explicit trust anchors; no implication that browser/OS trust stores are used; no private-key persistence; offline revocation first; network checks opt-in; restrictive CSP and worker isolation |
| **`data-tools`** | High priority; dedicated repository | Inspect, edit, validate, query and convert structured data | JSON, YAML, TOML, XML, CSV and NDJSON; tree/table/source views; schema inference; flattening; filtering; field mapping; JSONPath-style queries; conversion recipes | Every conversion should disclose information loss, coercion and round-trip instability; XML-specialist workflows hand off to `xslt-tools` |
| **`image-tools`** | High priority; dedicated repository | Edit and batch-process raster images | Crop, resize, rotate, compress, convert, contact sheets, sprites, responsive sets, favicon generation, metadata removal and visual quality comparison | Use workers and off-main-thread rendering; preserve or deliberately remove colour profiles; integrate with colour, SVG, PDF and privacy tools |
| **`diff-tools`** | High priority; dedicated repository or shared engine plus UI | Compare documents and files semantically | Text; JSON object-aware comparison; XML normalization; CSV keyed comparison; images with overlay/heatmap; archives and directories; PDF pages; optional audio waveform comparison | Normalization and ignored properties must be visible; produce portable reports and standard patch formats where possible |
| **`file-tools`** | High priority; dedicated repository | Identify, inspect and organize local files | Magic-byte detection; hex view; decoded strings; checksums; directory manifests; split/join; duplicate detection; trailing-data detection; batch-rename preview | All inputs are untrusted; format inspectors require strict size and nesting limits; natural entry point to archive, privacy, crypto and package tools |
| **`archive-tools`** | Medium-to-high priority; dedicated repository | Inspect, create, compare and extract archives | ZIP, TAR, gzip and selected additional formats; selective extraction; content preview; deterministic archives; timestamp normalization; archive comparison | Expansion-ratio, entry-count and total-size limits; traversal-safe extraction; never execute extracted files |
| **`text-tools`** | Medium priority; one app with deep-linked panels | Transform and normalize plain text | Unicode normalization; line-ending conversion; encoding; sorting; deduplication; case changes; transliteration; escaping; column operations; transformation pipelines | Preserve exact input/output visibility; warn about lossy encodings and Unicode confusables; integrate regex operations |
| **`query-tools`** | High-value but large; dedicated repository | Analyse local tabular and relational data with SQL | CSV, JSON, NDJSON, Parquet and SQLite; schema inference; joins; aggregation; pivots; charts; export to common data formats | WASM memory and streaming; distinguish this analytical product from document-oriented `data-tools`; query work must remain local |
| **`privacy-tools`** | High priority; dedicated repository | Inspect and remove metadata before sharing | EXIF and GPS; document author/comments; embedded thumbnails; PDF metadata and attachments; hidden package entries; AV tags; personal filenames; structured safe-sharing report | Never promise absolute anonymity; explicitly state inspected, removed, preserved and unsupported structures; integrate with most file-oriented apps |
| **`time-tools`** | Medium priority; small dedicated app or helper family | Work with dates, timestamps, time zones and recurrences | Unix timestamps; date arithmetic; durations; ISO 8601; timezone comparison; cron; RRULE; ICS generation; business days; DST-transition visualization | Visualize actual recurrence instances and ambiguous/skipped local times; do not rely on simplified fixed-offset calculations |
| **`subtitle-tools`** | Medium priority; dedicated repository | Edit, validate and synchronize captions | SRT, WebVTT and ASS; timing shifts; stretching; frame-rate conversion; overlap and reading-speed checks; waveform synchronization; revision comparison | Direct integration with `av-tools`; preserve style information when supported and disclose conversion losses |
| **`font-tools`** | Medium priority; dedicated repository | Inspect, preview and prepare fonts | Glyph coverage; variable axes; metadata; fallback comparison; subsetting; CSS generation; arbitrary-text previews | Font licensing and embedding restrictions must be visible; use untrusted-font isolation and strict parser limits |
| **`label-tools`** | Medium priority; dedicated repository | Generate labels and badges from templates and data | CSV/JSON merge into SVG templates; QR/barcode fields; serial numbers; A4 label sheets; badges; asset tags; cut marks; calibration | Strong suite demonstration linking data, SVG, barcode, random and PDF capabilities; print dimensions must be explicit and testable |
| -------------------- | --------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| **`flow-tools`** | Later platform layer | Compose local processing operations across tools | Example: CSV → filter → QR generation → SVG template → PDF → ZIP; reusable recipes; typed inputs and outputs; progress and cancellation | Do not load complete React applications into one runtime; expose separate worker-safe operation packages only after several apps have stable operations |
| **`file-tools`** | High priority; dedicated repository | Identify, inspect and organize local files | Magic-byte detection; hex view; decoded strings; checksums; directory manifests; split/join; duplicate detection; trailing-data detection; batch-rename preview | All inputs are untrusted; format inspectors require strict size and nesting limits; natural entry point to archive, privacy, crypto and package tools |
| **`svg-tools`** | Planned; dedicated repository | Source and visual SVG editing | Synchronized DOM tree, code and canvas; optimization; path simplification; symbols; accessibility; animation; sanitization | SVG is potentially active content; previews require sanitization or sandboxing; integrate with `colour-tools`, `token-tools`, `image-tools` and `label-tools` |
| **`image-tools`** | High priority; dedicated repository | Edit and batch-process raster images | Crop, resize, rotate, compress, convert, contact sheets, sprites, responsive sets, favicon generation, metadata removal and visual quality comparison | Use workers and off-main-thread rendering; preserve or deliberately remove colour profiles; integrate with colour, SVG, PDF and privacy tools |
| **`subtitle-tools`** | Medium priority; dedicated repository | Edit, validate and synchronize captions | SRT, WebVTT and ASS; timing shifts; stretching; frame-rate conversion; overlap and reading-speed checks; waveform synchronization; revision comparison | Direct integration with `av-tools`; preserve style information when supported and disclose conversion losses |
| **`midi-tools`** | Medium specialist app | MIDI event inspection and editing; transpose, quantize, tempo maps and controller data; device access should remain optional | |
| **`3d-tools`** | Large specialist app | Inspect STL, OBJ and glTF; dimensions, bounding boxes, mesh simplification and common geometry defects; worker/GPU use and file limits | |
| **`query-tools`** | High-value but large; dedicated repository | Analyse local tabular and relational data with SQL | CSV, JSON, NDJSON, Parquet and SQLite; schema inference; joins; aggregation; pivots; charts; export to common data formats | WASM memory and streaming; distinguish this analytical product from document-oriented `data-tools`; query work must remain local |
| **`epub-tools`** | Medium app | EPUB inspection, metadata and cover editing, link validation, chapter extraction and structural repair; sanitize embedded HTML and SVG | |
| **`scan-tools`** | Large app | Camera/document correction; crop, deskew and threshold; page assembly; local OCR; PDF output; model downloads and memory use must be explicit | |
| **`office-tools`** | Medium-to-large app | Inspect rather than reproduce an office suite: package structure, relationships, metadata, comments, embedded images and basic repairs | |
| **`package-tools`** | Medium priority | Inspect compound and package-based formats | EPUB; DOCX/XLSX/PPTX; ODF; JAR; APK; browser extensions; package trees; manifests; relationships; embedded media; signatures; orphaned parts | Generic container inspector with format adapters; complements rather than replaces `onenote-tools`, `epub-tools` and `office-tools` |
| **`archive-tools`** | Medium-to-high priority; dedicated repository | Inspect, create, compare and extract archives | ZIP, TAR, gzip and selected additional formats; selective extraction; content preview; deterministic archives; timestamp normalization; archive comparison | Expansion-ratio, entry-count and total-size limits; traversal-safe extraction; never execute extracted files |
| **`privacy-tools`** | High priority; dedicated repository | Inspect and remove metadata before sharing | EXIF and GPS; document author/comments; embedded thumbnails; PDF metadata and attachments; hidden package entries; AV tags; personal filenames; structured safe-sharing report | Never promise absolute anonymity; explicitly state inspected, removed, preserved and unsupported structures; integrate with most file-oriented apps |
| **`crypto-tools`** | Planned; dedicated repository | Inspect keys, certificates and signatures; validate chains | X.509 PEM/DER, CSR, CRL, PKCS #8, encrypted PKCS #8, PKCS #12/PFX, JWK/JWKS; key/certificate matching; path construction; hostname, purpose and time checks; CMS/JWS later | Inspection-first; explicit trust anchors; no implication that browser/OS trust stores are used; no private-key persistence; offline revocation first; network checks opt-in; restrictive CSP and worker isolation |
| **`barcode-tools`** | Planned; dedicated repository | Generate, inspect and decode QR and barcodes | QR, common one- and two-dimensional codes; camera/image decoding; GS1 assistance; CSV batch generation; quiet-zone and print-size checks; structured payload builders | Treat decoded payloads as untrusted; never open links automatically; camera permission must be optional; integrate with `label-tools`, `contact-tools` and `crypto-tools` |
| **`label-tools`** | Medium priority; dedicated repository | Generate labels and badges from templates and data | CSV/JSON merge into SVG templates; QR/barcode fields; serial numbers; A4 label sheets; badges; asset tags; cut marks; calibration | Strong suite demonstration linking data, SVG, barcode, random and PDF capabilities; print dimensions must be explicit and testable |
| **`font-tools`** | Medium priority; dedicated repository | Inspect, preview and prepare fonts | Glyph coverage; variable axes; metadata; fallback comparison; subsetting; CSS generation; arbitrary-text previews | Font licensing and embedding restrictions must be visible; use untrusted-font isolation and strict parser limits |
| **`fixture-tools`** | Medium priority | Generate deterministic test data | JSON Schema, XSD, SQL DDL, CSV headings or interactive models to JSON, CSV, XML, SQL and NDJSON; foreign keys; distributions; boundary and invalid cases | Seeded reproducibility; uniqueness constraints; privacy-safe synthetic data; natural extension of `rand-tools` |
| **`minimize-tools`** | Distinctive specialist tool | Reduce a failing input while preserving a failure | Minimize XML triggering an XSLT error; shortest regex pathological input; smallest JSON schema failure; selectable failure predicates | Expensive repeated execution must be bounded and cancellable; record the exact predicate and transformation versions |
| **`format-lab`** | Later, distinctive product | Explore conversion paths and information loss | Format graph; round-trip tests; property preservation; type coercion; metadata loss; recommended path selection across data, image, AV and subtitle formats | No conversion should be described as lossless without testing relevant properties |
| **`repro-tools`** | Medium-to-later | Create manifests and provenance records | File hashes; directory manifests; operation history; tool/version/parameter records; deterministic package creation; verification reports | Useful foundation for reproducible workflows and signed manifests; integrates with crypto, archive and flow tools |
| **`package-tools`** | Medium priority | Inspect compound and package-based formats | EPUB; DOCX/XLSX/PPTX; ODF; JAR; APK; browser extensions; package trees; manifests; relationships; embedded media; signatures; orphaned parts | Generic container inspector with format adapters; complements rather than replaces `onenote-tools`, `epub-tools` and `office-tools` |
| **`schema-tools`** | Dedicated specialist app | Validate, inspect and generate examples for JSON Schema, XSD, Relax NG, Schematron and OpenAPI; visualize references and incompatibilities | |
| **`log-tools`** | Dedicated app | Stream and filter large logs; parse common formats; correlate records; build timelines; extract fields; anonymize values; avoid loading the complete file into memory | |
| **`network-tools`** | Helper family or small app | IPv4/IPv6 subnetting; CIDR ranges; URL and query parsing; DNS-record construction; HTTP headers; CSP generation; MIME lookup; avoid turning it into an intrusive scanner | |
| **`binary-tools`** | Dedicated specialist app | Base64, hexadecimal, CBOR, MessagePack, ASN.1 and Protocol Buffers; schema-assisted decoding; byte-range highlighting; strict depth and size limits | |
| **`git-tools`** | Small-to-medium app | Patch inspection and editing; `.gitignore` testing; semantic-version comparison; conventional commits; changelog normalization; no repository-hosting dependency | |
| **`api-tools`** | Medium app | OpenAPI validation; request and response examples; curl and client-command generation; saved HTTP-exchange inspection; direct browser requests remain subject to CORS | |
| **`mail-tools`** | Medium app | EML and MIME inspection; header analysis; attachment extraction; body-part comparison; HTML mail must be heavily sandboxed | |
| **`calendar-tools`** | Small-to-medium app | ICS inspection, merging, deduplication, repair, recurrence visualization and timezone diagnostics | |
| **`contact-tools`** | Small-to-medium app | vCard inspection and editing; CSV mapping; deduplication; contact QR generation; treat all contact data as sensitive | |
| **`mail-tools`** | Medium app | EML and MIME inspection; header analysis; attachment extraction; body-part comparison; HTML mail must be heavily sandboxed | |
| **`epub-tools`** | Medium app | EPUB inspection, metadata and cover editing, link validation, chapter extraction and structural repair; sanitize embedded HTML and SVG | |
| **`scan-tools`** | Large app | Camera/document correction; crop, deskew and threshold; page assembly; local OCR; PDF output; model downloads and memory use must be explicit | |
| **`office-tools`** | Medium-to-large app | Inspect rather than reproduce an office suite: package structure, relationships, metadata, comments, embedded images and basic repairs | |
| **`geo-tools`** | Medium app | GeoJSON, GPX, KML and coordinate CSV; format conversion; track simplification; distance/elevation analysis; coordinate-reference assumptions must be explicit | |
| **`diagram-tools`** | Medium app | Code and visual editing for Mermaid, Graphviz and related representations; renderers require sanitization and sandboxing | |
| **`token-tools`** | Small-to-medium app | Design-token editing and conversion between JSON, CSS custom properties, Sass, Android and iOS forms; integrate with colour and SVG tools | |
| **`3d-tools`** | Large specialist app | Inspect STL, OBJ and glTF; dimensions, bounding boxes, mesh simplification and common geometry defects; worker/GPU use and file limits | |
| **`midi-tools`** | Medium specialist app | MIDI event inspection and editing; transpose, quantize, tempo maps and controller data; device access should remain optional | |
| **`device-tools`** | Specialist app | Serial terminal, sensor logger, microcontroller console and controlled firmware installation; explicit permission and browser-capability checks | |
## Planned helpers
Helpers export funcionality for other tools to use. They may also present a tool surface to be used directly
| Tool | State and boundary | Principal workflows | Important concrete scope | Critical considerations and integrations |
| ------------------- | ------------------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
| **`helpers-tools`** | One shared artifact with deep links | Stateless Base64, URL encoding, case conversion, number bases, Unicode code points, unit conversion, small checksums, subnet and timestamp calculators; avoid a repository per calculator | |
| **`colour-tools`** | Planned; small-to-medium dedicated repository | Colour conversion, calculation and palette work | sRGB, Display P3, Lab, LCH, OKLab and OKLCH; gamut mapping; contrast; colour-vision simulation; gradients; palette extraction; design-token export | Make colour-space assumptions explicit; distinguish mathematical conversion from display simulation; integrate with SVG, image and token tools |
| **`rand-tools`** | Planned; small dedicated repository | Generate random numbers, strings, identifiers and samples | Cryptographically secure generation; deterministic seeded generation; UUIDs, ULIDs, passphrases, dice notation, distributions, weighted selection and shuffling | Clearly separate secure randomness from reproducible pseudorandomness; no misleading “strength” claims; natural foundation for `fixture-tools` |
| **`data-tools`** | High priority; dedicated repository | Inspect, edit, validate, query and convert structured data | JSON, YAML, TOML, XML, CSV and NDJSON; tree/table/source views; schema inference; flattening; filtering; field mapping; JSONPath-style queries; conversion recipes | Every conversion should disclose information loss, coercion and round-trip instability; XML-specialist workflows hand off to `xslt-tools` |
| **`diff-tools`** | High priority; dedicated repository or shared engine plus UI | Compare documents and files semantically | Text; JSON object-aware comparison; XML normalization; CSV keyed comparison; images with overlay/heatmap; archives and directories; PDF pages; optional audio waveform comparison | Normalization and ignored properties must be visible; produce portable reports and standard patch formats where possible |
| **`text-tools`** | Medium priority; one app with deep-linked panels | Transform and normalize plain text | Unicode normalization; line-ending conversion; encoding; sorting; deduplication; case changes; transliteration; escaping; column operations; transformation pipelines | Preserve exact input/output visibility; warn about lossy encodings and Unicode confusables; integrate regex operations |
| **`time-tools`** | Medium priority; small dedicated app or helper family | Work with dates, timestamps, time zones and recurrences | Unix timestamps; date arithmetic; durations; ISO 8601; timezone comparison; cron; RRULE; ICS generation; business days; DST-transition visualization | Visualize actual recurrence instances and ambiguous/skipped local times; do not rely on simplified fixed-offset calculations |
## Licensing

View File

@@ -3,7 +3,7 @@ services:
build:
context: .
dockerfile: Containerfile
image: git.add-ideas.de/zemion/toolbox:0.4.3
image: git.add-ideas.de/zemion/toolbox:0.9.0
restart: unless-stopped
read_only: true
ports:

View File

@@ -6,9 +6,9 @@ services:
context: .
dockerfile: Containerfile.release
args:
TOOLBOX_RELEASE_VERSION: "${TOOLBOX_RELEASE_VERSION:-0.4.3}"
TOOLBOX_RELEASE_SHA256: "${TOOLBOX_RELEASE_SHA256:-0c26315c987f7ef6d025c66c21e25999786ede3e971b66495c856117e0b7c522}"
image: "git.add-ideas.de/zemion/toolbox:${TOOLBOX_RELEASE_VERSION:-0.4.3}"
TOOLBOX_RELEASE_VERSION: "${TOOLBOX_RELEASE_VERSION:-0.9.0}"
TOOLBOX_RELEASE_SHA256: "${TOOLBOX_RELEASE_SHA256:-fa3c5b8a4148dbb360acc04fd505a6fa64e06c5c842cf1334af5c7d232208f3c}"
image: "git.add-ideas.de/zemion/toolbox:${TOOLBOX_RELEASE_VERSION:-0.9.0}"
restart: unless-stopped
read_only: true
tmpfs:
@@ -30,6 +30,13 @@ services:
traefik.http.routers.addideas-toolbox.tls: "true"
traefik.http.routers.addideas-toolbox.tls.certresolver: "${TRAEFIK_CERT_RESOLVER:-netcup}"
traefik.http.routers.addideas-toolbox.service: addideas-toolbox
traefik.http.routers.addideas-toolbox-http.rule: "Host(`${TOOLBOX_HOST:-toolbox.add-ideas.de}`)"
traefik.http.routers.addideas-toolbox-http.priority: 900
traefik.http.routers.addideas-toolbox-http.entrypoints: web
traefik.http.routers.addideas-toolbox-http.middlewares: addideas-toolbox-https-redirect
traefik.http.routers.addideas-toolbox-http.service: addideas-toolbox
traefik.http.middlewares.addideas-toolbox-https-redirect.redirectscheme.scheme: https
traefik.http.middlewares.addideas-toolbox-https-redirect.redirectscheme.permanent: "true"
traefik.http.services.addideas-toolbox.loadbalancer.server.port: "8080"
networks:

View File

@@ -1,6 +1,7 @@
map $uri $toolbox_cache_control {
default "no-cache";
"~^/(?:.*/)?assets/.*-[A-Za-z0-9_-]{8,}\.[^/]+$" "public, max-age=31536000, immutable";
"~^/apps/[a-z0-9][a-z0-9-]*/vendor/ffmpeg/(?:(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)\.(?:0|[1-9][0-9]*)|0\.12\.10-reviewed-stack5m\.1)/(?:version\.json|(?:st|mt)/ffmpeg-core\.(?:js|wasm)|mt/ffmpeg-core\.worker\.js)$" "public, max-age=31536000, immutable";
}
server {
@@ -20,7 +21,7 @@ server {
add_header Cross-Origin-Embedder-Policy "require-corp" always;
add_header Cross-Origin-Resource-Policy "same-origin" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=(), usb=()" always;
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; manifest-src 'self'" always;
add_header Content-Security-Policy "default-src 'self'; base-uri 'self'; object-src 'none'; frame-ancestors 'none'; form-action 'self'; script-src 'self' 'wasm-unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; media-src 'self' blob:; font-src 'self' data:; connect-src 'self'; worker-src 'self' blob:; manifest-src 'self'" always;
add_header Cache-Control $toolbox_cache_control always;
gzip on;
@@ -32,7 +33,14 @@ server {
try_files $uri =404;
}
location ~* \.(?:css|js|mjs|wasm|woff2?|png|jpe?g|gif|webp|svg|ico|webmanifest)$ {
location ~* \.wasm$ {
types {
application/wasm wasm;
}
try_files $uri =404;
}
location ~* \.(?:css|js|mjs|woff2?|png|jpe?g|gif|webp|svg|ico|webmanifest)$ {
try_files $uri =404;
}

557
package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@add-ideas/toolbox-portal",
"version": "0.2.3",
"version": "0.2.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@add-ideas/toolbox-portal",
"version": "0.2.3",
"version": "0.2.8",
"license": "AGPL-3.0-only",
"dependencies": {
"@add-ideas/toolbox-contract": "^0.2.2",
@@ -26,7 +26,7 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"archiver": "^7.0.1",
"archiver": "^8.0.0",
"eslint": "^10.4.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
@@ -822,24 +822,6 @@
"url": "https://github.com/sponsors/nzakas"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"license": "ISC",
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/gen-mapping": {
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
@@ -919,17 +901,6 @@
"url": "https://github.com/sponsors/Boshen"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"license": "MIT",
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@rolldown/binding-android-arm64": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz",
@@ -1838,6 +1809,7 @@
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"license": "MIT",
"peer": true,
"engines": {
"node": ">=8"
}
@@ -1857,41 +1829,24 @@
}
},
"node_modules/archiver": {
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-7.0.1.tgz",
"integrity": "sha512-ZcbTaIqJOfCc03QwD468Unz/5Ir8ATtvAHsK+FdXbDIbGfihqh9mrvdcYunQzqn4HrvWWaFyaxJhGZagaJJpPQ==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/archiver/-/archiver-8.0.0.tgz",
"integrity": "sha512-fV1orZfsnPn9BaSByR/qE67rJCLJEy2Ox5bq7nJh+jquWaNh6Sfec75kJ2T6PtdGUbPQlrVoSVCEOa5SdiTQ1g==",
"dev": true,
"license": "MIT",
"dependencies": {
"archiver-utils": "^5.0.2",
"async": "^3.2.4",
"buffer-crc32": "^1.0.0",
"readable-stream": "^4.0.0",
"readdir-glob": "^1.1.2",
"tar-stream": "^3.0.0",
"zip-stream": "^6.0.1"
},
"engines": {
"node": ">= 14"
}
},
"node_modules/archiver-utils": {
"version": "5.0.2",
"resolved": "https://registry.npmjs.org/archiver-utils/-/archiver-utils-5.0.2.tgz",
"integrity": "sha512-wuLJMmIBQYCsGZgYLTy5FIB2pF6Lfb6cXMSF8Qywwk3t20zWnAi7zLcQFdKQmIB8wyZpY5ER38x08GbwtR2cLA==",
"dev": true,
"license": "MIT",
"dependencies": {
"glob": "^10.0.0",
"graceful-fs": "^4.2.0",
"is-stream": "^2.0.1",
"is-stream": "^4.0.0",
"lazystream": "^1.0.0",
"lodash": "^4.17.15",
"normalize-path": "^3.0.0",
"readable-stream": "^4.0.0"
"readable-stream": "^4.0.0",
"readdir-glob": "^3.0.0",
"tar-stream": "^3.0.0",
"zip-stream": "^7.0.2"
},
"engines": {
"node": ">= 14"
"node": ">=18"
}
},
"node_modules/aria-query": {
@@ -2076,16 +2031,16 @@
}
},
"node_modules/brace-expansion": {
"version": "5.0.7",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.7.tgz",
"integrity": "sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==",
"version": "5.0.8",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.8.tgz",
"integrity": "sha512-JZyDyq3D4AUifKTPOB7DELf6XsB3WdPuNxCtob1vFXPsSXhdAiHBWJ/tJ8HAc9aH84BK+5JFZLNkJKx3G9kzQg==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^4.0.2"
},
"engines": {
"node": "18 || 20 || >=22"
"node": "20 || >=22"
}
},
"node_modules/browserslist": {
@@ -2188,41 +2143,21 @@
"node": ">=18"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true,
"license": "MIT"
},
"node_modules/compress-commons": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-6.0.2.tgz",
"integrity": "sha512-6FqVXeETqWPoGcfzrXb37E50NP0LXT8kAMu5ooZayhWWdgEY4lBEEcbQNXtkuKQsGduxiIcI4gOTsxTmuq/bSg==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/compress-commons/-/compress-commons-7.0.1.tgz",
"integrity": "sha512-g0S8KAD8qf4+V//pr3BfB1aBnARLXNz2Gx+jmHU0LEriUuoQUOPOulVquHKTJ8+EAIIO7fhseNDr9wK5Q9FKBQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"crc-32": "^1.2.0",
"crc32-stream": "^6.0.0",
"is-stream": "^2.0.1",
"crc32-stream": "^7.0.1",
"is-stream": "^4.0.0",
"normalize-path": "^3.0.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
"node": ">=18"
}
},
"node_modules/convert-source-map": {
@@ -2253,9 +2188,9 @@
}
},
"node_modules/crc32-stream": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-6.0.0.tgz",
"integrity": "sha512-piICUB6ei4IlTv1+653yq5+KoqfBYmj9bw6LqXoOneTMDXk5nM1qt12mFW1caG3LlJXEKW1Bp0WggEmIfQB34g==",
"version": "7.0.1",
"resolved": "https://registry.npmjs.org/crc32-stream/-/crc32-stream-7.0.1.tgz",
"integrity": "sha512-IBWsY8xznyQrcHn8h4bC8/4ErNke5elzgG8GcqF4RFPw6aHkWWRc7Tgw6upjaTX/CT/yQgqYENkxYsTYN+hW2g==",
"dev": true,
"license": "MIT",
"dependencies": {
@@ -2263,7 +2198,7 @@
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
"node": ">=18"
}
},
"node_modules/cross-spawn": {
@@ -2383,13 +2318,6 @@
"license": "MIT",
"peer": true
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true,
"license": "MIT"
},
"node_modules/electron-to-chromium": {
"version": "1.5.393",
"resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz",
@@ -2397,13 +2325,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true,
"license": "MIT"
},
"node_modules/entities": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz",
@@ -2779,23 +2700,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/foreground-child": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.3.1.tgz",
"integrity": "sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==",
"dev": true,
"license": "ISC",
"dependencies": {
"cross-spawn": "^7.0.6",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/fsevents": {
"version": "2.3.3",
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
@@ -2821,28 +2725,6 @@
"node": ">=6.9.0"
}
},
"node_modules/glob": {
"version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me",
"dev": true,
"license": "ISC",
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^3.1.2",
"minimatch": "^9.0.4",
"minipass": "^7.1.2",
"package-json-from-dist": "^1.0.0",
"path-scurry": "^1.11.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob-parent": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
@@ -2856,39 +2738,6 @@
"node": ">=10.13.0"
}
},
"node_modules/glob/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/glob/node_modules/brace-expansion": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/glob/node_modules/minimatch": {
"version": "9.0.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.9.tgz",
"integrity": "sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.2"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/globals": {
"version": "17.7.0",
"resolved": "https://registry.npmjs.org/globals/-/globals-17.7.0.tgz",
@@ -2902,13 +2751,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/graceful-fs": {
"version": "4.2.11",
"resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
"integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
"dev": true,
"license": "ISC"
},
"node_modules/hermes-estree": {
"version": "0.25.1",
"resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.25.1.tgz",
@@ -3007,16 +2849,6 @@
"node": ">=0.10.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
}
},
"node_modules/is-glob": {
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
@@ -3038,13 +2870,13 @@
"license": "MIT"
},
"node_modules/is-stream": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-4.0.1.tgz",
"integrity": "sha512-Dnz92NInDqYckGEUJv689RbRiTSEHCQ7wOVeALbkOz999YpqT46yMRIGtSNl2iCL1waAZSx40+h59NV/EwzV/A==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=8"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
@@ -3064,22 +2896,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/jackspeak": {
"version": "3.4.3",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-3.4.3.tgz",
"integrity": "sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/js-tokens": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
@@ -3544,13 +3360,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/lodash": {
"version": "4.18.1",
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz",
"integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==",
"dev": true,
"license": "MIT"
},
"node_modules/lru-cache": {
"version": "5.1.1",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
@@ -3615,16 +3424,6 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.1.3",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz",
"integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==",
"dev": true,
"license": "BlueOak-1.0.0",
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
@@ -3742,13 +3541,6 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/package-json-from-dist": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz",
"integrity": "sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==",
"dev": true,
"license": "BlueOak-1.0.0"
},
"node_modules/parse5": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz",
@@ -3782,30 +3574,6 @@
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.11.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.1.tgz",
"integrity": "sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA==",
"dev": true,
"license": "BlueOak-1.0.0",
"dependencies": {
"lru-cache": "^10.2.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.18"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/path-scurry/node_modules/lru-cache": {
"version": "10.4.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz",
"integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==",
"dev": true,
"license": "ISC"
},
"node_modules/pathe": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz",
@@ -3985,43 +3753,19 @@
}
},
"node_modules/readdir-glob": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz",
"integrity": "sha512-v05I2k7xN8zXvPD9N+z/uhXPaj0sUFCe2rcWZIpBsqxfP7xXFQ0tipAd/wjj1YxWyWtUS5IDJpOG82JKt2EAVA==",
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-3.0.0.tgz",
"integrity": "sha512-AhNB2KgKeVJr16nK9LLZbJNWnYoT23ZrumNKFDebHBdkC8KHSqWo871JAUhoWC/RtjEVdqNMFpM6qrwRbaUqpw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"minimatch": "^5.1.0"
}
},
"node_modules/readdir-glob/node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true,
"license": "MIT"
},
"node_modules/readdir-glob/node_modules/brace-expansion": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.2.tgz",
"integrity": "sha512-w5JZcKgdhDOgOwm8H+KgbosopHMuGcl6qbulwjtz3SM7I7P3yW1eAjzMPLrIE+NQ9vjgANKHWeMHnrT0OXW1oA==",
"dev": true,
"license": "MIT",
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/readdir-glob/node_modules/minimatch": {
"version": "5.1.9",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz",
"integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==",
"dev": true,
"license": "ISC",
"dependencies": {
"brace-expansion": "^2.0.1"
"minimatch": "^10.2.2"
},
"engines": {
"node": ">=10"
"node": ">=18"
},
"funding": {
"url": "https://github.com/sponsors/yqnn"
}
},
"node_modules/redent": {
@@ -4162,19 +3906,6 @@
"dev": true,
"license": "ISC"
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"license": "ISC",
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -4221,103 +3952,6 @@
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"license": "MIT",
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^6.2.2"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi/node_modules/ansi-regex": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/strip-indent": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
@@ -4855,107 +4489,6 @@
"node": ">=0.10.0"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"license": "MIT",
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true,
"license": "MIT"
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"license": "MIT",
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"license": "MIT",
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi/node_modules/ansi-styles": {
"version": "6.2.3",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
"dev": true,
"license": "MIT",
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/xml-name-validator": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz",
@@ -5007,18 +4540,18 @@
}
},
"node_modules/zip-stream": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-6.0.1.tgz",
"integrity": "sha512-zK7YHHz4ZXpW89AHXUPbQVGKI7uvkd3hzusTdotCg1UxyaVtg0zFJSTfW/Dq5f7OBBVnq6cZIaC8Ti4hb6dtCA==",
"version": "7.0.5",
"resolved": "https://registry.npmjs.org/zip-stream/-/zip-stream-7.0.5.tgz",
"integrity": "sha512-dSvYKdvLsAHCDqPOhIwk/q5CvuWtTB3Dgpoe0uVEFjTzIOAmsQpprX25InCvrvJsirEbu1OHyy67n/kAj1Sw/w==",
"dev": true,
"license": "MIT",
"dependencies": {
"archiver-utils": "^5.0.0",
"compress-commons": "^6.0.2",
"compress-commons": "^7.0.0",
"normalize-path": "^3.0.0",
"readable-stream": "^4.0.0"
},
"engines": {
"node": ">= 14"
"node": ">=18"
}
},
"node_modules/zod": {

View File

@@ -1,6 +1,6 @@
{
"name": "@add-ideas/toolbox-portal",
"version": "0.2.3",
"version": "0.2.8",
"license": "AGPL-3.0-only",
"private": true,
"type": "module",
@@ -38,7 +38,7 @@
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.2",
"archiver": "^7.0.1",
"archiver": "^8.0.0",
"eslint": "^10.4.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",

View File

@@ -1,8 +1,8 @@
# Corresponding source
The source code corresponding to add·ideas Toolbox Portal 0.2.3 is available at:
The source code corresponding to add·ideas Toolbox Portal 0.2.8 is available at:
https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.4.3
https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.9.0
Each bundled application contains its own `SOURCE.md`, license, and third-party
license materials. The application sources are also linked from the portal.

View File

@@ -20,6 +20,14 @@
{
"manifest": "./apps/onenote/toolbox-app.json",
"enabled": true
},
{
"manifest": "./apps/av/toolbox-app.json",
"enabled": true
},
{
"manifest": "./apps/regex/toolbox-app.json",
"enabled": true
}
]
}

View File

@@ -1,8 +1,8 @@
{
"$schema": "./toolbox-release-lock.schema.json",
"schemaVersion": 1,
"releaseVersion": "0.4.3",
"portalVersion": "0.2.3",
"releaseVersion": "0.9.0",
"portalVersion": "0.2.8",
"catalogue": {
"id": "de.add-ideas.toolbox",
"name": "add·ideas Toolbox",
@@ -33,6 +33,20 @@
"artifact": "https://git.add-ideas.de/zemion/onenote-tools/releases/download/v0.3.3/onenote-tools-0.3.3.zip",
"sha256": "37c0ef7cdc7f435d0d4f5a341523f4e522118d5b258c374b5a7ef7b8e6c27cec",
"target": "onenote"
},
{
"id": "de.add-ideas.av-tools",
"version": "0.2.0",
"artifact": "https://git.add-ideas.de/zemion/av-tools/releases/download/v0.2.0/av-tools-0.2.0.zip",
"sha256": "40f9e7f48617930ddcba25538bd12d0a24197c7c4824989f20fd23fd25349a54",
"target": "av"
},
{
"id": "de.add-ideas.regex-tools",
"version": "0.4.0",
"artifact": "https://git.add-ideas.de/zemion/regex-tools/releases/download/v0.4.0/regex-tools-0.4.0.zip",
"sha256": "68505ed3e302627d889c45c651431d8fb9638056415cad72a28196fd33acd2b4",
"target": "regex"
}
]
}

View File

@@ -1,8 +1,8 @@
{
"$schema": "./toolbox-release-lock.schema.json",
"schemaVersion": 1,
"releaseVersion": "0.4.3",
"portalVersion": "0.2.3",
"releaseVersion": "0.9.0",
"portalVersion": "0.2.8",
"catalogue": {
"id": "de.add-ideas.toolbox",
"name": "add·ideas Toolbox",
@@ -33,6 +33,20 @@
"artifact": "https://git.add-ideas.de/zemion/onenote-tools/releases/download/v0.3.3/onenote-tools-0.3.3.zip",
"sha256": "37c0ef7cdc7f435d0d4f5a341523f4e522118d5b258c374b5a7ef7b8e6c27cec",
"target": "onenote"
},
{
"id": "de.add-ideas.av-tools",
"version": "0.2.0",
"artifact": "https://git.add-ideas.de/zemion/av-tools/releases/download/v0.2.0/av-tools-0.2.0.zip",
"sha256": "40f9e7f48617930ddcba25538bd12d0a24197c7c4824989f20fd23fd25349a54",
"target": "av"
},
{
"id": "de.add-ideas.regex-tools",
"version": "0.4.0",
"artifact": "https://git.add-ideas.de/zemion/regex-tools/releases/download/v0.4.0/regex-tools-0.4.0.zip",
"sha256": "68505ed3e302627d889c45c651431d8fb9638056415cad72a28196fd33acd2b4",
"target": "regex"
}
]
}

View File

@@ -2,7 +2,7 @@ import { createReadStream, createWriteStream } from 'node:fs';
import { mkdir, readdir, stat } from 'node:fs/promises';
import path from 'node:path';
import { pipeline } from 'node:stream/promises';
import archiver from 'archiver';
import { ZipArchive } from 'archiver';
import {
canonicalExistingPath,
canonicalPathsOverlap,
@@ -56,7 +56,7 @@ export async function createStaticArchive(inputDirectory, outputFile) {
throw new Error(`Archive input is not a directory: ${source}`);
await mkdir(path.dirname(output), { recursive: true });
const archive = archiver('zip', { zlib: { level: 9 } });
const archive = new ZipArchive({ zlib: { level: 9 } });
const destination = createWriteStream(output, { flags: 'wx' });
archive.on('warning', (error) => {
if (error.code !== 'ENOENT') destination.destroy(error);

View File

@@ -14,7 +14,7 @@ import os from 'node:os';
import path from 'node:path';
import { pipeline } from 'node:stream/promises';
import { pathToFileURL } from 'node:url';
import archiver from 'archiver';
import { ZipArchive } from 'archiver';
import { afterEach, describe, expect, it, vi } from 'vitest';
import { assembleRelease } from './assemble.mjs';
import { createStaticArchive } from './archive.mjs';
@@ -46,7 +46,7 @@ async function zipEntries(
output: string,
entries: Array<{ name: string; content: string }>
) {
const archive = archiver('zip', { zlib: { level: 9 } });
const archive = new ZipArchive({ zlib: { level: 9 } });
const writing = pipeline(archive, createWriteStream(output, { flags: 'wx' }));
for (const entry of entries)
archive.append(entry.content, { name: entry.name });
@@ -83,7 +83,7 @@ function makeLock(artifact: string, sha256: string) {
return {
schemaVersion: 1,
releaseVersion: '0.1.0',
portalVersion: '0.2.3',
portalVersion: '0.2.8',
catalogue: {
id: 'de.add-ideas.toolbox',
name: 'Test Toolbox',
@@ -238,7 +238,7 @@ describe('release assembly integrity', () => {
it('blocks symbolic links before extraction', async () => {
const directory = await temporaryDirectory();
const artifact = path.join(directory, 'symlink.zip');
const archive = archiver('zip', { zlib: { level: 9 } });
const archive = new ZipArchive({ zlib: { level: 9 } });
const writing = pipeline(
archive,
createWriteStream(artifact, { flags: 'wx' })

View File

@@ -4,10 +4,18 @@ import { describe, expect, it } from 'vitest';
const projectRoot = process.cwd();
const compose = readFileSync(path.join(projectRoot, 'compose.yaml'), 'utf8');
const composeExample = readFileSync(
path.join(projectRoot, 'compose.example.yaml'),
'utf8'
);
const containerfile = readFileSync(
path.join(projectRoot, 'Containerfile.release'),
'utf8'
);
const environmentExample = readFileSync(
path.join(projectRoot, '.env.example'),
'utf8'
);
const nginxConfig = readFileSync(
path.join(projectRoot, 'deploy', 'nginx.conf'),
'utf8'
@@ -16,6 +24,23 @@ const releaseLock = JSON.parse(
readFileSync(path.join(projectRoot, 'release', 'toolbox.lock.json'), 'utf8')
) as { releaseVersion: string };
const immutableCacheControl = 'public, max-age=31536000, immutable';
const immutableUriPatterns = Array.from(
nginxConfig.matchAll(
new RegExp(
`^\\s*"~([^"]+)"\\s+"${immutableCacheControl.replaceAll(
', ',
',\\s+'
)}";$`,
'gmu'
)
),
(match) => new RegExp(match[1], 'u')
);
const isImmutable = (uri: string): boolean =>
immutableUriPatterns.some((pattern) => pattern.test(uri));
describe('production deployment', () => {
it('keeps the release version and checksum pins synchronized', () => {
const containerVersion = containerfile.match(
@@ -30,10 +55,25 @@ describe('production deployment', () => {
const composeChecksum = compose.match(
/\$\{TOOLBOX_RELEASE_SHA256:-([a-f0-9]{64})\}/u
)?.[1];
const environmentVersion = environmentExample.match(
/^TOOLBOX_RELEASE_VERSION=(\S+)$/mu
)?.[1];
const environmentChecksum = environmentExample.match(
/^TOOLBOX_RELEASE_SHA256=([a-f0-9]{64})$/mu
)?.[1];
const localImageVersion = composeExample.match(
/^\s+image: git\.add-ideas\.de\/zemion\/toolbox:(\S+)$/mu
)?.[1];
expect(containerVersion).toBe(releaseLock.releaseVersion);
expect(composeVersion).toBe(containerVersion);
expect(containerChecksum).toMatch(/^[a-f0-9]{64}$/u);
expect(composeChecksum).toMatch(/^[a-f0-9]{64}$/u);
expect(environmentChecksum).toMatch(/^[a-f0-9]{64}$/u);
expect(composeChecksum).toBe(containerChecksum);
expect(environmentVersion).toBe(containerVersion);
expect(environmentChecksum).toBe(containerChecksum);
expect(localImageVersion).toBe(containerVersion);
});
it('verifies the release before extraction and retains a rootless final image', () => {
@@ -45,10 +85,15 @@ describe('production deployment', () => {
expect(containerfile).toContain(
'COPY --from=release --chown=101:101 /tmp/toolbox/'
);
for (const app of ['pdf', 'xslt', 'onenote', 'av', 'regex']) {
expect(containerfile).toContain(
`test -f /tmp/toolbox/apps/${app}/toolbox-app.json`
);
}
expect(containerfile).toMatch(/\nUSER 101:101\nEXPOSE 8080\n/u);
});
it('uses only the requested external Traefik network and HTTPS router', () => {
it('uses only the requested external Traefik network and redirects HTTP to HTTPS', () => {
expect(compose).toContain('dockerfile: Containerfile.release');
expect(compose).toContain('traefik.enable: "true"');
expect(compose).toContain(
@@ -63,6 +108,27 @@ describe('production deployment', () => {
expect(compose).toContain(
'traefik.http.routers.addideas-toolbox.tls.certresolver: "${TRAEFIK_CERT_RESOLVER:-netcup}"'
);
expect(compose).toContain(
'traefik.http.routers.addideas-toolbox-http.rule: "Host(`${TOOLBOX_HOST:-toolbox.add-ideas.de}`)"'
);
expect(compose).toContain(
'traefik.http.routers.addideas-toolbox-http.entrypoints: web'
);
expect(compose).toContain(
'traefik.http.routers.addideas-toolbox-http.priority: 900'
);
expect(compose).toContain(
'traefik.http.routers.addideas-toolbox-http.middlewares: addideas-toolbox-https-redirect'
);
expect(compose).toContain(
'traefik.http.routers.addideas-toolbox-http.service: addideas-toolbox'
);
expect(compose).toContain(
'traefik.http.middlewares.addideas-toolbox-https-redirect.redirectscheme.scheme: https'
);
expect(compose).toContain(
'traefik.http.middlewares.addideas-toolbox-https-redirect.redirectscheme.permanent: "true"'
);
expect(compose).toContain('external: true');
expect(compose).not.toMatch(/^\s+ports:/mu);
});
@@ -73,4 +139,67 @@ describe('production deployment', () => {
);
expect(nginxConfig).not.toMatch(/^\s*~[^\n]*\{\d/mu);
});
it('caches only hashed assets and the versioned FFmpeg runtime immutably', () => {
expect(immutableUriPatterns).toHaveLength(2);
for (const uri of [
'/assets/index-Bp9nK_3a.js',
'/apps/av/vendor/ffmpeg/0.12.10/version.json',
'/apps/av/vendor/ffmpeg/0.12.10/st/ffmpeg-core.js',
'/apps/av/vendor/ffmpeg/0.12.10/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.js',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.worker.js',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/version.json',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/st/ffmpeg-core.js',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/mt/ffmpeg-core.js',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/mt/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.1/mt/ffmpeg-core.worker.js',
]) {
expect(isImmutable(uri), uri).toBe(true);
}
for (const uri of [
'/',
'/index.html',
'/SOURCE.md',
'/LICENSE',
'/toolbox.catalog.json',
'/toolbox.release.json',
'/apps/av/toolbox-app.json',
'/apps/av/THIRD_PARTY_NOTICES.md',
'/apps/av/vendor/ffmpeg/version.json',
'/apps/av/vendor/ffmpeg/latest/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/01.2.3/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/st/other.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/mt/ffmpeg-core.worker.wasm',
'/apps/av/vendor/ffmpeg/0.12.10/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10-/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed_/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10-latest/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10-SNAPSHOT/st/ffmpeg-core.wasm',
'/apps/av/vendor/ffmpeg/0.12.10-reviewed-stack5m.2/st/ffmpeg-core.wasm',
]) {
expect(isImmutable(uri), uri).toBe(false);
}
});
it('serves WebAssembly through an explicit MIME-mapped location', () => {
expect(nginxConfig).toMatch(
/location ~\* \\\.wasm\$\s*\{\s*types\s*\{\s*application\/wasm wasm;\s*\}\s*try_files \$uri =404;\s*\}/mu
);
expect(nginxConfig).not.toMatch(
/location ~\* [^\n]*\(\?:[^\n|)]*\bwasm\b[^\n)]*\)/u
);
});
it('allows same-origin WebAssembly workers and local blob media previews', () => {
expect(nginxConfig).toContain("script-src 'self' 'wasm-unsafe-eval'");
expect(nginxConfig).toContain("worker-src 'self' blob:");
expect(nginxConfig).toContain("media-src 'self' blob:");
expect(nginxConfig).not.toContain("'unsafe-eval'");
});
});

View File

@@ -121,6 +121,33 @@ describe('portal UI', () => {
);
});
it('uses the compact Regex presentation for the released tool', async () => {
vi.stubGlobal(
'fetch',
vi.fn(
catalogueFetch({
'/toolbox.catalog.json': Response.json({
...catalogue,
apps: [
...catalogue.apps,
{ manifest: './apps/regex/toolbox-app.json', enabled: true },
],
}),
})
)
);
render(<App />);
await findToolLink();
const card = screen.getByTestId('app-card-de.add-ideas.regex-tools');
expect(within(card).getByRole('heading', { level: 3 })).toHaveTextContent(
'Regex'
);
expect(
within(card).getByText('Explain and test regex locally.')
).toBeVisible();
});
it('moves the full disclosure into an accessible modal', async () => {
vi.stubGlobal('fetch', vi.fn(catalogueFetch()));
const user = userEvent.setup();

View File

@@ -305,6 +305,7 @@ export default function App() {
tool. Choose one and keep your work moving.
</p>
</div>
</section>
{loaded && (
<section className="tool-controls" aria-label="Find a tool">
@@ -364,7 +365,6 @@ export default function App() {
</label>
</section>
)}
</section>
{loadState.status === 'loading' && (
<section className="state-card" aria-live="polite">
@@ -489,9 +489,9 @@ export default function App() {
</span>
</p>
<span>
Portal v0.2.3 ·{' '}
Portal v0.2.8 ·{' '}
<a
href="https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.4.3"
href="https://git.add-ideas.de/zemion/toolbox-portal/src/tag/v0.9.0"
target="_blank"
rel="noopener noreferrer"
>

View File

@@ -17,13 +17,37 @@ interface AppCardProps {
function MoveIcon() {
return (
<svg viewBox="0 0 20 20" aria-hidden="true">
<svg viewBox="0 0 20 80" aria-hidden="true">
<circle cx="7" cy="5" r="1.2" />
<circle cx="13" cy="5" r="1.2" />
<circle cx="7" cy="10" r="1.2" />
<circle cx="13" cy="10" r="1.2" />
<circle cx="7" cy="15" r="1.2" />
<circle cx="13" cy="15" r="1.2" />
<circle cx="7" cy="20" r="1.2" />
<circle cx="13" cy="20" r="1.2" />
<circle cx="7" cy="25" r="1.2" />
<circle cx="13" cy="25" r="1.2" />
<circle cx="7" cy="30" r="1.2" />
<circle cx="13" cy="30" r="1.2" />
<circle cx="7" cy="35" r="1.2" />
<circle cx="13" cy="35" r="1.2" />
<circle cx="7" cy="40" r="1.2" />
<circle cx="13" cy="40" r="1.2" />
<circle cx="7" cy="45" r="1.2" />
<circle cx="13" cy="45" r="1.2" />
<circle cx="7" cy="50" r="1.2" />
<circle cx="13" cy="50" r="1.2" />
<circle cx="7" cy="55" r="1.2" />
<circle cx="13" cy="55" r="1.2" />
<circle cx="7" cy="60" r="1.2" />
<circle cx="13" cy="60" r="1.2" />
<circle cx="7" cy="65" r="1.2" />
<circle cx="13" cy="65" r="1.2" />
<circle cx="7" cy="70" r="1.2" />
<circle cx="13" cy="70" r="1.2" />
<circle cx="7" cy="75" r="1.2" />
<circle cx="13" cy="75" r="1.2" />
</svg>
);
}

View File

@@ -151,14 +151,14 @@ main {
.hero {
display: grid;
grid-template-columns: minmax(0, 1fr) minmax(19rem, 26rem);
grid-template-columns: 1fr;
align-items: end;
gap: clamp(2rem, 6vw, 5rem);
padding: clamp(3.5rem, 7vw, 6.8rem) 0 clamp(2.5rem, 5vw, 4.2rem);
}
.hero-intro {
max-width: 750px;
max-width: 100%;
}
.eyebrow {
@@ -403,8 +403,8 @@ main {
z-index: 2;
top: 50%;
left: 0.35rem;
width: 1.7rem;
height: 2.25rem;
width: 1rem;
height: 5rem;
display: grid;
place-items: center;
padding: 0;
@@ -440,8 +440,8 @@ main {
}
.drag-handle svg {
width: 1rem;
height: 1rem;
width: calc(1rem - 2px);
height: 4rem;
fill: currentColor;
}
@@ -1018,7 +1018,7 @@ main {
@media (min-width: 701px) and (max-width: 960px) {
.hero {
grid-template-columns: minmax(0, 1fr) minmax(17rem, 21rem);
grid-template-columns: 1fr;
}
}

View File

@@ -65,6 +65,28 @@ export const onenoteManifest: ToolboxAppManifest = {
},
};
export const regexManifest: ToolboxAppManifest = {
...pdfManifest,
id: 'de.add-ideas.regex-tools',
name: 'Regex Tools',
version: '0.4.0',
description:
'Develop, explain, test and apply regular expressions locally in the browser.',
icon: './regex.svg',
categories: ['developer', 'text', 'regex'],
tags: ['regular-expression', 'match', 'replace', 'explain', 'test'],
requirements: {
secureContext: false,
workers: true,
indexedDb: true,
crossOriginIsolated: false,
},
source: {
repository: 'https://git.add-ideas.de/zemion/regex-tools',
license: 'GPL-3.0-or-later',
},
};
export const catalogue: ToolboxCatalog = {
schemaVersion: 1,
id: 'de.add-ideas.toolbox',
@@ -94,6 +116,8 @@ export function catalogueFetch(overrides: Record<string, Response> = {}) {
return Response.json(xsltManifest);
if (url.pathname.endsWith('/apps/onenote/toolbox-app.json'))
return Response.json(onenoteManifest);
if (url.pathname.endsWith('/apps/regex/toolbox-app.json'))
return Response.json(regexManifest);
return new Response('Not found', { status: 404 });
};
}

View File

@@ -13,6 +13,10 @@ const PRESENTATION: Record<string, { title: string; description: string }> = {
title: 'OneNote',
description: 'Open OneNote files locally.',
},
'de.add-ideas.regex-tools': {
title: 'Regex',
description: 'Explain and test regex locally.',
},
};
export function shortToolTitle(app: ResolvedApp): string {