Compare commits

..

40 Commits

Author SHA1 Message Date
c0f9f979c7 v1.22.2
All checks were successful
Release / build-and-release (push) Successful in 2m59s
2026-03-18 09:48:12 +00:00
6e5743c837 fix(web-ui): stabilize app store service creation flow and add Ghost sqlite defaults 2026-03-18 09:48:12 +00:00
829f7e47f1 v1.22.1
Some checks failed
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 29s
CI / Build Test (Current Platform) (push) Successful in 1m2s
CI / Build All Platforms (push) Successful in 2m19s
Release / build-and-release (push) Successful in 3m45s
2026-03-18 02:44:14 +00:00
a36af5c3d5 fix(repo): no changes to commit 2026-03-18 02:44:14 +00:00
3c99ee5f83 v1.22.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 7s
CI / Type Check & Lint (push) Failing after 29s
CI / Build Test (Current Platform) (push) Successful in 1m1s
CI / Build All Platforms (push) Successful in 2m11s
Release / build-and-release (push) Successful in 3m56s
2026-03-18 02:37:39 +00:00
2faa416895 feat(web-appstore): add an App Store view for quick service deployment from curated templates 2026-03-18 02:37:39 +00:00
acbf448c6f v1.21.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 27s
CI / Build Test (Current Platform) (push) Successful in 1m2s
CI / Build All Platforms (push) Successful in 2m13s
Release / build-and-release (push) Successful in 3m34s
2026-03-18 02:22:45 +00:00
5c48ae4156 feat(opsserver): add container workspace API and backend execution environment for services 2026-03-18 02:22:45 +00:00
3108408133 v1.20.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 7s
CI / Type Check & Lint (push) Failing after 39s
CI / Build Test (Current Platform) (push) Successful in 1m7s
CI / Build All Platforms (push) Successful in 2m52s
Release / build-and-release (push) Successful in 6m2s
2026-03-17 23:39:25 +00:00
6defdb4431 feat(ops-dashboard): stream user service logs to the ops dashboard and resolve service containers for Docker log streaming 2026-03-17 23:39:24 +00:00
f63be883ce v1.19.12
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
CI / Build All Platforms (push) Failing after 13m15s
CI / Build Test (Current Platform) (push) Failing after 13m17s
CI / Type Check & Lint (push) Failing after 13m19s
Release / build-and-release (push) Successful in 6m6s
2026-03-17 12:52:34 +00:00
87844bbb8e fix(repo): no changes to commit 2026-03-17 12:52:34 +00:00
02b7cda2be v1.19.11
Some checks failed
Publish to npm / npm-publish (push) Failing after 9s
CI / Type Check & Lint (push) Failing after 28s
CI / Build Test (Current Platform) (push) Successful in 1m0s
CI / Build All Platforms (push) Successful in 1m57s
Release / build-and-release (push) Successful in 5m51s
2026-03-17 10:40:40 +00:00
3b8f95e8e1 fix(repo): no changes to commit 2026-03-17 10:40:40 +00:00
ee774e3f41 v1.19.10
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
CI / Type Check & Lint (push) Failing after 38s
CI / Build Test (Current Platform) (push) Successful in 1m6s
CI / Build All Platforms (push) Successful in 2m17s
Release / build-and-release (push) Successful in 7m46s
2026-03-17 02:09:50 +00:00
6d93dfa459 fix(repo): no changes to commit 2026-03-17 02:09:50 +00:00
ac394cfafc v1.19.9
Some checks failed
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 29s
CI / Build Test (Current Platform) (push) Successful in 1m2s
CI / Build All Platforms (push) Successful in 2m0s
Release / build-and-release (push) Successful in 2m51s
2026-03-17 01:53:38 +00:00
97e9f232fa fix(repo): no changes to commit 2026-03-17 01:53:38 +00:00
3dcb6a38e5 v1.19.8
Some checks failed
CI / Build All Platforms (push) Failing after 9s
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 28s
CI / Build Test (Current Platform) (push) Successful in 59s
Release / build-and-release (push) Failing after 1m24s
2026-03-17 01:37:18 +00:00
ca33970e9a fix(repo): no changes to commit 2026-03-17 01:37:18 +00:00
cd34b98a25 v1.19.7
Some checks failed
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 33s
CI / Build Test (Current Platform) (push) Successful in 1m7s
CI / Build All Platforms (push) Successful in 1m52s
Release / build-and-release (push) Successful in 3m50s
2026-03-17 01:03:14 +00:00
a089e5bedb fix(repo): no changes to commit 2026-03-17 01:03:14 +00:00
9786ff62f0 v1.19.6
Some checks failed
Publish to npm / npm-publish (push) Failing after 9s
CI / Type Check & Lint (push) Failing after 31s
CI / Build Test (Current Platform) (push) Successful in 1m6s
CI / Build All Platforms (push) Successful in 2m15s
Release / build-and-release (push) Failing after 3m30s
2026-03-17 00:45:56 +00:00
4a5abc4a0a fix(repository): no changes to commit 2026-03-17 00:45:55 +00:00
893a532758 v1.19.5
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
Release / build-and-release (push) Failing after 3m34s
2026-03-17 00:45:37 +00:00
7ea286c0a9 fix(repo): no changes to commit 2026-03-17 00:45:37 +00:00
f94f47e313 v1.19.4
Some checks failed
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 32s
CI / Build Test (Current Platform) (push) Successful in 1m7s
CI / Build All Platforms (push) Successful in 2m8s
Release / build-and-release (push) Successful in 3m38s
2026-03-17 00:01:08 +00:00
b1a46f8757 fix(repository): no changes to commit 2026-03-17 00:01:08 +00:00
56c71226e5 v1.19.3
Some checks failed
CI / Build All Platforms (push) Failing after 3s
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 36s
CI / Build Test (Current Platform) (push) Successful in 1m3s
Release / build-and-release (push) Successful in 2m48s
2026-03-16 20:06:11 +00:00
f53109a01e fix(repo): no changes to commit 2026-03-16 20:06:11 +00:00
bcb2473cc5 v1.19.2
Some checks failed
Publish to npm / npm-publish (push) Failing after 10s
CI / Type Check & Lint (push) Failing after 34s
CI / Build Test (Current Platform) (push) Successful in 1m10s
CI / Build All Platforms (push) Successful in 2m34s
Release / build-and-release (push) Failing after 4m2s
2026-03-16 20:00:10 +00:00
689dcf295b fix(docs): remove outdated UI screenshot assets from project documentation 2026-03-16 20:00:10 +00:00
c1e14e9fc7 v1.19.1
Some checks failed
CI / Type Check & Lint (push) Failing after 30s
CI / Build Test (Current Platform) (push) Successful in 1m3s
CI / Build All Platforms (push) Successful in 2m5s
2026-03-16 19:55:27 +00:00
d5fd57e2c3 fix(dashboard): add updated dashboard screenshots for refresh and resource usage states 2026-03-16 19:55:27 +00:00
079e6a64a9 fix(dashboard): add aggregated resource usage stats to the dashboard
Some checks failed
Publish to npm / npm-publish (push) Failing after 4s
CI / Type Check & Lint (push) Failing after 29s
CI / Build Test (Current Platform) (push) Successful in 1m1s
Release / build-and-release (push) Failing after 1m43s
CI / Build All Platforms (push) Successful in 2m8s
- Aggregate CPU, memory, and network stats across all running containers
- Extend ISystemStatus.docker with resource usage fields
- Fix getContainerStats Swarm service ID fallback
- Wire dashboard resource usage card to real backend data
2026-03-16 16:47:05 +00:00
a04cf053db v1.19.0
Some checks failed
Publish to npm / npm-publish (push) Failing after 11s
CI / Type Check & Lint (push) Failing after 30s
CI / Build Test (Current Platform) (push) Successful in 58s
CI / Build All Platforms (push) Successful in 2m21s
Release / build-and-release (push) Successful in 4m11s
2026-03-16 16:19:39 +00:00
ec0e377ccb feat(opsserver,web): add real-time platform service log streaming to the dashboard 2026-03-16 16:19:39 +00:00
3b3d0433cb fix(platform-services): fix detail view navigation and log display
Some checks failed
CI / Build All Platforms (push) Failing after 4s
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 29s
CI / Build Test (Current Platform) (push) Successful in 1m6s
Release / build-and-release (push) Successful in 3m21s
- Add back button for returning to services list
- Fix DOM lifecycle when switching between platform services
- Fix timestamp format for dees-chart-log compatibility
- Clear previous stats/logs state before fetching new data
2026-03-16 14:48:46 +00:00
5f876449ca v1.18.4
Some checks failed
CI / Build All Platforms (push) Failing after 6s
Publish to npm / npm-publish (push) Failing after 8s
CI / Type Check & Lint (push) Failing after 24s
CI / Build Test (Current Platform) (push) Successful in 54s
Release / build-and-release (push) Successful in 2m19s
2026-03-16 14:35:45 +00:00
8e781c7f9d fix(repo): no changes to commit 2026-03-16 14:35:45 +00:00
30 changed files with 1484 additions and 406 deletions

View File

@@ -1,114 +0,0 @@
name: CI
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
check:
name: Type Check & Lint
runs-on: ubuntu-latest
container:
image: code.foss.global/host.today/ht-docker-node:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Install dependencies
run: deno install --entrypoint mod.ts
- name: Check TypeScript types
run: deno check mod.ts
- name: Lint code
run: deno lint
continue-on-error: true
- name: Format check
run: deno fmt --check
continue-on-error: true
build:
name: Build Test (Current Platform)
runs-on: ubuntu-latest
container:
image: code.foss.global/host.today/ht-docker-node:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Enable corepack
run: corepack enable
- name: Install dependencies
run: pnpm install --ignore-scripts
- name: Compile for current platform
run: |
echo "Testing compilation for Linux x86_64..."
npx tsdeno compile --allow-all --no-check \
--output onebox-test \
--target x86_64-unknown-linux-gnu mod.ts
- name: Test binary execution
run: |
chmod +x onebox-test
./onebox-test --version
./onebox-test --help
build-all:
name: Build All Platforms
runs-on: ubuntu-latest
container:
image: code.foss.global/host.today/ht-docker-node:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Set up Node.js
uses: actions/setup-node@v4
with:
node-version: '22'
- name: Enable corepack
run: corepack enable
- name: Install dependencies
run: pnpm install --ignore-scripts
- name: Compile all platform binaries
run: mkdir -p dist/binaries && npx tsdeno compile
- name: Upload all binaries as artifact
uses: actions/upload-artifact@v3
with:
name: onebox-binaries.zip
path: dist/binaries/*
retention-days: 30

View File

@@ -1,131 +0,0 @@
name: Publish to npm
on:
push:
tags:
- 'v*'
jobs:
npm-publish:
runs-on: ubuntu-latest
container:
image: code.foss.global/host.today/ht-docker-node:latest
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up Deno
uses: denoland/setup-deno@v1
with:
deno-version: v2.x
- name: Setup Node.js for npm publishing
uses: actions/setup-node@v4
with:
node-version: '18.x'
registry-url: 'https://registry.npmjs.org/'
- name: Get version from tag
id: version
run: |
VERSION=${GITHUB_REF#refs/tags/}
echo "version=$VERSION" >> $GITHUB_OUTPUT
echo "version_number=${VERSION#v}" >> $GITHUB_OUTPUT
echo "Publishing version: $VERSION"
- name: Verify deno.json version matches tag
run: |
DENO_VERSION=$(grep -o '"version": "[^"]*"' deno.json | cut -d'"' -f4)
TAG_VERSION="${{ steps.version.outputs.version_number }}"
echo "deno.json version: $DENO_VERSION"
echo "Tag version: $TAG_VERSION"
if [ "$DENO_VERSION" != "$TAG_VERSION" ]; then
echo "ERROR: Version mismatch!"
echo "deno.json has version $DENO_VERSION but tag is $TAG_VERSION"
exit 1
fi
- name: Compile binaries for npm package
run: |
echo "Compiling binaries for npm package..."
deno task compile
echo ""
echo "Binary sizes:"
ls -lh dist/binaries/
- name: Generate SHA256 checksums
run: |
cd dist/binaries
sha256sum * > SHA256SUMS
cat SHA256SUMS
cd ../..
- name: Sync package.json version
run: |
VERSION="${{ steps.version.outputs.version_number }}"
echo "Syncing package.json to version ${VERSION}..."
npm version ${VERSION} --no-git-tag-version --allow-same-version
echo "package.json version: $(grep '"version"' package.json | head -1)"
- name: Create npm package
run: |
echo "Creating npm package..."
npm pack
echo ""
echo "Package created:"
ls -lh *.tgz
- name: Test local installation
run: |
echo "Testing local package installation..."
PACKAGE_FILE=$(ls *.tgz)
npm install -g ${PACKAGE_FILE}
echo ""
echo "Testing onebox command:"
onebox --version || echo "Note: Binary execution may fail in CI environment"
echo ""
echo "Checking installed files:"
npm ls -g @serve.zone/onebox || true
- name: Publish to npm
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
run: |
echo "Publishing to npm registry..."
npm publish --access public
echo ""
echo "Successfully published @serve.zone/onebox to npm!"
echo ""
echo "Package info:"
npm view @serve.zone/onebox
- name: Verify npm package
run: |
echo "Waiting for npm propagation..."
sleep 30
echo ""
echo "Verifying published package..."
npm view @serve.zone/onebox
echo ""
echo "Testing installation from npm:"
npm install -g @serve.zone/onebox
echo ""
echo "Package installed successfully!"
which onebox || echo "Binary location check skipped"
- name: Publish Summary
run: |
echo "================================================"
echo " npm Publish Complete!"
echo "================================================"
echo ""
echo "Package: @serve.zone/onebox"
echo "Version: ${{ steps.version.outputs.version }}"
echo ""
echo "Installation:"
echo " npm install -g @serve.zone/onebox"
echo ""
echo "Registry:"
echo " https://www.npmjs.com/package/@serve.zone/onebox"
echo ""

View File

@@ -1,5 +1,121 @@
# Changelog
## 2026-03-18 - 1.22.2 - fix(web-ui)
stabilize app store service creation flow and add Ghost sqlite defaults
- Defers App Store navigation to the services view to avoid destroying the current view during the deploy event handler.
- Processes pending app templates after services view updates so the create flow opens reliably.
- Adds default Ghost environment variables for sqlite3 and the database file path in the App Store template.
- Removes obsolete Gitea CI and npm publish workflow definitions.
## 2026-03-18 - 1.22.1 - fix(repo)
no changes to commit
## 2026-03-18 - 1.22.0 - feat(web-appstore)
add an App Store view for quick service deployment from curated templates
- adds a new App Store tab to the web UI with curated Docker app templates
- passes selected app templates through UI state into the services view for quick deployment
- supports quick deploy creation with prefilled image, port, environment variables, and optional platform service flags
- updates @serve.zone/catalog to ^2.8.0 to support the new app store view
## 2026-03-18 - 1.21.0 - feat(opsserver)
add container workspace API and backend execution environment for services
- introduces typed workspace handlers for reading, writing, listing, creating, removing, and executing commands inside service containers
- adds frontend backend-execution environment integration so the service view can open a workspace against a selected service
- extends Docker exec lookup to resolve Swarm service container IDs when a direct container ID is unavailable
## 2026-03-17 - 1.20.0 - feat(ops-dashboard)
stream user service logs to the ops dashboard and resolve service containers for Docker log streaming
- add typed socket support for pushing live user service log entries to the web app
- extend platform log streaming to include running user services with separate dashboard handlers
- fall back from direct container lookup to service-to-container resolution when streaming Docker logs
- update log parsing to preserve timestamps and infer log levels for service log entries
- bump @serve.zone/catalog to ^2.7.0
## 2026-03-17 - 1.19.12 - fix(repo)
no changes to commit
## 2026-03-17 - 1.19.11 - fix(repo)
no changes to commit
## 2026-03-17 - 1.19.10 - fix(repo)
no changes to commit
## 2026-03-17 - 1.19.9 - fix(repo)
no changes to commit
## 2026-03-17 - 1.19.8 - fix(repo)
no changes to commit
## 2026-03-17 - 1.19.7 - fix(repo)
no changes to commit
## 2026-03-17 - 1.19.6 - fix(repository)
no changes to commit
## 2026-03-17 - 1.19.5 - fix(repo)
no changes to commit
## 2026-03-17 - 1.19.4 - fix(repository)
no changes to commit
## 2026-03-16 - 1.19.3 - fix(repo)
no changes to commit
## 2026-03-16 - 1.19.2 - fix(docs)
remove outdated UI screenshot assets from project documentation
- Deletes multiple PNG screenshots that documented previous dashboard, service form, and hello-world states.
- Reduces repository clutter by removing obsolete image assets no longer needed in docs.
## 2026-03-16 - 1.19.1 - fix(dashboard)
add updated dashboard screenshots for refresh and resource usage states
- Adds new dashboard screenshots covering post-refresh, resource usage, and populated data views.
- Updates visual assets to document current dashboard behavior and UI states.
## 2026-03-16 - 1.19.1 - fix(dashboard)
add aggregated resource usage stats to the dashboard
- Aggregate CPU, memory, and network stats across all running user and platform service containers in getSystemStatus
- Extend ISystemStatus.docker interface with cpuUsage, memoryUsage, memoryTotal, networkIn, networkOut fields
- Fix getContainerStats to properly handle Swarm service IDs by catching exceptions and falling back to label-based container lookup
- Wire dashboard resource usage card to display real aggregated data from the backend
## 2026-03-16 - 1.19.0 - feat(opsserver,web)
add real-time platform service log streaming to the dashboard
- stream running platform service container logs from the ops server to connected dashboard clients via TypedSocket
- parse Docker log timestamps and levels for both pushed and fetched platform service log entries
- enhance the platform service detail view with mapped statuses and predefined host, port, version, and config metadata
- add the typedsocket dependency and update the catalog package for dashboard support
## 2026-03-16 - 1.18.5 - fix(platform-services)
fix platform service detail view navigation and log display
- Add back button to platform service detail view for returning to services list
- Fix DOM lifecycle when switching between platform services (destroy and recreate dees-chart-log)
- Fix timestamp format for log entries to use ISO 8601 for dees-chart-log compatibility
- Clear previous stats/logs state before fetching new platform service data
## 2026-03-16 - 1.18.4 - fix(repo)
no changes to commit
## 2026-03-16 - 1.18.3 - fix(deps)
bump @serve.zone/catalog to ^2.6.1

Binary file not shown.

Before

Width:  |  Height:  |  Size: 59 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@serve.zone/onebox",
"version": "1.18.3",
"version": "1.22.2",
"exports": "./mod.ts",
"tasks": {
"test": "deno test --allow-all test/",
@@ -25,7 +25,8 @@
"@api.global/typedrequest": "npm:@api.global/typedrequest@^3.2.6",
"@api.global/typedserver": "npm:@api.global/typedserver@^8.3.1",
"@push.rocks/smartguard": "npm:@push.rocks/smartguard@^3.1.0",
"@push.rocks/smartjwt": "npm:@push.rocks/smartjwt@^2.2.1"
"@push.rocks/smartjwt": "npm:@push.rocks/smartjwt@^2.2.1",
"@api.global/typedsocket": "npm:@api.global/typedsocket@^4.1.2"
},
"compilerOptions": {
"lib": [

Binary file not shown.

Before

Width:  |  Height:  |  Size: 49 KiB

View File

@@ -1,6 +1,6 @@
{
"name": "@serve.zone/onebox",
"version": "1.18.3",
"version": "1.22.2",
"description": "Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers",
"main": "mod.ts",
"type": "module",
@@ -55,9 +55,10 @@
"packageManager": "pnpm@10.18.1+sha512.77a884a165cbba2d8d1c19e3b4880eee6d2fcabd0d879121e282196b80042351d5eb3ca0935fa599da1dc51265cc68816ad2bddd2a2de5ea9fdf92adbec7cd34",
"dependencies": {
"@api.global/typedrequest-interfaces": "^3.0.19",
"@api.global/typedsocket": "^4.1.2",
"@design.estate/dees-catalog": "^3.43.3",
"@design.estate/dees-element": "^2.1.6",
"@serve.zone/catalog": "^2.6.1"
"@serve.zone/catalog": "^2.8.0"
},
"devDependencies": {
"@git.zone/tsbundle": "^2.9.0",

229
pnpm-lock.yaml generated
View File

@@ -11,6 +11,9 @@ importers:
'@api.global/typedrequest-interfaces':
specifier: ^3.0.19
version: 3.0.19
'@api.global/typedsocket':
specifier: ^4.1.2
version: 4.1.2(@push.rocks/smartserve@2.0.1)
'@design.estate/dees-catalog':
specifier: ^3.43.3
version: 3.48.5(@tiptap/pm@2.27.2)
@@ -18,8 +21,8 @@ importers:
specifier: ^2.1.6
version: 2.2.3
'@serve.zone/catalog':
specifier: ^2.6.1
version: 2.6.1(@tiptap/pm@2.27.2)
specifier: ^2.8.0
version: 2.8.0(@tiptap/pm@2.27.2)
devDependencies:
'@git.zone/tsbundle':
specifier: ^2.9.0
@@ -60,8 +63,8 @@ packages:
'@cfworker/json-schema@4.1.1':
resolution: {integrity: sha512-gAmrUZSGtKc3AiBL71iNWxDsyUC5uMaKKGdvzYsBoTW/xi42JQHl7eKV2OYzCUqvc+D2RCcf7EXY2iCyFIk6og==}
'@cloudflare/workers-types@4.20260313.1':
resolution: {integrity: sha512-jMEeX3RKfOSVqqXRKr/ulgglcTloeMzSH3FdzIfqJHtvc12/ELKd5Ldsg8ZHahKX/4eRxYdw3kbzb8jLXbq/jQ==}
'@cloudflare/workers-types@4.20260317.1':
resolution: {integrity: sha512-+G4eVwyCpm8Au1ex8vQBCuA9wnwqetz4tPNRoB/53qvktERWBRMQnrtvC1k584yRE3emMThtuY0gWshvSJ++PQ==}
'@configvault.io/interfaces@1.0.17':
resolution: {integrity: sha512-bEcCUR2VBDJsTin8HQh8Uw/mlYl2v8A3jMIaQ+MTB9Hrqd6CZL2dL7iJdWyFl/3EIX+LDxWFR+Oq7liIq7w+1Q==}
@@ -373,74 +376,74 @@ packages:
'@module-federation/webpack-bundler-runtime@0.22.0':
resolution: {integrity: sha512-aM8gCqXu+/4wBmJtVeMeeMN5guw3chf+2i6HajKtQv7SJfxV/f4IyNQJUeUQu9HfiAZHjqtMV5Lvq/Lvh8LdyA==}
'@napi-rs/canvas-android-arm64@0.1.96':
resolution: {integrity: sha512-ew1sPrN3dGdZ3L4FoohPfnjq0f9/Jk7o+wP7HkQZokcXgIUD6FIyICEWGhMYzv53j63wUcPvZeAwgewX58/egg==}
'@napi-rs/canvas-android-arm64@0.1.97':
resolution: {integrity: sha512-V1c/WVw+NzH8vk7ZK/O8/nyBSCQimU8sfMsB/9qeSvdkGKNU7+mxy/bIF0gTgeBFmHpj30S4E9WHMSrxXGQuVQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [android]
'@napi-rs/canvas-darwin-arm64@0.1.96':
resolution: {integrity: sha512-Q/wOXZ5PzTqpdmA5eUOcegCf4Go/zz3aZ5DlzSeDpOjFmfwMKh8EzLAoweQ+mJVagcHQyzoJhaTEnrO68TNyNg==}
'@napi-rs/canvas-darwin-arm64@0.1.97':
resolution: {integrity: sha512-ok+SCEF4YejcxuJ9Rm+WWunHHpf2HmiPxfz6z1a/NFQECGXtsY7A4B8XocK1LmT1D7P174MzwPF9Wy3AUAwEPw==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [darwin]
'@napi-rs/canvas-darwin-x64@0.1.96':
resolution: {integrity: sha512-UrXiQz28tQEvGM1qvyptewOAfmUrrd5+wvi6Rzjj2VprZI8iZ2KIvBD2lTTG1bVF95AbeDeG7PJA0D9sLKaOFA==}
'@napi-rs/canvas-darwin-x64@0.1.97':
resolution: {integrity: sha512-PUP6e6/UGlclUvAQNnuXCcnkpdUou6VYZfQOQxExLp86epOylmiwLkqXIvpFmjoTEDmPmXrI+coL/9EFU1gKPA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [darwin]
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.96':
resolution: {integrity: sha512-I90ODxweD8aEP6XKU/NU+biso95MwCtQ2F46dUvhec1HesFi0tq/tAJkYic/1aBSiO/1kGKmSeD1B0duOHhEHQ==}
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.97':
resolution: {integrity: sha512-XyXH2L/cic8eTNtbrXCcvqHtMX/nEOxN18+7rMrAM2XtLYC/EB5s0wnO1FsLMWmK+04ZSLN9FBGipo7kpIkcOw==}
engines: {node: '>= 10'}
cpu: [arm]
os: [linux]
'@napi-rs/canvas-linux-arm64-gnu@0.1.96':
resolution: {integrity: sha512-Dx/0+RFV++w3PcRy+4xNXkghhXjA5d0Mw1bs95emn5Llinp1vihMaA6WJt3oYv2LAHc36+gnrhIBsPhUyI2SGw==}
'@napi-rs/canvas-linux-arm64-gnu@0.1.97':
resolution: {integrity: sha512-Kuq/M3djq0K8ktgz6nPlK7Ne5d4uWeDxPpyKWOjWDK2RIOhHVtLtyLiJw2fuldw7Vn4mhw05EZXCEr4Q76rs9w==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@napi-rs/canvas-linux-arm64-musl@0.1.96':
resolution: {integrity: sha512-UvOi7fii3IE2KDfEfhh8m+LpzSRvhGK7o1eho99M2M0HTik11k3GX+2qgVx9EtujN3/bhFFS1kSO3+vPMaJ0Mg==}
'@napi-rs/canvas-linux-arm64-musl@0.1.97':
resolution: {integrity: sha512-kKmSkQVnWeqg7qdsiXvYxKhAFuHz3tkBjW/zyQv5YKUPhotpaVhpBGv5LqCngzyuRV85SXoe+OFj+Tv0a0QXkQ==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
'@napi-rs/canvas-linux-riscv64-gnu@0.1.96':
resolution: {integrity: sha512-MBSukhGCQ5nRtf9NbFYWOU080yqkZU1PbuH4o1ROvB4CbPl12fchDR35tU83Wz8gWIM9JTn99lBn9DenPIv7Ig==}
'@napi-rs/canvas-linux-riscv64-gnu@0.1.97':
resolution: {integrity: sha512-Jc7I3A51jnEOIAXeLsN/M/+Z28LUeakcsXs07FLq9prXc0eYOtVwsDEv913Gr+06IRo34gJJVgT0TXvmz+N2VA==}
engines: {node: '>= 10'}
cpu: [riscv64]
os: [linux]
'@napi-rs/canvas-linux-x64-gnu@0.1.96':
resolution: {integrity: sha512-I/ccu2SstyKiV3HIeVzyBIWfrJo8cN7+MSQZPnabewWV6hfJ2nY7Df2WqOHmobBRUw84uGR6zfQHsUEio/m5Vg==}
'@napi-rs/canvas-linux-x64-gnu@0.1.97':
resolution: {integrity: sha512-iDUBe7AilfuBSRbSa8/IGX38Mf+iCSBqoVKLSQ5XaY2JLOaqz1TVyPFEyIck7wT6mRQhQt5sN6ogfjIDfi74tg==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@napi-rs/canvas-linux-x64-musl@0.1.96':
resolution: {integrity: sha512-H3uov7qnTl73GDT4h52lAqpJPsl1tIUyNPWJyhQ6gHakohNqqRq3uf80+NEpzcytKGEOENP1wX3yGwZxhjiWEQ==}
'@napi-rs/canvas-linux-x64-musl@0.1.97':
resolution: {integrity: sha512-AKLFd/v0Z5fvgqBDqhvqtAdx+fHMJ5t9JcUNKq4FIZ5WH+iegGm8HPdj00NFlCSnm83Fp3Ln8I2f7uq1aIiWaA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
'@napi-rs/canvas-win32-arm64-msvc@0.1.96':
resolution: {integrity: sha512-ATp6Y+djOjYtkfV/VRH7CZ8I1MEtkUQBmKUbuWw5zWEHHqfL0cEcInE4Cxgx7zkNAhEdBbnH8HMVrqNp+/gwxA==}
'@napi-rs/canvas-win32-arm64-msvc@0.1.97':
resolution: {integrity: sha512-u883Yr6A6fO7Vpsy9YE4FVCIxzzo5sO+7pIUjjoDLjS3vQaNMkVzx5bdIpEL+ob+gU88WDK4VcxYMZ6nmnoX9A==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [win32]
'@napi-rs/canvas-win32-x64-msvc@0.1.96':
resolution: {integrity: sha512-UYGdTltVd+Z8mcIuoqGmAXXUvwH5CLf2M6mIB5B0/JmX5J041jETjqtSYl7gN+aj3k1by/SG6sS0hAwCqyK7zw==}
'@napi-rs/canvas-win32-x64-msvc@0.1.97':
resolution: {integrity: sha512-sWtD2EE3fV0IzN+iiQUqr/Q1SwqWhs2O1FKItFlxtdDkikpEj5g7DKQpY3x55H/MAOnL8iomnlk3mcEeGiUMoQ==}
engines: {node: '>= 10'}
cpu: [x64]
os: [win32]
'@napi-rs/canvas@0.1.96':
resolution: {integrity: sha512-6NNmNxvoJKeucVjxaaRUt3La2i5jShgiAbaY3G/72s1Vp3U06XPrAIxkAjBxpDcamEn/t+WJ4OOlGmvILo4/Ew==}
'@napi-rs/canvas@0.1.97':
resolution: {integrity: sha512-8cFniXvrIEnVwuNSRCW9wirRZbHvrD3JVujdS2P5n5xiJZNZMOZcfOvJ1pb66c7jXMKHHglJEDVJGbm8XWFcXQ==}
engines: {node: '>= 10'}
'@napi-rs/wasm-runtime@1.0.7':
@@ -769,60 +772,60 @@ packages:
'@rolldown/pluginutils@1.0.0-beta.52':
resolution: {integrity: sha512-/L0htLJZbaZFL1g9OHOblTxbCYIGefErJjtYOwgl9ZqNx27P3L0SDfjhhHIss32gu5NWgnxuT2a2Hnnv6QGHKA==}
'@rspack/binding-darwin-arm64@1.7.8':
resolution: {integrity: sha512-KS6SRc+4VYRdX1cKr1j1HEuMNyEzt7onBS0rkenaiCRRYF0z4WNZNyZqRiuxgM3qZ3TISF7gdmgJQyd4ZB43ig==}
'@rspack/binding-darwin-arm64@1.7.9':
resolution: {integrity: sha512-64dgstte0If5czi9bA/cpOe0ryY6wC9AIQRtyJ3DlOF6Tt+y9cKkmUoGu3V+WYaYIZRT7HNk8V7kL8amVjFTYw==}
cpu: [arm64]
os: [darwin]
'@rspack/binding-darwin-x64@1.7.8':
resolution: {integrity: sha512-uyXSDKLg2CtqIJrsJDlCqQH80YIPsCUiTToJ59cXAG3v4eke0Qbiv6d/+pV0h/mc0u4inAaSkr5dD18zkMIghw==}
'@rspack/binding-darwin-x64@1.7.9':
resolution: {integrity: sha512-2QSLs3w4rLy4UUGVnIlkt6IlIKOzR1e0RPsq2FYQW6s3p9JrwRCtOeHohyh7EJSqF54dtfhe9UZSAwba3LqH1Q==}
cpu: [x64]
os: [darwin]
'@rspack/binding-linux-arm64-gnu@1.7.8':
resolution: {integrity: sha512-dD6gSHA18Uj0eqc1FCwwQ5IO5mIckrpYN4H4kPk9Pjau+1mxWvC4y5Lryz1Z8P/Rh1lnQ/wwGE0XL9nd80+LqQ==}
'@rspack/binding-linux-arm64-gnu@1.7.9':
resolution: {integrity: sha512-qhUGI/uVfvLmKWts4QkVHGL8yfUyJkblZs+OFD5Upa2y676EOsbQgWsCwX4xGB6Tv+TOzFP0SLh/UfO8ZfdE+w==}
cpu: [arm64]
os: [linux]
'@rspack/binding-linux-arm64-musl@1.7.8':
resolution: {integrity: sha512-m+uBi9mEVGkZ02PPOAYN2BSmmvc00XGa6v9CjV8qLpolpUXQIMzDNG+i1fD5SHp8LO+XWsZJOHypMsT0MzGTGw==}
'@rspack/binding-linux-arm64-musl@1.7.9':
resolution: {integrity: sha512-VjfmR1hgO9n3L6MaE5KG+DXSrrLVqHHOkVcOtS2LMq3bjMTwbBywY7ycymcLnX5KJsol8d3ZGYep6IfSOt3lFA==}
cpu: [arm64]
os: [linux]
'@rspack/binding-linux-x64-gnu@1.7.8':
resolution: {integrity: sha512-IAPp2L3yS33MAEkcGn/I1gO+a+WExJHXz2ZlRlL2oFCUGpYi2ZQHyAcJ3o2tJqkXmdqsTiN+OjEVMd/RcLa24g==}
'@rspack/binding-linux-x64-gnu@1.7.9':
resolution: {integrity: sha512-0kldV+3WTs/VYDWzxJ7K40hCW26IHtnk8xPK3whKoo1649rgeXXa0EdsU5P7hG8Ef5SWQjHHHZ/fuHYSO3Y6HA==}
cpu: [x64]
os: [linux]
'@rspack/binding-linux-x64-musl@1.7.8':
resolution: {integrity: sha512-do/QNzb4GWdXCsipblDcroqRDR3BFcbyzpZpAw/3j9ajvEqsOKpdHZpILT2NZX/VahhjqfqB3k0kJVt3uK7UYQ==}
'@rspack/binding-linux-x64-musl@1.7.9':
resolution: {integrity: sha512-Gi4872cFtc2d83FKATR6Qcf2VBa/tFCqffI/IwRRl6Hx5FulEBqx+tH7gAuRVF693vrbXNxK+FQ+k4iEsEJxrw==}
cpu: [x64]
os: [linux]
'@rspack/binding-wasm32-wasi@1.7.8':
resolution: {integrity: sha512-mHtgYTpdhx01i0XNKFYBZyCjtv9YUe/sDfpD1QK4FytPFB+1VpYnmZiaJIMM77VpNsjxGAqWhmUYxi2P6jWifw==}
'@rspack/binding-wasm32-wasi@1.7.9':
resolution: {integrity: sha512-5QEzqo6EaolpuZmK6w/mgSueorgGnnzp7dJaAvBj6ECFIg/aLXhXXmWCWbxt7Ws2gKvG5/PgaxDqbUxYL51juA==}
cpu: [wasm32]
'@rspack/binding-win32-arm64-msvc@1.7.8':
resolution: {integrity: sha512-Mkxg86F7kIT4pM9XvE/1LAGjK5NOQi/GJxKyyiKbUAeKM8XBUizVeNuvKR0avf2V5IDAIRXiH1SX8SpujMJteA==}
'@rspack/binding-win32-arm64-msvc@1.7.9':
resolution: {integrity: sha512-MMqvcrIc8aOqTuHjWkjdzilvoZ3Hv07Od0Foogiyq3JMudsS3Wcmh7T1dFerGg19MOJcRUeEkrg2NQOMOQ6xDA==}
cpu: [arm64]
os: [win32]
'@rspack/binding-win32-ia32-msvc@1.7.8':
resolution: {integrity: sha512-VmTOZ/X7M85lKFNwb2qJpCRzr4SgO42vucq/X7Uz1oSoTPAf8UUMNdi7BPnu+D4lgy6l8PwV804ZyHO3gGsvPA==}
'@rspack/binding-win32-ia32-msvc@1.7.9':
resolution: {integrity: sha512-4kYYS+NZ2CuNbKjq40yB/UEyB51o1PHj5wpr+Y943oOJXpEKWU2Q4vkF8VEohPEcnA9cKVotYCnqStme+02suA==}
cpu: [ia32]
os: [win32]
'@rspack/binding-win32-x64-msvc@1.7.8':
resolution: {integrity: sha512-BK0I4HAwp/yQLnmdJpUtGHcht3x11e9fZwyaiMzznznFc+Oypbf+FS5h+aBgpb53QnNkPpdG7MfAPoKItOcU8A==}
'@rspack/binding-win32-x64-msvc@1.7.9':
resolution: {integrity: sha512-1g+QyXXvs+838Un/4GaUvJfARDGHMCs15eXDYWBl5m/Skubyng8djWAgr6ag1+cVoJZXCPOvybTItcblWF3gbQ==}
cpu: [x64]
os: [win32]
'@rspack/binding@1.7.8':
resolution: {integrity: sha512-P4fbrQx5hRhAiC8TBTEMCTnNawrIzJLjWwAgrTwRxjgenpjNvimEkQBtSGrXOY+c+MV5Q74P+9wPvVWLKzRkQQ==}
'@rspack/binding@1.7.9':
resolution: {integrity: sha512-A56e0NdfNwbOSJoilMkxzaPuVYaKCNn1shuiwWnCIBmhV9ix1n9S1XvquDjkGyv+gCdR1+zfJBOa5DMB7htLHw==}
'@rspack/core@1.7.8':
resolution: {integrity: sha512-kT6yYo8xjKoDfM7iB8N9AmN9DJIlrs7UmQDbpTu1N4zaZocN1/t2fIAWOKjr5+3eJlZQR2twKZhDVHNLbLPjOw==}
'@rspack/core@1.7.9':
resolution: {integrity: sha512-VHuSKvRkuv42Ya+TxEGO0LE0r9+8P4tKGokmomj4R1f/Nu2vtS3yoaIMfC4fR6VuHGd3MZ+KTI0cNNwHfFcskw==}
engines: {node: '>=18.12.0'}
peerDependencies:
'@swc/helpers': '>=0.5.1'
@@ -836,8 +839,8 @@ packages:
'@sec-ant/readable-stream@0.4.1':
resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==}
'@serve.zone/catalog@2.6.1':
resolution: {integrity: sha512-8L78lyTvZPin3ELkaPOvUmeKuzqMNZunnyvF9LR2q4Eakxgc/KgbV0Q1M0fF0ENpGxQH+2kOWobwQsH2RfDmVA==}
'@serve.zone/catalog@2.8.0':
resolution: {integrity: sha512-p0ES14JwUoJE88DBtLSHcCfFPVa0vKhvHnQLaAY3OC15kfheNKidi1SwTFyMh43jj0ZNi4Lecc3W02wG6sasHw==}
'@tempfix/idb@8.0.3':
resolution: {integrity: sha512-hPJQKO7+oAIY+pDNImrZ9QAINbz9KmwT+yO4iRVwdPanok2YKpaUxdJzIvCUwY0YgAawlvYdffbLvRLV5hbs2g==}
@@ -1362,15 +1365,15 @@ packages:
fast-json-stable-stringify@2.1.0:
resolution: {integrity: sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==}
fast-xml-builder@1.1.3:
resolution: {integrity: sha512-1o60KoFw2+LWKQu3IdcfcFlGTW4dpqEWmjhYec6H82AYZU2TVBXep6tMl8Z1Y+wM+ZrzCwe3BZ9Vyd9N2rIvmg==}
fast-xml-builder@1.1.4:
resolution: {integrity: sha512-f2jhpN4Eccy0/Uz9csxh3Nu6q4ErKxf0XIsasomfOihuSUa3/xw6w8dnOtCDgEItQFJG8KyXPzQXzcODDrrbOg==}
fast-xml-parser@4.5.4:
resolution: {integrity: sha512-jE8ugADnYOBsu1uaoayVl1tVKAMNOXyjwvv2U6udEA2ORBhDooJDWoGxTkhd4Qn4yh59JVVt/pKXtjPwx9OguQ==}
hasBin: true
fast-xml-parser@5.5.5:
resolution: {integrity: sha512-NLY+V5NNbdmiEszx9n14mZBseJTC50bRq1VHsaxOmR72JDuZt+5J1Co+dC/4JPnyq+WrIHNM69r0sqf7BMb3Mg==}
fast-xml-parser@5.5.6:
resolution: {integrity: sha512-3+fdZyBRVg29n4rXP0joHthhcHdPUHaIC16cuyyd1iLsuaO6Vea36MPrxgAzbZna8lhvZeRL8Bc9GP56/J9xEw==}
hasBin: true
fault@2.0.1:
@@ -2337,7 +2340,7 @@ snapshots:
'@api.global/typedrequest': 3.3.0
'@api.global/typedrequest-interfaces': 3.0.19
'@api.global/typedsocket': 4.1.2(@push.rocks/smartserve@2.0.1)
'@cloudflare/workers-types': 4.20260313.1
'@cloudflare/workers-types': 4.20260317.1
'@design.estate/dees-catalog': 3.48.5(@tiptap/pm@2.27.2)
'@design.estate/dees-comms': 1.0.30
'@push.rocks/lik': 6.3.1
@@ -2397,7 +2400,7 @@ snapshots:
'@cfworker/json-schema@4.1.1': {}
'@cloudflare/workers-types@4.20260313.1': {}
'@cloudflare/workers-types@4.20260317.1': {}
'@configvault.io/interfaces@1.0.17':
dependencies:
@@ -2620,7 +2623,7 @@ snapshots:
'@push.rocks/smartpath': 6.0.0
'@push.rocks/smartpromise': 4.2.3
'@push.rocks/smartspawn': 3.0.3
'@rspack/core': 1.7.8
'@rspack/core': 1.7.9
'@types/html-minifier': 4.0.6
esbuild: 0.27.4
html-minifier: 4.0.0
@@ -2814,52 +2817,52 @@ snapshots:
'@module-federation/runtime': 0.22.0
'@module-federation/sdk': 0.22.0
'@napi-rs/canvas-android-arm64@0.1.96':
'@napi-rs/canvas-android-arm64@0.1.97':
optional: true
'@napi-rs/canvas-darwin-arm64@0.1.96':
'@napi-rs/canvas-darwin-arm64@0.1.97':
optional: true
'@napi-rs/canvas-darwin-x64@0.1.96':
'@napi-rs/canvas-darwin-x64@0.1.97':
optional: true
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.96':
'@napi-rs/canvas-linux-arm-gnueabihf@0.1.97':
optional: true
'@napi-rs/canvas-linux-arm64-gnu@0.1.96':
'@napi-rs/canvas-linux-arm64-gnu@0.1.97':
optional: true
'@napi-rs/canvas-linux-arm64-musl@0.1.96':
'@napi-rs/canvas-linux-arm64-musl@0.1.97':
optional: true
'@napi-rs/canvas-linux-riscv64-gnu@0.1.96':
'@napi-rs/canvas-linux-riscv64-gnu@0.1.97':
optional: true
'@napi-rs/canvas-linux-x64-gnu@0.1.96':
'@napi-rs/canvas-linux-x64-gnu@0.1.97':
optional: true
'@napi-rs/canvas-linux-x64-musl@0.1.96':
'@napi-rs/canvas-linux-x64-musl@0.1.97':
optional: true
'@napi-rs/canvas-win32-arm64-msvc@0.1.96':
'@napi-rs/canvas-win32-arm64-msvc@0.1.97':
optional: true
'@napi-rs/canvas-win32-x64-msvc@0.1.96':
'@napi-rs/canvas-win32-x64-msvc@0.1.97':
optional: true
'@napi-rs/canvas@0.1.96':
'@napi-rs/canvas@0.1.97':
optionalDependencies:
'@napi-rs/canvas-android-arm64': 0.1.96
'@napi-rs/canvas-darwin-arm64': 0.1.96
'@napi-rs/canvas-darwin-x64': 0.1.96
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.96
'@napi-rs/canvas-linux-arm64-gnu': 0.1.96
'@napi-rs/canvas-linux-arm64-musl': 0.1.96
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.96
'@napi-rs/canvas-linux-x64-gnu': 0.1.96
'@napi-rs/canvas-linux-x64-musl': 0.1.96
'@napi-rs/canvas-win32-arm64-msvc': 0.1.96
'@napi-rs/canvas-win32-x64-msvc': 0.1.96
'@napi-rs/canvas-android-arm64': 0.1.97
'@napi-rs/canvas-darwin-arm64': 0.1.97
'@napi-rs/canvas-darwin-x64': 0.1.97
'@napi-rs/canvas-linux-arm-gnueabihf': 0.1.97
'@napi-rs/canvas-linux-arm64-gnu': 0.1.97
'@napi-rs/canvas-linux-arm64-musl': 0.1.97
'@napi-rs/canvas-linux-riscv64-gnu': 0.1.97
'@napi-rs/canvas-linux-x64-gnu': 0.1.97
'@napi-rs/canvas-linux-x64-musl': 0.1.97
'@napi-rs/canvas-win32-arm64-msvc': 0.1.97
'@napi-rs/canvas-win32-x64-msvc': 0.1.97
optional: true
'@napi-rs/wasm-runtime@1.0.7':
@@ -3273,7 +3276,7 @@ snapshots:
'@push.rocks/smartxml@2.0.0':
dependencies:
fast-xml-parser: 5.5.5
fast-xml-parser: 5.5.6
'@push.rocks/smartyaml@2.0.5':
dependencies:
@@ -3419,62 +3422,62 @@ snapshots:
'@rolldown/pluginutils@1.0.0-beta.52': {}
'@rspack/binding-darwin-arm64@1.7.8':
'@rspack/binding-darwin-arm64@1.7.9':
optional: true
'@rspack/binding-darwin-x64@1.7.8':
'@rspack/binding-darwin-x64@1.7.9':
optional: true
'@rspack/binding-linux-arm64-gnu@1.7.8':
'@rspack/binding-linux-arm64-gnu@1.7.9':
optional: true
'@rspack/binding-linux-arm64-musl@1.7.8':
'@rspack/binding-linux-arm64-musl@1.7.9':
optional: true
'@rspack/binding-linux-x64-gnu@1.7.8':
'@rspack/binding-linux-x64-gnu@1.7.9':
optional: true
'@rspack/binding-linux-x64-musl@1.7.8':
'@rspack/binding-linux-x64-musl@1.7.9':
optional: true
'@rspack/binding-wasm32-wasi@1.7.8':
'@rspack/binding-wasm32-wasi@1.7.9':
dependencies:
'@napi-rs/wasm-runtime': 1.0.7
optional: true
'@rspack/binding-win32-arm64-msvc@1.7.8':
'@rspack/binding-win32-arm64-msvc@1.7.9':
optional: true
'@rspack/binding-win32-ia32-msvc@1.7.8':
'@rspack/binding-win32-ia32-msvc@1.7.9':
optional: true
'@rspack/binding-win32-x64-msvc@1.7.8':
'@rspack/binding-win32-x64-msvc@1.7.9':
optional: true
'@rspack/binding@1.7.8':
'@rspack/binding@1.7.9':
optionalDependencies:
'@rspack/binding-darwin-arm64': 1.7.8
'@rspack/binding-darwin-x64': 1.7.8
'@rspack/binding-linux-arm64-gnu': 1.7.8
'@rspack/binding-linux-arm64-musl': 1.7.8
'@rspack/binding-linux-x64-gnu': 1.7.8
'@rspack/binding-linux-x64-musl': 1.7.8
'@rspack/binding-wasm32-wasi': 1.7.8
'@rspack/binding-win32-arm64-msvc': 1.7.8
'@rspack/binding-win32-ia32-msvc': 1.7.8
'@rspack/binding-win32-x64-msvc': 1.7.8
'@rspack/binding-darwin-arm64': 1.7.9
'@rspack/binding-darwin-x64': 1.7.9
'@rspack/binding-linux-arm64-gnu': 1.7.9
'@rspack/binding-linux-arm64-musl': 1.7.9
'@rspack/binding-linux-x64-gnu': 1.7.9
'@rspack/binding-linux-x64-musl': 1.7.9
'@rspack/binding-wasm32-wasi': 1.7.9
'@rspack/binding-win32-arm64-msvc': 1.7.9
'@rspack/binding-win32-ia32-msvc': 1.7.9
'@rspack/binding-win32-x64-msvc': 1.7.9
'@rspack/core@1.7.8':
'@rspack/core@1.7.9':
dependencies:
'@module-federation/runtime-tools': 0.22.0
'@rspack/binding': 1.7.8
'@rspack/binding': 1.7.9
'@rspack/lite-tapable': 1.1.0
'@rspack/lite-tapable@1.1.0': {}
'@sec-ant/readable-stream@0.4.1': {}
'@serve.zone/catalog@2.6.1(@tiptap/pm@2.27.2)':
'@serve.zone/catalog@2.8.0(@tiptap/pm@2.27.2)':
dependencies:
'@design.estate/dees-catalog': 3.48.5(@tiptap/pm@2.27.2)
'@design.estate/dees-domtools': 2.5.1
@@ -4004,7 +4007,7 @@ snapshots:
fast-json-stable-stringify@2.1.0: {}
fast-xml-builder@1.1.3:
fast-xml-builder@1.1.4:
dependencies:
path-expression-matcher: 1.1.3
@@ -4012,9 +4015,9 @@ snapshots:
dependencies:
strnum: 1.1.2
fast-xml-parser@5.5.5:
fast-xml-parser@5.5.6:
dependencies:
fast-xml-builder: 1.1.3
fast-xml-builder: 1.1.4
path-expression-matcher: 1.1.3
strnum: 2.2.0
@@ -4736,7 +4739,7 @@ snapshots:
pdfjs-dist@4.10.38:
optionalDependencies:
'@napi-rs/canvas': 0.1.96
'@napi-rs/canvas': 0.1.97
peek-readable@5.4.2: {}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/onebox',
version: '1.18.3',
version: '1.22.2',
description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers'
}

View File

@@ -596,18 +596,26 @@ export class OneboxDockerManager {
async getContainerStats(containerID: string): Promise<IContainerStats | null> {
try {
// Try to get container directly first
let container = await this.dockerClient!.getContainerById(containerID);
let container: any = null;
try {
container = await this.dockerClient!.getContainerById(containerID);
} catch {
// Container not found by ID — might be a Swarm service ID
}
// If not found, it might be a service ID - try to get the actual container ID
if (!container) {
const serviceContainerId = await this.getContainerIdForService(containerID);
if (serviceContainerId) {
container = await this.dockerClient!.getContainerById(serviceContainerId);
try {
container = await this.dockerClient!.getContainerById(serviceContainerId);
} catch {
// Service container also not found
}
}
}
if (!container) {
// Container/service not found
return null;
}
@@ -849,7 +857,23 @@ export class OneboxDockerManager {
cmd: string[]
): Promise<{ stdout: string; stderr: string; exitCode: number }> {
try {
const container = await this.dockerClient!.getContainerById(containerID);
let container: any = null;
try {
container = await this.dockerClient!.getContainerById(containerID);
} catch {
// Not a direct container ID — try Swarm service lookup
}
if (!container) {
const serviceContainerId = await this.getContainerIdForService(containerID);
if (serviceContainerId) {
try {
container = await this.dockerClient!.getContainerById(serviceContainerId);
} catch {
// Service container also not found
}
}
}
if (!container) {
throw new Error(`Container not found: ${containerID}`);
@@ -1011,7 +1035,23 @@ export class OneboxDockerManager {
callback: (line: string, isError: boolean) => void
): Promise<void> {
try {
const container = await this.dockerClient!.getContainerById(containerID);
let container: any = null;
try {
container = await this.dockerClient!.getContainerById(containerID);
} catch {
// Not a direct container ID — try Swarm service lookup
}
if (!container) {
const serviceContainerId = await this.getContainerIdForService(containerID);
if (serviceContainerId) {
try {
container = await this.dockerClient!.getContainerById(serviceContainerId);
} catch {
// Service container also not found
}
}
}
if (!container) {
throw new Error(`Container not found: ${containerID}`);

View File

@@ -291,10 +291,65 @@ export class Onebox {
// Sort expiring domains by days remaining (ascending)
expiringDomains.sort((a, b) => a.daysRemaining - b.daysRemaining);
// Aggregate resource usage across all running service containers
let totalCpu = 0;
let totalMemoryUsed = 0;
let totalMemoryLimit = 0;
let totalNetworkIn = 0;
let totalNetworkOut = 0;
if (dockerRunning) {
const allServices = this.services.listServices();
const runningUserServices = allServices.filter((s) => s.status === 'running' && s.containerID);
logger.debug(`Resource stats: ${runningUserServices.length} running user services`);
const statsPromises = runningUserServices
.map((s) => {
logger.debug(`Fetching stats for user service: ${s.name} (${s.containerID})`);
return this.docker.getContainerStats(s.containerID!).catch((err) => {
logger.debug(`Stats failed for ${s.name}: ${(err as Error).message}`);
return null;
});
});
// Also get stats for platform service containers
const allPlatformServices = this.platformServices.getAllPlatformServices();
const runningPlatformServices = allPlatformServices.filter((s) => s.status === 'running' && s.containerId);
logger.debug(`Resource stats: ${runningPlatformServices.length} running platform services`);
const platformStatsPromises = runningPlatformServices
.map((s) => {
logger.debug(`Fetching stats for platform service: ${s.type} (${s.containerId})`);
return this.docker.getContainerStats(s.containerId!).catch((err) => {
logger.debug(`Stats failed for ${s.type}: ${(err as Error).message}`);
return null;
});
});
const allStats = await Promise.all([...statsPromises, ...platformStatsPromises]);
let successCount = 0;
for (const stats of allStats) {
if (stats) {
successCount++;
totalCpu += stats.cpuPercent;
totalMemoryUsed += stats.memoryUsed;
totalMemoryLimit = Math.max(totalMemoryLimit, stats.memoryLimit);
totalNetworkIn += stats.networkRx;
totalNetworkOut += stats.networkTx;
}
}
logger.debug(`Resource stats: ${successCount}/${allStats.length} containers returned stats. CPU: ${totalCpu}, Mem: ${totalMemoryUsed}`);
}
return {
docker: {
running: dockerRunning,
version: dockerRunning ? await this.docker.getDockerVersion() : null,
cpuUsage: Math.round(totalCpu * 10) / 10,
memoryUsage: totalMemoryUsed,
memoryTotal: totalMemoryLimit,
networkIn: totalNetworkIn,
networkOut: totalNetworkOut,
},
reverseProxy: proxyStatus,
dns: {

View File

@@ -23,6 +23,7 @@ export class OpsServer {
public schedulesHandler!: handlers.SchedulesHandler;
public settingsHandler!: handlers.SettingsHandler;
public logsHandler!: handlers.LogsHandler;
public workspaceHandler!: handlers.WorkspaceHandler;
constructor(oneboxRef: Onebox) {
this.oneboxRef = oneboxRef;
@@ -63,6 +64,7 @@ export class OpsServer {
this.schedulesHandler = new handlers.SchedulesHandler(this);
this.settingsHandler = new handlers.SettingsHandler(this);
this.logsHandler = new handlers.LogsHandler(this);
this.workspaceHandler = new handlers.WorkspaceHandler(this);
logger.success('OpsServer TypedRequest handlers initialized');
}

View File

@@ -11,3 +11,4 @@ export * from './backups.handler.ts';
export * from './schedules.handler.ts';
export * from './settings.handler.ts';
export * from './logs.handler.ts';
export * from './workspace.handler.ts';

View File

@@ -6,10 +6,128 @@ import { requireValidIdentity } from '../helpers/guards.ts';
export class PlatformHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
private activeLogStreams = new Map<string, boolean>();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
this.startLogStreaming();
}
/**
* Start streaming logs from all running containers (platform + user services)
* and push new entries to connected dashboard clients via TypedSocket
*/
private async startLogStreaming(): Promise<void> {
const checkAndStream = async () => {
// Stream platform service containers
const platformServices = this.opsServerRef.oneboxRef.database.getAllPlatformServices();
for (const service of platformServices) {
if (service.status !== 'running' || !service.containerId) continue;
const key = `platform:${service.type}`;
if (this.activeLogStreams.has(key)) continue;
this.activeLogStreams.set(key, true);
logger.info(`Starting log stream for platform service: ${service.type}`);
try {
await this.opsServerRef.oneboxRef.docker.streamContainerLogs(
service.containerId,
(line: string, isError: boolean) => {
this.pushPlatformLogToClients(service.type as interfaces.data.TPlatformServiceType, line, isError);
}
);
} catch (err) {
logger.warn(`Log stream failed for ${service.type}: ${(err as Error).message}`);
this.activeLogStreams.delete(key);
}
}
// Stream user service containers
const userServices = this.opsServerRef.oneboxRef.services.listServices();
for (const service of userServices) {
if (service.status !== 'running' || !service.containerID) continue;
const key = `service:${service.name}`;
if (this.activeLogStreams.has(key)) continue;
this.activeLogStreams.set(key, true);
logger.info(`Starting log stream for user service: ${service.name}`);
try {
await this.opsServerRef.oneboxRef.docker.streamContainerLogs(
service.containerID,
(line: string, isError: boolean) => {
this.pushServiceLogToClients(service.name, line, isError);
}
);
} catch (err) {
logger.warn(`Log stream failed for ${service.name}: ${(err as Error).message}`);
this.activeLogStreams.delete(key);
}
}
};
// Initial check after a short delay (let services start first)
setTimeout(() => checkAndStream(), 5000);
// Re-check periodically for newly started services
setInterval(() => checkAndStream(), 15000);
}
private parseLogLine(line: string, isError: boolean): { timestamp: string; level: string; message: string } {
const tsMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)/);
const timestamp = tsMatch ? tsMatch[1] : new Date().toISOString();
const message = tsMatch ? tsMatch[2] : line;
const msgLower = message.toLowerCase();
const level = isError || msgLower.includes('error') || msgLower.includes('fatal')
? 'error'
: msgLower.includes('warn')
? 'warn'
: 'info';
return { timestamp, level, message };
}
private pushPlatformLogToClients(
serviceType: interfaces.data.TPlatformServiceType,
line: string,
isError: boolean,
): void {
const typedsocket = (this.opsServerRef.server as any)?.typedserver?.typedsocket;
if (!typedsocket) return;
const entry = this.parseLogLine(line, isError);
typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard')
.then((connections: any[]) => {
for (const conn of connections) {
typedsocket.createTypedRequest<interfaces.requests.IReq_PushPlatformServiceLog>(
'pushPlatformServiceLog',
conn,
).fire({ serviceType, entry }).catch(() => {});
}
})
.catch(() => {});
}
private pushServiceLogToClients(
serviceName: string,
line: string,
isError: boolean,
): void {
const typedsocket = (this.opsServerRef.server as any)?.typedserver?.typedsocket;
if (!typedsocket) return;
const entry = this.parseLogLine(line, isError);
typedsocket.findAllTargetConnectionsByTag('role', 'ops_dashboard')
.then((connections: any[]) => {
for (const conn of connections) {
typedsocket.createTypedRequest<interfaces.requests.IReq_PushServiceLog>(
'pushServiceLog',
conn,
).fire({ serviceName, entry }).catch(() => {});
}
})
.catch(() => {});
}
private registerHandlers(): void {
@@ -186,13 +304,18 @@ export class PlatformHandler {
.filter((line: string) => line.trim());
const logs = logLines.map((line: string, index: number) => {
const isError = line.toLowerCase().includes('error') || line.toLowerCase().includes('fatal');
const isWarn = line.toLowerCase().includes('warn');
// Try to parse Docker timestamp from beginning of line
const tsMatch = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)/);
const timestamp = tsMatch ? new Date(tsMatch[1]).getTime() : Date.now();
const message = tsMatch ? tsMatch[2] : line;
const msgLower = message.toLowerCase();
const isError = msgLower.includes('error') || msgLower.includes('fatal');
const isWarn = msgLower.includes('warn');
return {
id: index,
serviceId: 0,
timestamp: Date.now(),
message: line,
timestamp,
message,
level: (isError ? 'error' : isWarn ? 'warn' : 'info') as 'info' | 'warn' | 'error' | 'debug',
source: 'stdout' as const,
};

View File

@@ -0,0 +1,181 @@
import * as plugins from '../../plugins.ts';
import { logger } from '../../logging.ts';
import type { OpsServer } from '../classes.opsserver.ts';
import * as interfaces from '../../../ts_interfaces/index.ts';
import { requireValidIdentity } from '../helpers/guards.ts';
import { getErrorMessage } from '../../utils/error.ts';
export class WorkspaceHandler {
public typedrouter = new plugins.typedrequest.TypedRouter();
constructor(private opsServerRef: OpsServer) {
this.opsServerRef.typedrouter.addTypedRouter(this.typedrouter);
this.registerHandlers();
}
/**
* Resolve a service name to a container ID (handling Swarm service IDs)
*/
private async resolveContainerId(serviceName: string): Promise<string> {
const service = this.opsServerRef.oneboxRef.services.getService(serviceName);
if (!service || !service.containerID) {
throw new plugins.typedrequest.TypedResponseError(`Service not found or has no container: ${serviceName}`);
}
return service.containerID;
}
private registerHandlers(): void {
// Read file from container
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadFile>(
'workspaceReadFile',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const containerId = await this.resolveContainerId(dataArg.serviceName);
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
containerId,
['cat', dataArg.path],
);
if (result.exitCode !== 0) {
throw new plugins.typedrequest.TypedResponseError(`Failed to read file: ${result.stderr || 'File not found'}`);
}
return { content: result.stdout };
},
),
);
// Write file to container
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceWriteFile>(
'workspaceWriteFile',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const containerId = await this.resolveContainerId(dataArg.serviceName);
// Use sh -c with printf to write content (handles special characters)
const escaped = dataArg.content.replace(/'/g, "'\\''");
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
containerId,
['sh', '-c', `printf '%s' '${escaped}' > ${dataArg.path}`],
);
if (result.exitCode !== 0) {
throw new plugins.typedrequest.TypedResponseError(`Failed to write file: ${result.stderr}`);
}
return {};
},
),
);
// Read directory from container
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceReadDir>(
'workspaceReadDir',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const containerId = await this.resolveContainerId(dataArg.serviceName);
// Use ls with -1 -F to get entries with type indicators (/ for dirs)
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
containerId,
['ls', '-1', '-F', dataArg.path],
);
if (result.exitCode !== 0) {
throw new plugins.typedrequest.TypedResponseError(`Failed to read directory: ${result.stderr}`);
}
const entries = result.stdout
.split('\n')
.filter((line) => line.trim())
.map((line) => {
const isDir = line.endsWith('/');
const name = isDir ? line.slice(0, -1) : line.replace(/[*@=|]$/, '');
const basePath = dataArg.path.endsWith('/') ? dataArg.path : dataArg.path + '/';
return {
type: (isDir ? 'directory' : 'file') as 'file' | 'directory',
name,
path: basePath + name,
};
});
return { entries };
},
),
);
// Create directory in container
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceMkdir>(
'workspaceMkdir',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const containerId = await this.resolveContainerId(dataArg.serviceName);
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
containerId,
['mkdir', '-p', dataArg.path],
);
if (result.exitCode !== 0) {
throw new plugins.typedrequest.TypedResponseError(`Failed to create directory: ${result.stderr}`);
}
return {};
},
),
);
// Remove file/directory from container
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceRm>(
'workspaceRm',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const containerId = await this.resolveContainerId(dataArg.serviceName);
const args = dataArg.recursive ? ['rm', '-rf', dataArg.path] : ['rm', '-f', dataArg.path];
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
containerId,
args,
);
if (result.exitCode !== 0) {
throw new plugins.typedrequest.TypedResponseError(`Failed to remove: ${result.stderr}`);
}
return {};
},
),
);
// Check if path exists in container
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExists>(
'workspaceExists',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const containerId = await this.resolveContainerId(dataArg.serviceName);
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
containerId,
['test', '-e', dataArg.path],
);
return { exists: result.exitCode === 0 };
},
),
);
// Execute a command in the container (non-interactive)
this.typedrouter.addTypedHandler(
new plugins.typedrequest.TypedHandler<interfaces.requests.IReq_WorkspaceExec>(
'workspaceExec',
async (dataArg) => {
await requireValidIdentity(this.opsServerRef.adminHandler, dataArg);
const containerId = await this.resolveContainerId(dataArg.serviceName);
const cmd = dataArg.args
? [dataArg.command, ...dataArg.args]
: [dataArg.command];
const result = await this.opsServerRef.oneboxRef.docker.execInContainer(
containerId,
cmd,
);
return {
stdout: result.stdout,
stderr: result.stderr,
exitCode: result.exitCode,
};
},
),
);
logger.info('Workspace handler registered');
}
}

File diff suppressed because one or more lines are too long

View File

@@ -8,6 +8,11 @@ export interface ISystemStatus {
docker: {
running: boolean;
version: unknown;
cpuUsage: number;
memoryUsage: number;
memoryTotal: number;
networkIn: number;
networkOut: number;
};
reverseProxy: {
http: { running: boolean; port: number };

View File

@@ -11,3 +11,4 @@ export * from './backups.ts';
export * from './backup-schedules.ts';
export * from './settings.ts';
export * from './logs.ts';
export * from './workspace.ts';

View File

@@ -84,3 +84,19 @@ export interface IReq_GetPlatformServiceLogs extends plugins.typedrequestInterfa
logs: data.ILogEntry[];
};
}
export interface IReq_PushPlatformServiceLog extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_PushPlatformServiceLog
> {
method: 'pushPlatformServiceLog';
request: {
serviceType: data.TPlatformServiceType;
entry: {
timestamp: string;
level: string;
message: string;
};
};
response: {};
}

View File

@@ -212,3 +212,19 @@ export interface IReq_GetServiceBackupSchedules extends plugins.typedrequestInte
schedules: data.IBackupSchedule[];
};
}
export interface IReq_PushServiceLog extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_PushServiceLog
> {
method: 'pushServiceLog';
request: {
serviceName: string;
entry: {
timestamp: string;
level: string;
message: string;
};
};
response: {};
}

View File

@@ -0,0 +1,106 @@
import * as plugins from '../plugins.ts';
import * as data from '../data/index.ts';
export interface IReq_WorkspaceReadFile extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_WorkspaceReadFile
> {
method: 'workspaceReadFile';
request: {
identity: data.IIdentity;
serviceName: string;
path: string;
};
response: {
content: string;
};
}
export interface IReq_WorkspaceWriteFile extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_WorkspaceWriteFile
> {
method: 'workspaceWriteFile';
request: {
identity: data.IIdentity;
serviceName: string;
path: string;
content: string;
};
response: {};
}
export interface IReq_WorkspaceReadDir extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_WorkspaceReadDir
> {
method: 'workspaceReadDir';
request: {
identity: data.IIdentity;
serviceName: string;
path: string;
};
response: {
entries: Array<{ type: 'file' | 'directory'; name: string; path: string }>;
};
}
export interface IReq_WorkspaceMkdir extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_WorkspaceMkdir
> {
method: 'workspaceMkdir';
request: {
identity: data.IIdentity;
serviceName: string;
path: string;
};
response: {};
}
export interface IReq_WorkspaceRm extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_WorkspaceRm
> {
method: 'workspaceRm';
request: {
identity: data.IIdentity;
serviceName: string;
path: string;
recursive?: boolean;
};
response: {};
}
export interface IReq_WorkspaceExists extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_WorkspaceExists
> {
method: 'workspaceExists';
request: {
identity: data.IIdentity;
serviceName: string;
path: string;
};
response: {
exists: boolean;
};
}
export interface IReq_WorkspaceExec extends plugins.typedrequestInterfaces.implementsTR<
plugins.typedrequestInterfaces.ITypedRequest,
IReq_WorkspaceExec
> {
method: 'workspaceExec';
request: {
identity: data.IIdentity;
serviceName: string;
command: string;
args?: string[];
};
response: {
stdout: string;
stderr: string;
exitCode: number;
};
}

View File

@@ -3,6 +3,6 @@
*/
export const commitinfo = {
name: '@serve.zone/onebox',
version: '1.18.3',
version: '1.22.2',
description: 'Self-hosted container platform with automatic SSL and DNS - a mini Heroku for single servers'
}

View File

@@ -58,6 +58,7 @@ export interface IUiState {
activeView: string;
autoRefresh: boolean;
refreshInterval: number;
pendingAppTemplate?: any;
}
// ============================================================================
@@ -961,3 +962,104 @@ const startAutoRefresh = () => {
uiStatePart.select((s) => s).subscribe(() => startAutoRefresh());
loginStatePart.select((s) => s).subscribe(() => startAutoRefresh());
startAutoRefresh();
// ============================================================================
// TypedSocket — real-time server push (logs, events)
// ============================================================================
let socketClient: InstanceType<typeof plugins.typedsocket.TypedSocket> | null = null;
const socketRouter = new plugins.domtools.plugins.typedrequest.TypedRouter();
// Handle server-pushed platform service log entries
socketRouter.addTypedHandler(
new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushPlatformServiceLog>(
'pushPlatformServiceLog',
async (dataArg) => {
const state = servicesStatePart.getState();
const entry: interfaces.data.ILogEntry = {
id: state.currentPlatformServiceLogs.length,
serviceId: 0,
timestamp: new Date(dataArg.entry.timestamp).getTime(),
message: dataArg.entry.message,
level: dataArg.entry.level as 'info' | 'warn' | 'error' | 'debug',
source: 'stdout',
};
const updated = [...state.currentPlatformServiceLogs, entry];
// Cap at 2000 entries
if (updated.length > 2000) {
updated.splice(0, updated.length - 2000);
}
servicesStatePart.setState({
...state,
currentPlatformServiceLogs: updated,
});
return {};
},
),
);
// Handle server-pushed user service log entries
socketRouter.addTypedHandler(
new plugins.domtools.plugins.typedrequest.TypedHandler<interfaces.requests.IReq_PushServiceLog>(
'pushServiceLog',
async (dataArg) => {
const state = servicesStatePart.getState();
// Only append if we're currently viewing this service
if (!state.currentService || state.currentService.name !== dataArg.serviceName) {
return {};
}
const entry: interfaces.data.ILogEntry = {
id: state.currentServiceLogs.length,
serviceId: 0,
timestamp: new Date(dataArg.entry.timestamp).getTime(),
message: dataArg.entry.message,
level: dataArg.entry.level as 'info' | 'warn' | 'error' | 'debug',
source: 'stdout',
};
const updated = [...state.currentServiceLogs, entry];
if (updated.length > 2000) {
updated.splice(0, updated.length - 2000);
}
servicesStatePart.setState({
...state,
currentServiceLogs: updated,
});
return {};
},
),
);
async function connectSocket() {
if (socketClient) return;
try {
socketClient = await plugins.typedsocket.TypedSocket.createClient(
socketRouter,
plugins.typedsocket.TypedSocket.useWindowLocationOriginUrl(),
);
await socketClient.setTag('role', 'ops_dashboard');
console.log('TypedSocket dashboard connection established');
} catch (err) {
console.error('TypedSocket connection failed:', err);
socketClient = null;
}
}
async function disconnectSocket() {
if (socketClient) {
try {
await socketClient.disconnect();
} catch {
// ignore disconnect errors
}
socketClient = null;
}
}
// Connect socket when logged in, disconnect when logged out
loginStatePart.select((s) => s).subscribe((loginState) => {
if (loginState.isLoggedIn) {
connectSocket();
} else {
disconnectSocket();
}
});

View File

@@ -38,6 +38,7 @@ export class ObAppShell extends DeesElement {
private viewTabs = [
{ name: 'Dashboard', iconName: 'lucide:layoutDashboard', element: (async () => (await import('./ob-view-dashboard.js')).ObViewDashboard)() },
{ name: 'App Store', iconName: 'lucide:store', element: (async () => (await import('./ob-view-appstore.js')).ObViewAppStore)() },
{ name: 'Services', iconName: 'lucide:boxes', element: (async () => (await import('./ob-view-services.js')).ObViewServices)() },
{ name: 'Network', iconName: 'lucide:network', element: (async () => (await import('./ob-view-network.js')).ObViewNetwork)() },
{ name: 'Registries', iconName: 'lucide:package', element: (async () => (await import('./ob-view-registries.js')).ObViewRegistries)() },

View File

@@ -0,0 +1,224 @@
import * as plugins from '../plugins.js';
import * as shared from './shared/index.js';
import * as appstate from '../appstate.js';
import * as interfaces from '../../ts_interfaces/index.js';
import {
DeesElement,
customElement,
html,
state,
css,
cssManager,
type TemplateResult,
} from '@design.estate/dees-element';
// App template definitions — curated Docker apps
const appTemplates = [
{
id: 'nginx',
name: 'Nginx',
description: 'High-performance web server and reverse proxy. Lightweight, fast, and battle-tested.',
category: 'Web Server',
iconName: 'globe',
image: 'nginx:alpine',
port: 80,
},
{
id: 'wordpress',
name: 'WordPress',
description: 'The world\'s most popular content management system. Powers over 40% of the web.',
category: 'CMS',
iconName: 'file-text',
image: 'wordpress:latest',
port: 80,
enableMongoDB: false,
envVars: [
{ key: 'WORDPRESS_DB_HOST', value: '', description: 'Database host', required: true },
{ key: 'WORDPRESS_DB_USER', value: 'wordpress', description: 'Database user' },
{ key: 'WORDPRESS_DB_PASSWORD', value: '', description: 'Database password', required: true },
{ key: 'WORDPRESS_DB_NAME', value: 'wordpress', description: 'Database name' },
],
},
{
id: 'ghost',
name: 'Ghost',
description: 'Modern publishing platform for creating professional blogs and newsletters.',
category: 'CMS',
iconName: 'book-open',
image: 'ghost:latest',
port: 2368,
envVars: [
{ key: 'database__client', value: 'sqlite3', description: 'Database client (sqlite3 for standalone)' },
{ key: 'database__connection__filename', value: '/var/lib/ghost/content/data/ghost.db', description: 'SQLite database path' },
{ key: 'url', value: 'http://localhost:2368', description: 'Public URL of the blog' },
],
},
{
id: 'gitea',
name: 'Gitea',
description: 'Lightweight self-hosted Git service. Easy to install and maintain.',
category: 'Dev Tools',
iconName: 'git-branch',
image: 'gitea/gitea:latest',
port: 3000,
},
{
id: 'nextcloud',
name: 'Nextcloud',
description: 'Self-hosted file sync and share platform. Your own private cloud.',
category: 'Storage',
iconName: 'package',
image: 'nextcloud:latest',
port: 80,
},
{
id: 'grafana',
name: 'Grafana',
description: 'Open-source observability platform for metrics, logs, and traces visualization.',
category: 'Monitoring',
iconName: 'monitor',
image: 'grafana/grafana:latest',
port: 3000,
envVars: [
{ key: 'GF_SECURITY_ADMIN_PASSWORD', value: 'admin', description: 'Admin password' },
],
},
{
id: 'uptime-kuma',
name: 'Uptime Kuma',
description: 'Self-hosted monitoring tool. Beautiful UI for tracking uptime of services.',
category: 'Monitoring',
iconName: 'monitor',
image: 'louislam/uptime-kuma:latest',
port: 3001,
},
{
id: 'plausible',
name: 'Plausible Analytics',
description: 'Privacy-friendly web analytics. No cookies, GDPR compliant by design.',
category: 'Analytics',
iconName: 'monitor',
image: 'plausible/analytics:latest',
port: 8000,
enableClickHouse: true,
},
{
id: 'vaultwarden',
name: 'Vaultwarden',
description: 'Lightweight Bitwarden-compatible password manager server.',
category: 'Security',
iconName: 'shield',
image: 'vaultwarden/server:latest',
port: 80,
},
{
id: 'n8n',
name: 'N8N',
description: 'Workflow automation tool. Connect anything to everything with a visual editor.',
category: 'Automation',
iconName: 'server',
image: 'n8nio/n8n:latest',
port: 5678,
},
{
id: 'mattermost',
name: 'Mattermost',
description: 'Open-source Slack alternative for team communication and collaboration.',
category: 'Communication',
iconName: 'mail',
image: 'mattermost/mattermost-team-edition:latest',
port: 8065,
},
{
id: 'portainer',
name: 'Portainer',
description: 'Docker management UI. Monitor and manage containers from a web interface.',
category: 'Dev Tools',
iconName: 'package',
image: 'portainer/portainer-ce:latest',
port: 9000,
},
{
id: 'redis',
name: 'Redis',
description: 'In-memory data store used as database, cache, and message broker.',
category: 'Database',
iconName: 'database',
image: 'redis:alpine',
port: 6379,
},
{
id: 'postgres',
name: 'PostgreSQL',
description: 'Advanced open-source relational database. Reliable and feature-rich.',
category: 'Database',
iconName: 'database',
image: 'postgres:16-alpine',
port: 5432,
envVars: [
{ key: 'POSTGRES_PASSWORD', value: '', description: 'Superuser password', required: true },
{ key: 'POSTGRES_USER', value: 'postgres', description: 'Superuser name' },
{ key: 'POSTGRES_DB', value: 'postgres', description: 'Default database name' },
],
},
{
id: 'mariadb',
name: 'MariaDB',
description: 'Community-developed fork of MySQL. Drop-in replacement with enhanced features.',
category: 'Database',
iconName: 'database',
image: 'mariadb:latest',
port: 3306,
envVars: [
{ key: 'MARIADB_ROOT_PASSWORD', value: '', description: 'Root password', required: true },
],
},
{
id: 'adminer',
name: 'Adminer',
description: 'Database management tool in a single PHP file. Supports MySQL, PostgreSQL, SQLite.',
category: 'Dev Tools',
iconName: 'database',
image: 'adminer:latest',
port: 8080,
},
];
@customElement('ob-view-appstore')
export class ObViewAppStore extends DeesElement {
public static styles = [
cssManager.defaultStyles,
shared.viewHostCss,
css``,
];
async connectedCallback() {
super.connectedCallback();
}
public render(): TemplateResult {
return html`
<ob-sectionheading>App Store</ob-sectionheading>
<sz-app-store-view
.apps=${appTemplates}
@deploy-app=${(e: CustomEvent) => this.handleDeployApp(e)}
></sz-app-store-view>
`;
}
private handleDeployApp(e: CustomEvent) {
const app = e.detail?.app;
if (!app) return;
// Store the template and navigate on next microtask to avoid
// destroying the current view while the event handler is still on the call stack
setTimeout(() => {
// Set both pendingAppTemplate and activeView atomically
appstate.uiStatePart.setState({
...appstate.uiStatePart.getState(),
pendingAppTemplate: app,
activeView: 'services',
});
}, 0);
}
}

View File

@@ -110,8 +110,8 @@ export class ObViewDashboard extends DeesElement {
cpu: status?.docker?.cpuUsage || 0,
memoryUsed: status?.docker?.memoryUsage || 0,
memoryTotal: status?.docker?.memoryTotal || 0,
networkIn: 0,
networkOut: 0,
networkIn: status?.docker?.networkIn || 0,
networkOut: status?.docker?.networkOut || 0,
topConsumers: [],
},
platformServices: platformServices.map((ps) => ({

View File

@@ -2,6 +2,7 @@ import * as plugins from '../plugins.js';
import * as shared from './shared/index.js';
import * as appstate from '../appstate.js';
import * as interfaces from '../../ts_interfaces/index.js';
import { BackendExecutionEnvironment } from '../environments/backend-environment.js';
import {
DeesElement,
customElement,
@@ -76,20 +77,29 @@ function toServiceStats(stats: interfaces.data.IContainerStats) {
};
}
function parseLogs(logs: any): Array<{ timestamp: string; message: string }> {
function parseLogs(logs: any): Array<{ timestamp: string; message: string; level?: string }> {
if (Array.isArray(logs)) {
return logs.map((entry: any) => ({
timestamp: entry.timestamp ? String(entry.timestamp) : '',
message: entry.message || String(entry),
}));
return logs.map((entry: any) => {
const ts = entry.timestamp
? (typeof entry.timestamp === 'number' ? new Date(entry.timestamp).toISOString() : String(entry.timestamp))
: new Date().toISOString();
const message = entry.message || String(entry);
const level = entry.level || 'info';
return { timestamp: ts, message, level };
});
}
if (typeof logs === 'string' && logs.trim()) {
return logs.split('\n').filter((line: string) => line.trim()).map((line: string) => {
const match = line.match(/^(\d{4}-\d{2}-\d{2}T[\d:.]+Z?)\s+(.*)/);
if (match) {
return { timestamp: match[1], message: match[2] };
}
return { timestamp: '', message: line };
const timestamp = match ? match[1] : new Date().toISOString();
const message = match ? match[2] : line;
const msgLower = message.toLowerCase();
const level = msgLower.includes('error') || msgLower.includes('fatal')
? 'error'
: msgLower.includes('warn')
? 'warn'
: 'info';
return { timestamp, message, level };
});
}
return [];
@@ -126,6 +136,12 @@ export class ObViewServices extends DeesElement {
@state()
accessor selectedPlatformType: string = '';
@state()
accessor workspaceOpen: boolean = false;
@state()
accessor pendingTemplate: any = null;
constructor() {
super();
@@ -142,6 +158,8 @@ export class ObViewServices extends DeesElement {
this.backupsState = newState;
});
this.rxSubscriptions.push(backupsSub);
// No subscription needed — pendingAppTemplate is checked in render()
}
public static styles = [
@@ -177,6 +195,18 @@ export class ObViewServices extends DeesElement {
width: 16px;
height: 16px;
}
:host(.workspace-mode) {
max-width: none;
padding: 0;
height: 100%;
display: flex;
flex-direction: column;
}
:host(.workspace-mode) ob-sectionheading {
display: none;
}
`,
];
@@ -191,13 +221,27 @@ export class ObViewServices extends DeesElement {
const state = appstate.servicesStatePart.getState();
if (state.currentPlatformService) {
const type = state.currentPlatformService.type;
// Clear the selection so it doesn't persist on next visit
appstate.servicesStatePart.setState({
...appstate.servicesStatePart.getState(),
currentPlatformService: null,
});
this.navigateToPlatformDetail(type);
}
}
updated(changedProperties: Map<string, any>) {
super.updated(changedProperties);
// Check for pending app template from the App Store after each update
const uiState = appstate.uiStatePart.getState();
if (uiState.pendingAppTemplate && !this.pendingTemplate) {
this.pendingTemplate = uiState.pendingAppTemplate;
appstate.uiStatePart.setState({
...appstate.uiStatePart.getState(),
pendingAppTemplate: undefined,
});
this.currentView = 'create';
}
}
public render(): TemplateResult {
@@ -222,9 +266,20 @@ export class ObViewServices extends DeesElement {
domain: s.domain || null,
status: mapStatus(s.status),
}));
const displayStatus = (status: string) => {
switch (status) {
case 'running': return 'Running';
case 'stopped': return 'Stopped';
case 'starting': return 'Starting...';
case 'stopping': return 'Stopping...';
case 'failed': return 'Failed';
case 'not-deployed': return 'Not Deployed';
default: return status;
}
};
const mappedPlatformServices = this.servicesState.platformServices.map((ps) => ({
name: ps.displayName,
status: ps.status === 'running' ? `Running` : ps.status,
status: displayStatus(ps.status),
running: ps.status === 'running',
type: ps.type,
}));
@@ -273,7 +328,63 @@ export class ObViewServices extends DeesElement {
`;
}
private async deployFromTemplate(template: any): Promise<void> {
const name = template.id || template.name.toLowerCase().replace(/[^a-z0-9-]/g, '-');
const envVars: Record<string, string> = {};
if (template.envVars) {
for (const ev of template.envVars) {
if (ev.key && ev.value) envVars[ev.key] = ev.value;
}
}
const serviceConfig: interfaces.data.IServiceCreate = {
name,
image: template.image,
port: template.port || 80,
envVars,
enableMongoDB: template.enableMongoDB || false,
enableS3: template.enableS3 || false,
enableClickHouse: template.enableClickHouse || false,
};
await appstate.servicesStatePart.dispatchAction(appstate.createServiceAction, {
config: serviceConfig,
});
this.pendingTemplate = null;
this.currentView = 'list';
}
private renderCreateView(): TemplateResult {
// If we have a pending app template from the App Store, show a quick-deploy confirmation
if (this.pendingTemplate) {
const t = this.pendingTemplate;
return html`
<ob-sectionheading>Deploy ${t.name}</ob-sectionheading>
<div style="max-width: 600px; margin: 0 auto;">
<div style="background: var(--ci-shade-1, #09090b); border: 1px solid var(--ci-shade-2, #27272a); border-radius: 8px; padding: 24px; margin-bottom: 16px;">
<h3 style="margin: 0 0 8px 0; font-size: 18px;">${t.name}</h3>
<p style="margin: 0 0 16px 0; color: var(--ci-shade-5, #a1a1aa); font-size: 14px;">${t.description}</p>
<div style="display: grid; grid-template-columns: 1fr 1fr; gap: 12px; font-size: 13px;">
<div><span style="color: var(--ci-shade-5, #a1a1aa);">Image:</span> <strong>${t.image}</strong></div>
<div><span style="color: var(--ci-shade-5, #a1a1aa);">Port:</span> <strong>${t.port}</strong></div>
<div><span style="color: var(--ci-shade-5, #a1a1aa);">Service Name:</span> <strong>${t.id}</strong></div>
<div><span style="color: var(--ci-shade-5, #a1a1aa);">Category:</span> <strong>${t.category}</strong></div>
</div>
${t.enableMongoDB || t.enableS3 || t.enableClickHouse ? html`
<div style="margin-top: 12px; font-size: 13px; color: var(--ci-shade-5, #a1a1aa);">
Platform Services:
${t.enableMongoDB ? html`<span style="margin-right: 8px;">MongoDB</span>` : ''}
${t.enableS3 ? html`<span style="margin-right: 8px;">S3</span>` : ''}
${t.enableClickHouse ? html`<span>ClickHouse</span>` : ''}
</div>
` : ''}
</div>
<div style="display: flex; gap: 12px; justify-content: flex-end;">
<button class="deploy-button" style="background: transparent; border: 1px solid var(--ci-shade-2, #27272a); color: inherit;" @click=${() => { this.pendingTemplate = null; this.currentView = 'list'; }}>Cancel</button>
<button class="deploy-button" @click=${() => this.deployFromTemplate(t)}>Deploy ${t.name}</button>
</div>
</div>
`;
}
return html`
<ob-sectionheading>Create Service</ob-sectionheading>
<sz-service-create-view
@@ -327,6 +438,28 @@ export class ObViewServices extends DeesElement {
this.currentView = 'list';
}}
@service-action=${(e: CustomEvent) => this.handleServiceAction(e)}
@request-workspace=${async (e: CustomEvent) => {
const name = e.detail?.service?.name || this.selectedServiceName;
const identity = appstate.loginStatePart.getState().identity;
if (!name || !identity) return;
try {
const env = new BackendExecutionEnvironment(name, identity);
await env.init();
const detailView = this.shadowRoot?.querySelector('sz-service-detail-view') as any;
if (detailView) {
detailView.workspaceEnvironment = env;
}
this.workspaceOpen = true;
this.classList.add('workspace-mode');
} catch (err) {
console.error('Failed to open workspace:', err);
}
}}
@back=${() => {
this.workspaceOpen = false;
this.classList.remove('workspace-mode');
this.currentView = 'list';
}}
></sz-service-detail-view>
`;
}
@@ -357,12 +490,26 @@ export class ObViewServices extends DeesElement {
}
private navigateToPlatformDetail(type: string): void {
// Reset to list first to force fresh DOM for dees-chart-log
this.currentView = 'list';
this.selectedPlatformType = type;
// Clear previous stats/logs before fetching new ones
appstate.servicesStatePart.setState({
...appstate.servicesStatePart.getState(),
currentPlatformServiceStats: null,
currentPlatformServiceLogs: [],
});
// Fetch stats and logs for this platform service
const serviceType = type as interfaces.data.TPlatformServiceType;
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceStatsAction, { serviceType });
appstate.servicesStatePart.dispatchAction(appstate.fetchPlatformServiceLogsAction, { serviceType });
this.currentView = 'platform-detail';
// Switch to detail view on next microtask (ensures fresh DOM)
requestAnimationFrame(() => {
this.currentView = 'platform-detail';
});
}
private renderPlatformDetailView(): TemplateResult {
@@ -370,37 +517,60 @@ export class ObViewServices extends DeesElement {
(ps) => ps.type === this.selectedPlatformType,
);
const stats = this.servicesState.currentPlatformServiceStats;
const metrics = stats
? {
cpu: Math.round(stats.cpuPercent),
memory: Math.round(stats.memoryPercent),
storage: 0,
connections: 0,
}
: undefined;
const metrics = {
cpu: stats ? Math.round(stats.cpuPercent) : 0,
memory: stats ? Math.round(stats.memoryPercent) : 0,
storage: 0,
connections: undefined as number | undefined,
};
// Real service info per platform type
const serviceInfo: Record<string, { host: string; port: number; version: string; config: Record<string, any> }> = {
mongodb: { host: 'onebox-mongodb', port: 27017, version: '4.4', config: { engine: 'WiredTiger', authEnabled: true } },
minio: { host: 'onebox-minio', port: 9000, version: 'latest', config: { consolePort: 9001, region: 'us-east-1' } },
clickhouse: { host: 'onebox-clickhouse', port: 8123, version: 'latest', config: { nativePort: 9000, httpPort: 8123 } },
caddy: { host: 'onebox-caddy', port: 80, version: '2-alpine', config: { httpsPort: 443, adminApi: 2019 } },
};
const info = platformService
? serviceInfo[platformService.type] || { host: 'unknown', port: 0, version: '', config: {} }
: { host: '', port: 0, version: '', config: {} };
// Map backend status to catalog-compatible status
const mapPlatformStatus = (status: string): 'running' | 'stopped' | 'error' => {
switch (status) {
case 'running': return 'running';
case 'failed': return 'error';
case 'starting':
case 'stopping':
case 'stopped':
case 'not-deployed':
default: return 'stopped';
}
};
return html`
<ob-sectionheading>Platform Service</ob-sectionheading>
<div class="page-actions" style="justify-content: flex-start;">
<button class="deploy-button" style="background: transparent; border: 1px solid var(--ci-shade-2, #27272a); color: inherit;" @click=${() => { this.currentView = 'list'; }}>
&larr; Back to Services
</button>
</div>
<sz-platform-service-detail-view
.service=${platformService
? {
id: platformService.type,
name: platformService.displayName,
type: platformService.type,
status: platformService.status === 'running'
? 'running'
: platformService.status === 'failed'
? 'error'
: 'stopped',
version: '',
host: 'localhost',
port: 0,
config: {},
status: mapPlatformStatus(platformService.status),
version: info.version,
host: info.host,
port: info.port,
config: info.config,
metrics,
}
: null}
.logs=${this.servicesState.currentPlatformServiceLogs.map((log) => ({
timestamp: new Date(log.timestamp).toLocaleString(),
timestamp: new Date(log.timestamp).toISOString(),
level: log.level,
message: log.message,
}))}

View File

@@ -0,0 +1,155 @@
/**
* BackendExecutionEnvironment — implements IExecutionEnvironment
* by routing all filesystem and process operations through the onebox API
* to Docker exec on the target container.
*/
import * as plugins from '../plugins.js';
import * as interfaces from '../../ts_interfaces/index.js';
// Import IExecutionEnvironment type from dees-catalog
type IExecutionEnvironment = import('@design.estate/dees-catalog').IExecutionEnvironment;
type IFileEntry = import('@design.estate/dees-catalog').IFileEntry;
type IFileWatcher = import('@design.estate/dees-catalog').IFileWatcher;
type IProcessHandle = import('@design.estate/dees-catalog').IProcessHandle;
const domtools = plugins.deesElement.domtools;
export class BackendExecutionEnvironment implements IExecutionEnvironment {
readonly type = 'backend' as const;
private _ready = false;
private identity: interfaces.data.IIdentity;
constructor(
private serviceName: string,
identity: interfaces.data.IIdentity,
) {
this.identity = identity;
}
get ready(): boolean {
return this._ready;
}
async init(): Promise<void> {
// Verify the container is accessible by checking if root exists
const result = await this.fireRequest<interfaces.requests.IReq_WorkspaceExists>(
'workspaceExists',
{ path: '/' },
);
if (!result.exists) {
throw new Error(`Cannot access container filesystem for service: ${this.serviceName}`);
}
this._ready = true;
}
async destroy(): Promise<void> {
this._ready = false;
}
async readFile(path: string): Promise<string> {
const result = await this.fireRequest<interfaces.requests.IReq_WorkspaceReadFile>(
'workspaceReadFile',
{ path },
);
return result.content;
}
async writeFile(path: string, contents: string): Promise<void> {
await this.fireRequest<interfaces.requests.IReq_WorkspaceWriteFile>(
'workspaceWriteFile',
{ path, content: contents },
);
}
async readDir(path: string): Promise<IFileEntry[]> {
const result = await this.fireRequest<interfaces.requests.IReq_WorkspaceReadDir>(
'workspaceReadDir',
{ path },
);
return result.entries;
}
async mkdir(path: string): Promise<void> {
await this.fireRequest<interfaces.requests.IReq_WorkspaceMkdir>(
'workspaceMkdir',
{ path },
);
}
async rm(path: string, options?: { recursive?: boolean }): Promise<void> {
await this.fireRequest<interfaces.requests.IReq_WorkspaceRm>(
'workspaceRm',
{ path, recursive: options?.recursive },
);
}
async exists(path: string): Promise<boolean> {
const result = await this.fireRequest<interfaces.requests.IReq_WorkspaceExists>(
'workspaceExists',
{ path },
);
return result.exists;
}
watch(
_path: string,
_callback: (event: 'rename' | 'change', filename: string | null) => void,
_options?: { recursive?: boolean },
): IFileWatcher {
// Polling-based file watching — check for changes periodically
// For now, return a no-op watcher. Full implementation would poll readDir.
return { stop: () => {} };
}
async spawn(command: string, args?: string[]): Promise<IProcessHandle> {
// For interactive shell: execute the command via the workspace exec API
// and return a process handle that bridges stdin/stdout
const cmd = args ? [command, ...args] : [command];
const fullCommand = cmd.join(' ');
// Use a non-interactive exec for now — full interactive shell would need
// TypedSocket bidirectional streaming (to be implemented)
const result = await this.fireRequest<interfaces.requests.IReq_WorkspaceExec>(
'workspaceExec',
{ command: cmd[0], args: cmd.slice(1) },
);
// Create a ReadableStream from the exec output
const output = new ReadableStream<string>({
start(controller) {
if (result.stdout) controller.enqueue(result.stdout);
if (result.stderr) controller.enqueue(result.stderr);
controller.close();
},
});
// Create a writable stream (no-op for non-interactive)
const inputStream = new WritableStream<string>();
return {
output,
input: inputStream,
exit: Promise.resolve(result.exitCode),
kill: () => {},
};
}
/**
* Helper to fire TypedRequests to the workspace API
*/
private async fireRequest<T extends { method: string; request: any; response: any }>(
method: string,
data: Omit<T['request'], 'identity' | 'serviceName'>,
): Promise<T['response']> {
const typedRequest = new domtools.plugins.typedrequest.TypedRequest<T>(
'/typedrequest',
method,
);
return await typedRequest.fire({
identity: this.identity,
serviceName: this.serviceName,
...data,
} as T['request']);
}
}

View File

@@ -5,9 +5,13 @@ import * as deesCatalog from '@design.estate/dees-catalog';
// @serve.zone scope — side-effect import registers all sz-* custom elements
import '@serve.zone/catalog';
// TypedSocket for real-time server push (logs, events)
import * as typedsocket from '@api.global/typedsocket';
export {
deesElement,
deesCatalog,
typedsocket,
};
// domtools gives us TypedRequest, smartstate, smartrouter, and other utilities