mirror of
https://github.com/unraid/api.git
synced 2026-01-06 00:30:22 -06:00
Compare commits
42 Commits
v4.2.0
...
feat/web-c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9f492bf217 | ||
|
|
c4b4d26af0 | ||
|
|
0bcfb47bbc | ||
|
|
b0099421f3 | ||
|
|
49f636541b | ||
|
|
419c794e03 | ||
|
|
bbb02e991c | ||
|
|
8f78b3f1ca | ||
|
|
a1d02b486a | ||
|
|
cc85fba207 | ||
|
|
5958d33fce | ||
|
|
3a20930ead | ||
|
|
bf81a63f8e | ||
|
|
7e6be67f61 | ||
|
|
6a92f61f1a | ||
|
|
9cad1a9454 | ||
|
|
2c01ba9610 | ||
|
|
9ce10a72b2 | ||
|
|
f3e6a0011e | ||
|
|
cb2020dee6 | ||
|
|
db189abec4 | ||
|
|
d8afc8f4c9 | ||
|
|
3bfcc8e8c0 | ||
|
|
1892e23c22 | ||
|
|
03ece335b8 | ||
|
|
7bc9949110 | ||
|
|
ad3906e682 | ||
|
|
a356bf03fb | ||
|
|
57a6c49f8a | ||
|
|
9e54237670 | ||
|
|
f56d6ce5a2 | ||
|
|
590ab7327f | ||
|
|
9d63e56374 | ||
|
|
afa6087e95 | ||
|
|
c9789ac1f2 | ||
|
|
14ff3398ba | ||
|
|
6c042cbe01 | ||
|
|
3380929c04 | ||
|
|
bd69b3383f | ||
|
|
a0021bf682 | ||
|
|
b5f3a37863 | ||
|
|
7aa55883e2 |
31
.github/workflows/main.yml
vendored
31
.github/workflows/main.yml
vendored
@@ -152,6 +152,11 @@ jobs:
|
||||
with:
|
||||
name: unraid-api
|
||||
path: ${{ github.workspace }}/api/deploy/unraid-api.tgz
|
||||
- name: Upload PNPM Store to Github artifacts
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: packed-pnpm-store
|
||||
path: ${{ github.workspace }}/api/deploy/packed-pnpm-store.txz
|
||||
|
||||
build-unraid-ui-webcomponents:
|
||||
name: Build Unraid UI Library (Webcomponent Version)
|
||||
@@ -316,6 +321,15 @@ jobs:
|
||||
shell: bash
|
||||
run: |
|
||||
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||
|
||||
- name: Get API Version
|
||||
id: vars
|
||||
run: |
|
||||
GIT_SHA=$(git rev-parse --short HEAD)
|
||||
IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '')
|
||||
PACKAGE_LOCK_VERSION=$(jq -r '.version' package.json)
|
||||
API_VERSION=$([[ -n "$IS_TAGGED" ]] && echo "$PACKAGE_LOCK_VERSION" || echo "${PACKAGE_LOCK_VERSION}+${GIT_SHA}")
|
||||
echo "API_VERSION=${API_VERSION}" >> $GITHUB_OUTPUT
|
||||
|
||||
- uses: actions/cache@v4
|
||||
name: Setup pnpm cache
|
||||
@@ -347,6 +361,11 @@ jobs:
|
||||
with:
|
||||
name: unraid-api
|
||||
path: ${{ github.workspace }}/plugin/api/
|
||||
- name: Download PNPM Store
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: packed-pnpm-store
|
||||
path: ${{ github.workspace }}/plugin/
|
||||
- name: Extract Unraid API
|
||||
run: |
|
||||
mkdir -p ${{ github.workspace }}/plugin/source/dynamix.unraid.net/usr/local/unraid-api
|
||||
@@ -355,6 +374,7 @@ jobs:
|
||||
id: build-plugin
|
||||
run: |
|
||||
cd ${{ github.workspace }}/plugin
|
||||
ls -al
|
||||
pnpm run build:txz
|
||||
|
||||
if [ -n "${{ github.event.pull_request.number }}" ]; then
|
||||
@@ -375,7 +395,6 @@ jobs:
|
||||
echo "TAG=${TAG}" >> $GITHUB_OUTPUT
|
||||
|
||||
pnpm run build:plugin --tag="${TAG}" --base-url="${BASE_URL}"
|
||||
|
||||
- name: Ensure Plugin Files Exist
|
||||
run: |
|
||||
if [ ! -f ./deploy/*.plg ]; then
|
||||
@@ -387,6 +406,7 @@ jobs:
|
||||
echo "Error: .txz file not found in plugin/deploy/"
|
||||
exit 1
|
||||
fi
|
||||
ls -al ./deploy
|
||||
- name: Upload to GHA
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
@@ -399,8 +419,13 @@ jobs:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
|
||||
AWS_DEFAULT_REGION: auto
|
||||
run: |
|
||||
# Sync the deploy directory to the Cloudflare bucket - CRC32 is required for the checksum-algorithm on cloudflare (ref. https://community.cloudflare.com/t/an-error-occurred-internalerror-when-calling-the-putobject-operation/764905/8)
|
||||
aws s3 sync deploy/ s3://${{ secrets.CF_BUCKET_PREVIEW }}/${{ steps.build-plugin.outputs.BUCKET_PATH }} --endpoint-url ${{ secrets.CF_ENDPOINT }} --checksum-algorithm CRC32
|
||||
# Sync the deploy directory to the Cloudflare bucket with explicit content encoding and public-read ACL
|
||||
aws s3 sync deploy/ s3://${{ secrets.CF_BUCKET_PREVIEW }}/${{ steps.build-plugin.outputs.BUCKET_PATH }} \
|
||||
--endpoint-url ${{ secrets.CF_ENDPOINT }} \
|
||||
--checksum-algorithm CRC32 \
|
||||
--no-guess-mime-type \
|
||||
--content-encoding none \
|
||||
--acl public-read
|
||||
|
||||
- name: Upload Release Assets
|
||||
if: needs.release-please.outputs.releases_created == 'true'
|
||||
|
||||
33
.github/workflows/release-production.yml
vendored
33
.github/workflows/release-production.yml
vendored
@@ -33,7 +33,7 @@ jobs:
|
||||
prerelease: false
|
||||
- uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version: '20.x'
|
||||
node-version: '22.x'
|
||||
- run: npm install html-escaper@2 xml2js
|
||||
- name: Update Plugin Changelog
|
||||
uses: actions/github-script@v7
|
||||
@@ -66,8 +66,23 @@ jobs:
|
||||
|
||||
// Validate the plugin file is valid XML
|
||||
const xml2js = require('xml2js');
|
||||
const parser = new xml2js.Parser();
|
||||
const parser = new xml2js.Parser({
|
||||
explicitCharkey: true,
|
||||
trim: true,
|
||||
explicitRoot: true,
|
||||
explicitArray: false,
|
||||
attrkey: 'ATTR',
|
||||
charkey: 'TEXT',
|
||||
xmlnskey: 'XMLNS',
|
||||
normalizeTags: false,
|
||||
normalize: false,
|
||||
strict: false // Try with less strict parsing
|
||||
});
|
||||
parser.parseStringPromise(pluginContent).then((result) => {
|
||||
if (!result) {
|
||||
console.error('Plugin file is not valid XML');
|
||||
process.exit(1);
|
||||
}
|
||||
console.log('Plugin file is valid XML');
|
||||
|
||||
// Write back to file
|
||||
@@ -84,7 +99,12 @@ jobs:
|
||||
AWS_DEFAULT_REGION: ${{ secrets.DO_SPACE_REGION }}
|
||||
AWS_ENDPOINT_URL: https://${{ secrets.DO_SPACE_REGION }}.digitaloceanspaces.com
|
||||
run: |
|
||||
aws s3 sync . s3://${{ secrets.DO_SPACE_NAME }}/unraid-api --checksum-algorithm CRC32
|
||||
# Upload files with explicit content encoding and public-read ACL
|
||||
aws s3 sync . s3://${{ secrets.DO_SPACE_NAME }}/unraid-api \
|
||||
--checksum-algorithm CRC32 \
|
||||
--no-guess-mime-type \
|
||||
--content-encoding none \
|
||||
--acl public-read
|
||||
|
||||
- name: Upload Release Files to Cloudflare Bucket
|
||||
env:
|
||||
@@ -93,4 +113,9 @@ jobs:
|
||||
AWS_DEFAULT_REGION: auto
|
||||
AWS_ENDPOINT_URL: ${{ secrets.CF_ENDPOINT }}
|
||||
run: |
|
||||
aws s3 sync . s3://${{ secrets.CF_BUCKET }}/unraid-api --checksum-algorithm CRC32
|
||||
# Upload files with explicit content encoding and public-read ACL
|
||||
aws s3 sync . s3://${{ secrets.CF_BUCKET }}/unraid-api \
|
||||
--checksum-algorithm CRC32 \
|
||||
--no-guess-mime-type \
|
||||
--content-encoding none \
|
||||
--acl public-read
|
||||
|
||||
2
.github/workflows/test-libvirt.yml
vendored
2
.github/workflows/test-libvirt.yml
vendored
@@ -28,7 +28,7 @@ jobs:
|
||||
|
||||
- uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: "3.10"
|
||||
python-version: "3.13"
|
||||
|
||||
- name: Cache APT Packages
|
||||
uses: awalsh128/cache-apt-pkgs-action@v1.4.3
|
||||
|
||||
6
.gitignore
vendored
6
.gitignore
vendored
@@ -95,3 +95,9 @@ fb_keepalive
|
||||
|
||||
# pnpm store
|
||||
.pnpm-store
|
||||
|
||||
# Nix
|
||||
result
|
||||
result-*
|
||||
.direnv/
|
||||
.envrc
|
||||
|
||||
@@ -1 +1 @@
|
||||
{".":"4.2.0"}
|
||||
{".":"4.4.1"}
|
||||
|
||||
@@ -58,6 +58,14 @@ We use GitHub to host code, to track issues and feature requests, as well as acc
|
||||
|
||||
**Note:** Direct pushes to the main branch are not allowed. All changes must go through the PR process.
|
||||
|
||||
## Developer Documentation
|
||||
|
||||
For detailed information about development workflows, repository organization, and other technical details, please refer to our developer documentation:
|
||||
|
||||
- [Development Guide](api/docs/developer/development.md) - Setup, building, and debugging instructions
|
||||
- [Development Workflows](api/docs/developer/workflows.md) - Detailed workflows for local development, building, and deployment
|
||||
- [Repository Organization](api/docs/developer/repo-organization.md) - High-level architecture and project structure
|
||||
|
||||
## Bug Reports and Feature Requests
|
||||
|
||||
We use GitHub issues to track bugs and feature requests:
|
||||
|
||||
@@ -1,5 +1,66 @@
|
||||
# Changelog
|
||||
|
||||
## [4.4.1](https://github.com/unraid/api/compare/v4.4.0...v4.4.1) (2025-03-26)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* .env.production from allowing console logs on build ([#1273](https://github.com/unraid/api/issues/1273)) ([32acc2d](https://github.com/unraid/api/commit/32acc2d27c8bb565b38a66d8233030de3711ea12))
|
||||
* patch version override logic incorrect ([#1275](https://github.com/unraid/api/issues/1275)) ([6a59756](https://github.com/unraid/api/commit/6a597561a3e21c27fff8d4530cf59cf382eaa015))
|
||||
|
||||
## [4.4.0](https://github.com/unraid/api/compare/v4.3.1...v4.4.0) (2025-03-25)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add ReplaceKey functionality to plugin ([#1264](https://github.com/unraid/api/issues/1264)) ([4aadcef](https://github.com/unraid/api/commit/4aadcef1ca6b45b44885f2d2a986874e86945d4f))
|
||||
* downgrade page replace key check ([#1263](https://github.com/unraid/api/issues/1263)) ([8d56d12](https://github.com/unraid/api/commit/8d56d12f67d86d7015a358727bcb303eb511ac42))
|
||||
* make log viewer component dynamic ([#1242](https://github.com/unraid/api/issues/1242)) ([e6ec110](https://github.com/unraid/api/commit/e6ec110fbf81b329b72ef350643bf3c76734290a))
|
||||
* ReplaceKey functionality in Registration and Update pages ([#1246](https://github.com/unraid/api/issues/1246)) ([04307c9](https://github.com/unraid/api/commit/04307c977cfd4916753140e5a20811c561d2dfb2))
|
||||
* UnraidCheckExec for Check OS Updates via UPC dropdown ([#1265](https://github.com/unraid/api/issues/1265)) ([5935a3b](https://github.com/unraid/api/commit/5935a3b3c2f69ee683146c3fcc798d72996633f8))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update all non-major dependencies ([#1236](https://github.com/unraid/api/issues/1236)) ([7194f85](https://github.com/unraid/api/commit/7194f859ce0178116621b4bdf28db553b037940d))
|
||||
* **deps:** update all non-major dependencies ([#1247](https://github.com/unraid/api/issues/1247)) ([20b0aeb](https://github.com/unraid/api/commit/20b0aeb9d7e621ec917c928135b2c867e07ce7a4))
|
||||
* **deps:** update all non-major dependencies ([#1251](https://github.com/unraid/api/issues/1251)) ([33a1a1d](https://github.com/unraid/api/commit/33a1a1ddd2f228cf001bb492f9c76bb5bc6dc8a0))
|
||||
* **deps:** update all non-major dependencies ([#1253](https://github.com/unraid/api/issues/1253)) ([53fec0e](https://github.com/unraid/api/commit/53fec0efaba8f3e2dcf5f2899e3099e0e12f5162))
|
||||
* **deps:** update dependency @nestjs/passport to v11 ([#1244](https://github.com/unraid/api/issues/1244)) ([edc93a9](https://github.com/unraid/api/commit/edc93a921ea93d98b7c2de9a7fed2fed650365e8))
|
||||
* **deps:** update dependency graphql-subscriptions to v3 ([#1209](https://github.com/unraid/api/issues/1209)) ([c14c85f](https://github.com/unraid/api/commit/c14c85fcf7ce920edd75d15fa9b3d556f452bb88))
|
||||
* **deps:** update dependency ini to v5 ([#1217](https://github.com/unraid/api/issues/1217)) ([f27660f](https://github.com/unraid/api/commit/f27660f140acb647cab1dce162af0d49d5655fb6))
|
||||
* **deps:** update dependency jose to v6 ([#1248](https://github.com/unraid/api/issues/1248)) ([42e3d59](https://github.com/unraid/api/commit/42e3d59107dd800351ee1f7d175c550465ebddb4))
|
||||
* **deps:** update dependency marked to v15 ([#1249](https://github.com/unraid/api/issues/1249)) ([2b6693f](https://github.com/unraid/api/commit/2b6693f404a9a3405300637d2c55bd5b65c61f0b))
|
||||
* **deps:** update dependency pino-pretty to v13 ([#1250](https://github.com/unraid/api/issues/1250)) ([85fb910](https://github.com/unraid/api/commit/85fb91059a0ad7728d766cd3b429857b3fd4bd08))
|
||||
* **deps:** update dependency pm2 to v6 ([#1258](https://github.com/unraid/api/issues/1258)) ([04ad2bc](https://github.com/unraid/api/commit/04ad2bc9c8c1e5fd9622655ce9881bde17388246))
|
||||
* **deps:** update dependency shadcn-vue to v1 ([#1259](https://github.com/unraid/api/issues/1259)) ([1a4fe8f](https://github.com/unraid/api/commit/1a4fe8f85f50cf34df6af2c0bf55efc305fa9fff))
|
||||
* **deps:** update dependency vue-i18n to v11 ([#1261](https://github.com/unraid/api/issues/1261)) ([0063286](https://github.com/unraid/api/commit/0063286e29b9a7439e6acc64973a3152370c75c3))
|
||||
* **deps:** update vueuse monorepo to v13 (major) ([#1262](https://github.com/unraid/api/issues/1262)) ([94caae3](https://github.com/unraid/api/commit/94caae3d87fbfb04dfe5633030e24acddbcc0f6b))
|
||||
* make scripts executable when building the plugin ([#1255](https://github.com/unraid/api/issues/1255)) ([e237f38](https://github.com/unraid/api/commit/e237f38bc4646f13461938c03d566fef781ad406))
|
||||
* node installation not persisting across reboots ([#1256](https://github.com/unraid/api/issues/1256)) ([0415cf1](https://github.com/unraid/api/commit/0415cf1252ed8c7fba32a65c031dc0d21e0f5a81))
|
||||
* update configValid state to ineligible in var.ini and adjust rel… ([#1268](https://github.com/unraid/api/issues/1268)) ([ef8c954](https://github.com/unraid/api/commit/ef8c9548baef99010e2f26288af33a90167e5177))
|
||||
|
||||
## [4.3.1](https://github.com/unraid/api/compare/v4.3.0...v4.3.1) (2025-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* stepper fixes ([#1240](https://github.com/unraid/api/issues/1240)) ([e7f6f5e](https://github.com/unraid/api/commit/e7f6f5e8315c50fd37193f7d7de2af3d370c18ea))
|
||||
|
||||
## [4.3.0](https://github.com/unraid/api/compare/v4.2.1...v4.3.0) (2025-03-18)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* update production release flow to validate less strictly ([#1238](https://github.com/unraid/api/issues/1238)) ([3afb203](https://github.com/unraid/api/commit/3afb203b856655e4ce7c9d709e30d3f2ebd64784))
|
||||
|
||||
## [4.2.1](https://github.com/unraid/api/compare/v4.2.0...v4.2.1) (2025-03-18)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update all non-major dependencies ([#1192](https://github.com/unraid/api/issues/1192)) ([65b8db2](https://github.com/unraid/api/commit/65b8db2845a5f4234527a70b0625b1a88df5b2fe))
|
||||
|
||||
## [4.2.0](https://github.com/unraid/api/compare/v4.1.3...v4.2.0) (2025-03-18)
|
||||
|
||||
|
||||
|
||||
@@ -87,7 +87,7 @@ shareAvahiSMBModel="Xserve"
|
||||
shfs_logging="1"
|
||||
safeMode="no"
|
||||
startMode="Normal"
|
||||
configValid="yes"
|
||||
configValid="ineligible"
|
||||
joinStatus="Not joined"
|
||||
deviceCount="4"
|
||||
flashGUID="0000-0000-0000-000000000000"
|
||||
|
||||
@@ -215,3 +215,5 @@ unraid-api --help
|
||||
5. Test your changes: `pnpm test`
|
||||
6. Deploy to a development server: `pnpm unraid:deploy <SERVER_IP>`
|
||||
7. Verify your changes on the Unraid server
|
||||
|
||||
If using nix, run `nix develop` from the root of the repo before Step 2.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/api",
|
||||
"version": "4.2.0",
|
||||
"version": "4.4.1",
|
||||
"main": "src/cli/index.ts",
|
||||
"type": "module",
|
||||
"corepack": {
|
||||
@@ -63,7 +63,7 @@
|
||||
"@nestjs/common": "^11.0.11",
|
||||
"@nestjs/core": "^11.0.11",
|
||||
"@nestjs/graphql": "^13.0.3",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/passport": "^11.0.0",
|
||||
"@nestjs/platform-fastify": "^11.0.11",
|
||||
"@nestjs/schedule": "^5.0.0",
|
||||
"@nestjs/throttler": "^6.2.1",
|
||||
@@ -77,6 +77,7 @@
|
||||
"cacheable-lookup": "^7.0.0",
|
||||
"camelcase-keys": "^9.1.3",
|
||||
"casbin": "^5.32.0",
|
||||
"change-case": "^5.4.4",
|
||||
"chokidar": "^4.0.1",
|
||||
"cli-table": "^0.3.11",
|
||||
"command-exists": "^1.2.9",
|
||||
@@ -99,14 +100,14 @@
|
||||
"graphql": "^16.9.0",
|
||||
"graphql-fields": "^2.0.3",
|
||||
"graphql-scalars": "^1.23.0",
|
||||
"graphql-subscriptions": "^2.0.0",
|
||||
"graphql-subscriptions": "^3.0.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"graphql-type-uuid": "^0.2.0",
|
||||
"graphql-ws": "^5.16.0",
|
||||
"ini": "^4.1.2",
|
||||
"graphql-ws": "^6.0.0",
|
||||
"ini": "^5.0.0",
|
||||
"ip": "^2.0.1",
|
||||
"jose": "^5.9.6",
|
||||
"jose": "^6.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"multi-ini": "^2.3.2",
|
||||
"mustache": "^4.2.0",
|
||||
@@ -121,8 +122,8 @@
|
||||
"path-type": "^6.0.0",
|
||||
"pino": "^9.5.0",
|
||||
"pino-http": "^10.3.0",
|
||||
"pino-pretty": "^11.3.0",
|
||||
"pm2": "^5.4.2",
|
||||
"pino-pretty": "^13.0.0",
|
||||
"pm2": "^6.0.0",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"request": "^2.88.2",
|
||||
"rxjs": "^7.8.2",
|
||||
@@ -143,7 +144,7 @@
|
||||
"@graphql-codegen/typed-document-node": "^5.0.11",
|
||||
"@graphql-codegen/typescript": "^4.1.1",
|
||||
"@graphql-codegen/typescript-operations": "^4.3.1",
|
||||
"@graphql-codegen/typescript-resolvers": "4.4.3",
|
||||
"@graphql-codegen/typescript-resolvers": "4.4.4",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@ianvs/prettier-plugin-sort-imports": "^4.4.0",
|
||||
"@nestjs/testing": "^11.0.11",
|
||||
@@ -163,7 +164,7 @@
|
||||
"@types/lodash": "^4.17.13",
|
||||
"@types/mustache": "^4.2.5",
|
||||
"@types/node": "^22.13.4",
|
||||
"@types/pify": "^5.0.4",
|
||||
"@types/pify": "^6.0.0",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/sendmail": "^1.4.7",
|
||||
"@types/stoppable": "^1.1.3",
|
||||
@@ -186,6 +187,7 @@
|
||||
"rollup-plugin-node-externals": "^8.0.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"tsx": "^4.19.2",
|
||||
"type-fest": "^4.37.0",
|
||||
"typescript": "^5.6.3",
|
||||
"typescript-eslint": "^8.13.0",
|
||||
"unplugin-swc": "^1.5.1",
|
||||
@@ -201,5 +203,5 @@
|
||||
}
|
||||
},
|
||||
"private": true,
|
||||
"packageManager": "pnpm@10.4.1"
|
||||
"packageManager": "pnpm@10.6.5"
|
||||
}
|
||||
|
||||
@@ -25,6 +25,8 @@ try {
|
||||
|
||||
// Update the package.json version to the deployment version
|
||||
parsedPackageJson.version = deploymentVersion;
|
||||
// omit dev dependencies from release build
|
||||
parsedPackageJson.devDependencies = {};
|
||||
|
||||
// Create a temporary directory for packaging
|
||||
await mkdir('./deploy/pack/', { recursive: true });
|
||||
@@ -36,9 +38,18 @@ try {
|
||||
// Change to the pack directory and install dependencies
|
||||
cd('./deploy/pack');
|
||||
|
||||
console.log('Installing production dependencies...');
|
||||
console.log('Building production pnpm store...');
|
||||
$.verbose = true;
|
||||
await $`pnpm install --prod --ignore-workspace --node-linker hoisted`;
|
||||
await $`pnpm install --prod --ignore-workspace --store-dir=../.pnpm-store`;
|
||||
|
||||
await $`rm -rf node_modules`; // Don't include node_modules in final package
|
||||
|
||||
const sudoCheck = await $`command -v sudo`.nothrow();
|
||||
const SUDO = sudoCheck.exitCode === 0 ? 'sudo' : '';
|
||||
await $`${SUDO} chown -R 0:0 ../.pnpm-store`;
|
||||
|
||||
await $`XZ_OPT=-5 tar -cJf ../packed-pnpm-store.txz ../.pnpm-store`;
|
||||
await $`${SUDO} rm -rf ../.pnpm-store`;
|
||||
|
||||
// chmod the cli
|
||||
await $`chmod +x ./dist/cli.js`;
|
||||
|
||||
@@ -949,160 +949,160 @@ test('After init returns values from cfg file for all fields', async () => {
|
||||
]
|
||||
`);
|
||||
expect(varState).toMatchInlineSnapshot(`
|
||||
{
|
||||
"bindMgt": false,
|
||||
"cacheNumDevices": NaN,
|
||||
"cacheSbNumDisks": NaN,
|
||||
"comment": "Dev Server",
|
||||
"configState": "yes",
|
||||
"configValid": true,
|
||||
"csrfToken": "0000000000000000",
|
||||
"defaultFsType": "xfs",
|
||||
"deviceCount": 4,
|
||||
"domain": "",
|
||||
"domainLogin": "Administrator",
|
||||
"domainShort": "",
|
||||
"enableFruit": "no",
|
||||
"flashGuid": "0000-0000-0000-000000000000",
|
||||
"flashProduct": "DataTraveler_3.0",
|
||||
"flashVendor": "KINGSTON",
|
||||
"fsCopyPrcnt": 0,
|
||||
"fsNumMounted": 0,
|
||||
"fsNumUnmountable": 0,
|
||||
"fsProgress": "Autostart disabled",
|
||||
"fsState": "Stopped",
|
||||
"fsUnmountableMask": "",
|
||||
"fuseDirectio": "auto",
|
||||
"fuseDirectioDefault": "auto",
|
||||
"fuseDirectioStatus": "default",
|
||||
"fuseRemember": "330",
|
||||
"fuseRememberDefault": "330",
|
||||
"fuseRememberStatus": "default",
|
||||
"fuseUseino": "yes",
|
||||
"hideDotFiles": false,
|
||||
"joinStatus": "Not joined",
|
||||
"localMaster": true,
|
||||
"localTld": "local",
|
||||
"luksKeyfile": "/tmp/unraid/keyfile",
|
||||
"maxArraysz": 30,
|
||||
"maxCachesz": 30,
|
||||
"mdColor": "green-blink",
|
||||
"mdNumDisabled": 1,
|
||||
"mdNumDisks": 4,
|
||||
"mdNumErased": 0,
|
||||
"mdNumInvalid": 1,
|
||||
"mdNumMissing": 0,
|
||||
"mdNumNew": 0,
|
||||
"mdNumStripes": 1280,
|
||||
"mdNumStripesDefault": 1280,
|
||||
"mdNumStripesStatus": "default",
|
||||
"mdQueueLimit": "80",
|
||||
"mdQueueLimitDefault": "80",
|
||||
"mdQueueLimitStatus": "default",
|
||||
"mdResync": 0,
|
||||
"mdResyncAction": "check P",
|
||||
"mdResyncCorr": "0",
|
||||
"mdResyncDb": "0",
|
||||
"mdResyncDt": "0",
|
||||
"mdResyncPos": 0,
|
||||
"mdResyncSize": 438960096,
|
||||
"mdScheduler": "auto",
|
||||
"mdSchedulerDefault": "auto",
|
||||
"mdSchedulerStatus": "default",
|
||||
"mdState": "STOPPED",
|
||||
"mdSyncLimit": "5",
|
||||
"mdSyncLimitDefault": "5",
|
||||
"mdSyncLimitStatus": "default",
|
||||
"mdSyncThresh": NaN,
|
||||
"mdSyncThreshDefault": NaN,
|
||||
"mdSyncWindow": NaN,
|
||||
"mdSyncWindowDefault": NaN,
|
||||
"mdVersion": "2.9.14",
|
||||
"mdWriteMethod": NaN,
|
||||
"mdWriteMethodDefault": "auto",
|
||||
"mdWriteMethodStatus": "default",
|
||||
"name": "Tower",
|
||||
"nrRequests": NaN,
|
||||
"nrRequestsDefault": NaN,
|
||||
"nrRequestsStatus": "default",
|
||||
"ntpServer1": "time1.google.com",
|
||||
"ntpServer2": "time2.google.com",
|
||||
"ntpServer3": "time3.google.com",
|
||||
"ntpServer4": "time4.google.com",
|
||||
"pollAttributes": "1800",
|
||||
"pollAttributesDefault": "1800",
|
||||
"pollAttributesStatus": "default",
|
||||
"port": 80,
|
||||
"portssh": 22,
|
||||
"portssl": 443,
|
||||
"porttelnet": 23,
|
||||
"queueDepth": "auto",
|
||||
"regCheck": "Valid",
|
||||
"regExp": "",
|
||||
"regFile": "/app/dev/Unraid.net/Pro.key",
|
||||
"regGen": "0",
|
||||
"regGuid": "13FE-4200-C300-58C372A52B19",
|
||||
"regState": "PRO",
|
||||
"regTm": "1833409182",
|
||||
"regTm2": "0",
|
||||
"regTo": "Eli Bosley",
|
||||
"regTy": "PRO",
|
||||
"reservedNames": "parity,parity2,parity3,diskP,diskQ,diskR,disk,disks,flash,boot,user,user0,disk0,disk1,disk2,disk3,disk4,disk5,disk6,disk7,disk8,disk9,disk10,disk11,disk12,disk13,disk14,disk15,disk16,disk17,disk18,disk19,disk20,disk21,disk22,disk23,disk24,disk25,disk26,disk27,disk28,disk29,disk30,disk31",
|
||||
"safeMode": false,
|
||||
"sbClean": true,
|
||||
"sbEvents": 173,
|
||||
"sbName": "/boot/config/super.dat",
|
||||
"sbNumDisks": 5,
|
||||
"sbState": "1",
|
||||
"sbSyncErrs": 0,
|
||||
"sbSyncExit": "0",
|
||||
"sbSynced": 1586819259,
|
||||
"sbSynced2": 1586822456,
|
||||
"sbUpdated": "1596079143",
|
||||
"sbVersion": "2.9.13",
|
||||
"security": "user",
|
||||
"shareAvahiEnabled": true,
|
||||
"shareAvahiSmbModel": "Xserve",
|
||||
"shareAvahiSmbName": "%h",
|
||||
"shareCacheEnabled": true,
|
||||
"shareCacheFloor": "2000000",
|
||||
"shareCount": 0,
|
||||
"shareDisk": "yes",
|
||||
"shareInitialGroup": "Domain Users",
|
||||
"shareInitialOwner": "Administrator",
|
||||
"shareMoverActive": false,
|
||||
"shareMoverLogging": false,
|
||||
"shareMoverSchedule": "40 3 * * *",
|
||||
"shareNfsCount": 0,
|
||||
"shareNfsEnabled": false,
|
||||
"shareSmbCount": 1,
|
||||
"shareSmbEnabled": true,
|
||||
"shareSmbMode": "workgroup",
|
||||
"shareUser": "e",
|
||||
"shareUserExclude": "",
|
||||
"shareUserInclude": "",
|
||||
"shfsLogging": "1",
|
||||
"shutdownTimeout": 90,
|
||||
"spindownDelay": 0,
|
||||
"spinupGroups": false,
|
||||
"startArray": false,
|
||||
"startMode": "Normal",
|
||||
"startPage": "Main",
|
||||
"sysArraySlots": 24,
|
||||
"sysCacheSlots": NaN,
|
||||
"sysFlashSlots": 1,
|
||||
"sysModel": "Dell R710",
|
||||
"timeZone": "Australia/Adelaide",
|
||||
"useNetbios": "yes",
|
||||
"useNtp": true,
|
||||
"useSsh": true,
|
||||
"useSsl": null,
|
||||
"useTelnet": true,
|
||||
"useUpnp": true,
|
||||
"useWsd": "no",
|
||||
"version": "6.11.2",
|
||||
"workgroup": "WORKGROUP",
|
||||
"wsdOpt": "",
|
||||
}
|
||||
`);
|
||||
{
|
||||
"bindMgt": false,
|
||||
"cacheNumDevices": NaN,
|
||||
"cacheSbNumDisks": NaN,
|
||||
"comment": "Dev Server",
|
||||
"configErrorState": "INELIGIBLE",
|
||||
"configValid": false,
|
||||
"csrfToken": "0000000000000000",
|
||||
"defaultFsType": "xfs",
|
||||
"deviceCount": 4,
|
||||
"domain": "",
|
||||
"domainLogin": "Administrator",
|
||||
"domainShort": "",
|
||||
"enableFruit": "no",
|
||||
"flashGuid": "0000-0000-0000-000000000000",
|
||||
"flashProduct": "DataTraveler_3.0",
|
||||
"flashVendor": "KINGSTON",
|
||||
"fsCopyPrcnt": 0,
|
||||
"fsNumMounted": 0,
|
||||
"fsNumUnmountable": 0,
|
||||
"fsProgress": "Autostart disabled",
|
||||
"fsState": "Stopped",
|
||||
"fsUnmountableMask": "",
|
||||
"fuseDirectio": "auto",
|
||||
"fuseDirectioDefault": "auto",
|
||||
"fuseDirectioStatus": "default",
|
||||
"fuseRemember": "330",
|
||||
"fuseRememberDefault": "330",
|
||||
"fuseRememberStatus": "default",
|
||||
"fuseUseino": "yes",
|
||||
"hideDotFiles": false,
|
||||
"joinStatus": "Not joined",
|
||||
"localMaster": true,
|
||||
"localTld": "local",
|
||||
"luksKeyfile": "/tmp/unraid/keyfile",
|
||||
"maxArraysz": 30,
|
||||
"maxCachesz": 30,
|
||||
"mdColor": "green-blink",
|
||||
"mdNumDisabled": 1,
|
||||
"mdNumDisks": 4,
|
||||
"mdNumErased": 0,
|
||||
"mdNumInvalid": 1,
|
||||
"mdNumMissing": 0,
|
||||
"mdNumNew": 0,
|
||||
"mdNumStripes": 1280,
|
||||
"mdNumStripesDefault": 1280,
|
||||
"mdNumStripesStatus": "default",
|
||||
"mdQueueLimit": "80",
|
||||
"mdQueueLimitDefault": "80",
|
||||
"mdQueueLimitStatus": "default",
|
||||
"mdResync": 0,
|
||||
"mdResyncAction": "check P",
|
||||
"mdResyncCorr": "0",
|
||||
"mdResyncDb": "0",
|
||||
"mdResyncDt": "0",
|
||||
"mdResyncPos": 0,
|
||||
"mdResyncSize": 438960096,
|
||||
"mdScheduler": "auto",
|
||||
"mdSchedulerDefault": "auto",
|
||||
"mdSchedulerStatus": "default",
|
||||
"mdState": "STOPPED",
|
||||
"mdSyncLimit": "5",
|
||||
"mdSyncLimitDefault": "5",
|
||||
"mdSyncLimitStatus": "default",
|
||||
"mdSyncThresh": NaN,
|
||||
"mdSyncThreshDefault": NaN,
|
||||
"mdSyncWindow": NaN,
|
||||
"mdSyncWindowDefault": NaN,
|
||||
"mdVersion": "2.9.14",
|
||||
"mdWriteMethod": NaN,
|
||||
"mdWriteMethodDefault": "auto",
|
||||
"mdWriteMethodStatus": "default",
|
||||
"name": "Tower",
|
||||
"nrRequests": NaN,
|
||||
"nrRequestsDefault": NaN,
|
||||
"nrRequestsStatus": "default",
|
||||
"ntpServer1": "time1.google.com",
|
||||
"ntpServer2": "time2.google.com",
|
||||
"ntpServer3": "time3.google.com",
|
||||
"ntpServer4": "time4.google.com",
|
||||
"pollAttributes": "1800",
|
||||
"pollAttributesDefault": "1800",
|
||||
"pollAttributesStatus": "default",
|
||||
"port": 80,
|
||||
"portssh": 22,
|
||||
"portssl": 443,
|
||||
"porttelnet": 23,
|
||||
"queueDepth": "auto",
|
||||
"regCheck": "Valid",
|
||||
"regExp": "",
|
||||
"regFile": "/app/dev/Unraid.net/Pro.key",
|
||||
"regGen": "0",
|
||||
"regGuid": "13FE-4200-C300-58C372A52B19",
|
||||
"regState": "PRO",
|
||||
"regTm": "1833409182",
|
||||
"regTm2": "0",
|
||||
"regTo": "Eli Bosley",
|
||||
"regTy": "PRO",
|
||||
"reservedNames": "parity,parity2,parity3,diskP,diskQ,diskR,disk,disks,flash,boot,user,user0,disk0,disk1,disk2,disk3,disk4,disk5,disk6,disk7,disk8,disk9,disk10,disk11,disk12,disk13,disk14,disk15,disk16,disk17,disk18,disk19,disk20,disk21,disk22,disk23,disk24,disk25,disk26,disk27,disk28,disk29,disk30,disk31",
|
||||
"safeMode": false,
|
||||
"sbClean": true,
|
||||
"sbEvents": 173,
|
||||
"sbName": "/boot/config/super.dat",
|
||||
"sbNumDisks": 5,
|
||||
"sbState": "1",
|
||||
"sbSyncErrs": 0,
|
||||
"sbSyncExit": "0",
|
||||
"sbSynced": 1586819259,
|
||||
"sbSynced2": 1586822456,
|
||||
"sbUpdated": "1596079143",
|
||||
"sbVersion": "2.9.13",
|
||||
"security": "user",
|
||||
"shareAvahiEnabled": true,
|
||||
"shareAvahiSmbModel": "Xserve",
|
||||
"shareAvahiSmbName": "%h",
|
||||
"shareCacheEnabled": true,
|
||||
"shareCacheFloor": "2000000",
|
||||
"shareCount": 0,
|
||||
"shareDisk": "yes",
|
||||
"shareInitialGroup": "Domain Users",
|
||||
"shareInitialOwner": "Administrator",
|
||||
"shareMoverActive": false,
|
||||
"shareMoverLogging": false,
|
||||
"shareMoverSchedule": "40 3 * * *",
|
||||
"shareNfsCount": 0,
|
||||
"shareNfsEnabled": false,
|
||||
"shareSmbCount": 1,
|
||||
"shareSmbEnabled": true,
|
||||
"shareSmbMode": "workgroup",
|
||||
"shareUser": "e",
|
||||
"shareUserExclude": "",
|
||||
"shareUserInclude": "",
|
||||
"shfsLogging": "1",
|
||||
"shutdownTimeout": 90,
|
||||
"spindownDelay": 0,
|
||||
"spinupGroups": false,
|
||||
"startArray": false,
|
||||
"startMode": "Normal",
|
||||
"startPage": "Main",
|
||||
"sysArraySlots": 24,
|
||||
"sysCacheSlots": NaN,
|
||||
"sysFlashSlots": 1,
|
||||
"sysModel": "Dell R710",
|
||||
"timeZone": "Australia/Adelaide",
|
||||
"useNetbios": "yes",
|
||||
"useNtp": true,
|
||||
"useSsh": true,
|
||||
"useSsl": null,
|
||||
"useTelnet": true,
|
||||
"useUpnp": true,
|
||||
"useWsd": "no",
|
||||
"version": "6.11.2",
|
||||
"workgroup": "WORKGROUP",
|
||||
"wsdOpt": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -16,160 +16,160 @@ test('Returns parsed state file', async () => {
|
||||
});
|
||||
|
||||
expect(parse(stateFile)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"bindMgt": false,
|
||||
"cacheNumDevices": NaN,
|
||||
"cacheSbNumDisks": NaN,
|
||||
"comment": "Dev Server",
|
||||
"configState": "yes",
|
||||
"configValid": true,
|
||||
"csrfToken": "0000000000000000",
|
||||
"defaultFsType": "xfs",
|
||||
"deviceCount": 4,
|
||||
"domain": "",
|
||||
"domainLogin": "Administrator",
|
||||
"domainShort": "",
|
||||
"enableFruit": "no",
|
||||
"flashGuid": "0000-0000-0000-000000000000",
|
||||
"flashProduct": "DataTraveler_3.0",
|
||||
"flashVendor": "KINGSTON",
|
||||
"fsCopyPrcnt": 0,
|
||||
"fsNumMounted": 0,
|
||||
"fsNumUnmountable": 0,
|
||||
"fsProgress": "Autostart disabled",
|
||||
"fsState": "Stopped",
|
||||
"fsUnmountableMask": "",
|
||||
"fuseDirectio": "auto",
|
||||
"fuseDirectioDefault": "auto",
|
||||
"fuseDirectioStatus": "default",
|
||||
"fuseRemember": "330",
|
||||
"fuseRememberDefault": "330",
|
||||
"fuseRememberStatus": "default",
|
||||
"fuseUseino": "yes",
|
||||
"hideDotFiles": false,
|
||||
"joinStatus": "Not joined",
|
||||
"localMaster": true,
|
||||
"localTld": "local",
|
||||
"luksKeyfile": "/tmp/unraid/keyfile",
|
||||
"maxArraysz": 30,
|
||||
"maxCachesz": 30,
|
||||
"mdColor": "green-blink",
|
||||
"mdNumDisabled": 1,
|
||||
"mdNumDisks": 4,
|
||||
"mdNumErased": 0,
|
||||
"mdNumInvalid": 1,
|
||||
"mdNumMissing": 0,
|
||||
"mdNumNew": 0,
|
||||
"mdNumStripes": 1280,
|
||||
"mdNumStripesDefault": 1280,
|
||||
"mdNumStripesStatus": "default",
|
||||
"mdQueueLimit": "80",
|
||||
"mdQueueLimitDefault": "80",
|
||||
"mdQueueLimitStatus": "default",
|
||||
"mdResync": 0,
|
||||
"mdResyncAction": "check P",
|
||||
"mdResyncCorr": "0",
|
||||
"mdResyncDb": "0",
|
||||
"mdResyncDt": "0",
|
||||
"mdResyncPos": 0,
|
||||
"mdResyncSize": 438960096,
|
||||
"mdScheduler": "auto",
|
||||
"mdSchedulerDefault": "auto",
|
||||
"mdSchedulerStatus": "default",
|
||||
"mdState": "STOPPED",
|
||||
"mdSyncLimit": "5",
|
||||
"mdSyncLimitDefault": "5",
|
||||
"mdSyncLimitStatus": "default",
|
||||
"mdSyncThresh": NaN,
|
||||
"mdSyncThreshDefault": NaN,
|
||||
"mdSyncWindow": NaN,
|
||||
"mdSyncWindowDefault": NaN,
|
||||
"mdVersion": "2.9.14",
|
||||
"mdWriteMethod": NaN,
|
||||
"mdWriteMethodDefault": "auto",
|
||||
"mdWriteMethodStatus": "default",
|
||||
"name": "Tower",
|
||||
"nrRequests": NaN,
|
||||
"nrRequestsDefault": NaN,
|
||||
"nrRequestsStatus": "default",
|
||||
"ntpServer1": "time1.google.com",
|
||||
"ntpServer2": "time2.google.com",
|
||||
"ntpServer3": "time3.google.com",
|
||||
"ntpServer4": "time4.google.com",
|
||||
"pollAttributes": "1800",
|
||||
"pollAttributesDefault": "1800",
|
||||
"pollAttributesStatus": "default",
|
||||
"port": 80,
|
||||
"portssh": 22,
|
||||
"portssl": 443,
|
||||
"porttelnet": 23,
|
||||
"queueDepth": "auto",
|
||||
"regCheck": "Valid",
|
||||
"regExp": "",
|
||||
"regFile": "/app/dev/Unraid.net/Pro.key",
|
||||
"regGen": "0",
|
||||
"regGuid": "13FE-4200-C300-58C372A52B19",
|
||||
"regState": "PRO",
|
||||
"regTm": "1833409182",
|
||||
"regTm2": "0",
|
||||
"regTo": "Eli Bosley",
|
||||
"regTy": "PRO",
|
||||
"reservedNames": "parity,parity2,parity3,diskP,diskQ,diskR,disk,disks,flash,boot,user,user0,disk0,disk1,disk2,disk3,disk4,disk5,disk6,disk7,disk8,disk9,disk10,disk11,disk12,disk13,disk14,disk15,disk16,disk17,disk18,disk19,disk20,disk21,disk22,disk23,disk24,disk25,disk26,disk27,disk28,disk29,disk30,disk31",
|
||||
"safeMode": false,
|
||||
"sbClean": true,
|
||||
"sbEvents": 173,
|
||||
"sbName": "/boot/config/super.dat",
|
||||
"sbNumDisks": 5,
|
||||
"sbState": "1",
|
||||
"sbSyncErrs": 0,
|
||||
"sbSyncExit": "0",
|
||||
"sbSynced": 1586819259,
|
||||
"sbSynced2": 1586822456,
|
||||
"sbUpdated": "1596079143",
|
||||
"sbVersion": "2.9.13",
|
||||
"security": "user",
|
||||
"shareAvahiEnabled": true,
|
||||
"shareAvahiSmbModel": "Xserve",
|
||||
"shareAvahiSmbName": "%h",
|
||||
"shareCacheEnabled": true,
|
||||
"shareCacheFloor": "2000000",
|
||||
"shareCount": 0,
|
||||
"shareDisk": "yes",
|
||||
"shareInitialGroup": "Domain Users",
|
||||
"shareInitialOwner": "Administrator",
|
||||
"shareMoverActive": false,
|
||||
"shareMoverLogging": false,
|
||||
"shareMoverSchedule": "40 3 * * *",
|
||||
"shareNfsCount": 0,
|
||||
"shareNfsEnabled": false,
|
||||
"shareSmbCount": 1,
|
||||
"shareSmbEnabled": true,
|
||||
"shareSmbMode": "workgroup",
|
||||
"shareUser": "e",
|
||||
"shareUserExclude": "",
|
||||
"shareUserInclude": "",
|
||||
"shfsLogging": "1",
|
||||
"shutdownTimeout": 90,
|
||||
"spindownDelay": 0,
|
||||
"spinupGroups": false,
|
||||
"startArray": false,
|
||||
"startMode": "Normal",
|
||||
"startPage": "Main",
|
||||
"sysArraySlots": 24,
|
||||
"sysCacheSlots": NaN,
|
||||
"sysFlashSlots": 1,
|
||||
"sysModel": "Dell R710",
|
||||
"timeZone": "Australia/Adelaide",
|
||||
"useNetbios": "yes",
|
||||
"useNtp": true,
|
||||
"useSsh": true,
|
||||
"useSsl": null,
|
||||
"useTelnet": true,
|
||||
"useUpnp": true,
|
||||
"useWsd": "no",
|
||||
"version": "6.11.2",
|
||||
"workgroup": "WORKGROUP",
|
||||
"wsdOpt": "",
|
||||
}
|
||||
`);
|
||||
{
|
||||
"bindMgt": false,
|
||||
"cacheNumDevices": NaN,
|
||||
"cacheSbNumDisks": NaN,
|
||||
"comment": "Dev Server",
|
||||
"configErrorState": "INELIGIBLE",
|
||||
"configValid": false,
|
||||
"csrfToken": "0000000000000000",
|
||||
"defaultFsType": "xfs",
|
||||
"deviceCount": 4,
|
||||
"domain": "",
|
||||
"domainLogin": "Administrator",
|
||||
"domainShort": "",
|
||||
"enableFruit": "no",
|
||||
"flashGuid": "0000-0000-0000-000000000000",
|
||||
"flashProduct": "DataTraveler_3.0",
|
||||
"flashVendor": "KINGSTON",
|
||||
"fsCopyPrcnt": 0,
|
||||
"fsNumMounted": 0,
|
||||
"fsNumUnmountable": 0,
|
||||
"fsProgress": "Autostart disabled",
|
||||
"fsState": "Stopped",
|
||||
"fsUnmountableMask": "",
|
||||
"fuseDirectio": "auto",
|
||||
"fuseDirectioDefault": "auto",
|
||||
"fuseDirectioStatus": "default",
|
||||
"fuseRemember": "330",
|
||||
"fuseRememberDefault": "330",
|
||||
"fuseRememberStatus": "default",
|
||||
"fuseUseino": "yes",
|
||||
"hideDotFiles": false,
|
||||
"joinStatus": "Not joined",
|
||||
"localMaster": true,
|
||||
"localTld": "local",
|
||||
"luksKeyfile": "/tmp/unraid/keyfile",
|
||||
"maxArraysz": 30,
|
||||
"maxCachesz": 30,
|
||||
"mdColor": "green-blink",
|
||||
"mdNumDisabled": 1,
|
||||
"mdNumDisks": 4,
|
||||
"mdNumErased": 0,
|
||||
"mdNumInvalid": 1,
|
||||
"mdNumMissing": 0,
|
||||
"mdNumNew": 0,
|
||||
"mdNumStripes": 1280,
|
||||
"mdNumStripesDefault": 1280,
|
||||
"mdNumStripesStatus": "default",
|
||||
"mdQueueLimit": "80",
|
||||
"mdQueueLimitDefault": "80",
|
||||
"mdQueueLimitStatus": "default",
|
||||
"mdResync": 0,
|
||||
"mdResyncAction": "check P",
|
||||
"mdResyncCorr": "0",
|
||||
"mdResyncDb": "0",
|
||||
"mdResyncDt": "0",
|
||||
"mdResyncPos": 0,
|
||||
"mdResyncSize": 438960096,
|
||||
"mdScheduler": "auto",
|
||||
"mdSchedulerDefault": "auto",
|
||||
"mdSchedulerStatus": "default",
|
||||
"mdState": "STOPPED",
|
||||
"mdSyncLimit": "5",
|
||||
"mdSyncLimitDefault": "5",
|
||||
"mdSyncLimitStatus": "default",
|
||||
"mdSyncThresh": NaN,
|
||||
"mdSyncThreshDefault": NaN,
|
||||
"mdSyncWindow": NaN,
|
||||
"mdSyncWindowDefault": NaN,
|
||||
"mdVersion": "2.9.14",
|
||||
"mdWriteMethod": NaN,
|
||||
"mdWriteMethodDefault": "auto",
|
||||
"mdWriteMethodStatus": "default",
|
||||
"name": "Tower",
|
||||
"nrRequests": NaN,
|
||||
"nrRequestsDefault": NaN,
|
||||
"nrRequestsStatus": "default",
|
||||
"ntpServer1": "time1.google.com",
|
||||
"ntpServer2": "time2.google.com",
|
||||
"ntpServer3": "time3.google.com",
|
||||
"ntpServer4": "time4.google.com",
|
||||
"pollAttributes": "1800",
|
||||
"pollAttributesDefault": "1800",
|
||||
"pollAttributesStatus": "default",
|
||||
"port": 80,
|
||||
"portssh": 22,
|
||||
"portssl": 443,
|
||||
"porttelnet": 23,
|
||||
"queueDepth": "auto",
|
||||
"regCheck": "Valid",
|
||||
"regExp": "",
|
||||
"regFile": "/app/dev/Unraid.net/Pro.key",
|
||||
"regGen": "0",
|
||||
"regGuid": "13FE-4200-C300-58C372A52B19",
|
||||
"regState": "PRO",
|
||||
"regTm": "1833409182",
|
||||
"regTm2": "0",
|
||||
"regTo": "Eli Bosley",
|
||||
"regTy": "PRO",
|
||||
"reservedNames": "parity,parity2,parity3,diskP,diskQ,diskR,disk,disks,flash,boot,user,user0,disk0,disk1,disk2,disk3,disk4,disk5,disk6,disk7,disk8,disk9,disk10,disk11,disk12,disk13,disk14,disk15,disk16,disk17,disk18,disk19,disk20,disk21,disk22,disk23,disk24,disk25,disk26,disk27,disk28,disk29,disk30,disk31",
|
||||
"safeMode": false,
|
||||
"sbClean": true,
|
||||
"sbEvents": 173,
|
||||
"sbName": "/boot/config/super.dat",
|
||||
"sbNumDisks": 5,
|
||||
"sbState": "1",
|
||||
"sbSyncErrs": 0,
|
||||
"sbSyncExit": "0",
|
||||
"sbSynced": 1586819259,
|
||||
"sbSynced2": 1586822456,
|
||||
"sbUpdated": "1596079143",
|
||||
"sbVersion": "2.9.13",
|
||||
"security": "user",
|
||||
"shareAvahiEnabled": true,
|
||||
"shareAvahiSmbModel": "Xserve",
|
||||
"shareAvahiSmbName": "%h",
|
||||
"shareCacheEnabled": true,
|
||||
"shareCacheFloor": "2000000",
|
||||
"shareCount": 0,
|
||||
"shareDisk": "yes",
|
||||
"shareInitialGroup": "Domain Users",
|
||||
"shareInitialOwner": "Administrator",
|
||||
"shareMoverActive": false,
|
||||
"shareMoverLogging": false,
|
||||
"shareMoverSchedule": "40 3 * * *",
|
||||
"shareNfsCount": 0,
|
||||
"shareNfsEnabled": false,
|
||||
"shareSmbCount": 1,
|
||||
"shareSmbEnabled": true,
|
||||
"shareSmbMode": "workgroup",
|
||||
"shareUser": "e",
|
||||
"shareUserExclude": "",
|
||||
"shareUserInclude": "",
|
||||
"shfsLogging": "1",
|
||||
"shutdownTimeout": 90,
|
||||
"spindownDelay": 0,
|
||||
"spinupGroups": false,
|
||||
"startArray": false,
|
||||
"startMode": "Normal",
|
||||
"startPage": "Main",
|
||||
"sysArraySlots": 24,
|
||||
"sysCacheSlots": NaN,
|
||||
"sysFlashSlots": 1,
|
||||
"sysModel": "Dell R710",
|
||||
"timeZone": "Australia/Adelaide",
|
||||
"useNetbios": "yes",
|
||||
"useNtp": true,
|
||||
"useSsh": true,
|
||||
"useSsl": null,
|
||||
"useTelnet": true,
|
||||
"useUpnp": true,
|
||||
"useWsd": "no",
|
||||
"version": "6.11.2",
|
||||
"workgroup": "WORKGROUP",
|
||||
"wsdOpt": "",
|
||||
}
|
||||
`);
|
||||
});
|
||||
|
||||
@@ -20,7 +20,16 @@ const getUnraidApiLocation = async () => {
|
||||
};
|
||||
|
||||
try {
|
||||
await CommandFactory.run(CliModule, {
|
||||
// Register plugins and create a dynamic module configuration
|
||||
const dynamicModule = await CliModule.registerWithPlugins();
|
||||
|
||||
// Create a new class that extends CliModule with the dynamic configuration
|
||||
const DynamicCliModule = class extends CliModule {
|
||||
static module = dynamicModule.module;
|
||||
static imports = dynamicModule.imports;
|
||||
static providers = dynamicModule.providers;
|
||||
};
|
||||
await CommandFactory.run(DynamicCliModule, {
|
||||
cliName: 'unraid-api',
|
||||
logger: LOG_LEVEL === 'TRACE' ? new LogService() : false, // - enable this to see nest initialization issues
|
||||
completion: {
|
||||
|
||||
@@ -28,5 +28,5 @@ export const pubsub = new PubSub({ eventEmitter });
|
||||
* @param channel The pubsub channel to subscribe to.
|
||||
*/
|
||||
export const createSubscription = (channel: PUBSUB_CHANNEL) => {
|
||||
return pubsub.asyncIterator(channel);
|
||||
return pubsub.asyncIterableIterator(channel);
|
||||
};
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import {
|
||||
type ArrayState,
|
||||
type DiskFsType,
|
||||
type RegistrationState,
|
||||
type registrationType,
|
||||
import type {
|
||||
ArrayState,
|
||||
DiskFsType,
|
||||
RegistrationState,
|
||||
registrationType,
|
||||
} from '@app/graphql/generated/api/types.js';
|
||||
import { ConfigErrorState } from '@app/graphql/generated/api/types.js';
|
||||
|
||||
/**
|
||||
* Global vars
|
||||
@@ -17,7 +18,7 @@ export type Var = {
|
||||
/** Is the array's config valid. */
|
||||
configValid: boolean;
|
||||
/** @internal used to hold the value for config.error */
|
||||
configState: string;
|
||||
configErrorState: ConfigErrorState | null;
|
||||
/** Current CSRF token for HTTP requests with emhttpd. */
|
||||
csrfToken: string;
|
||||
defaultFormat: string;
|
||||
|
||||
@@ -3,36 +3,57 @@ import { homedir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const getPackageJsonVersion = () => {
|
||||
import type { PackageJson, SetRequired } from 'type-fest';
|
||||
|
||||
/**
|
||||
* Tries to get the package.json at the given location.
|
||||
* @param location - The location of the package.json file, relative to the current file
|
||||
* @returns The package.json object or undefined if unable to read
|
||||
*/
|
||||
function readPackageJson(location: string): PackageJson | undefined {
|
||||
try {
|
||||
// Try different possible locations for package.json
|
||||
const possibleLocations = ['../package.json', '../../package.json'];
|
||||
|
||||
for (const location of possibleLocations) {
|
||||
try {
|
||||
const packageJsonUrl = import.meta.resolve(location);
|
||||
const packageJsonPath = fileURLToPath(packageJsonUrl);
|
||||
const packageJson = readFileSync(packageJsonPath, 'utf-8');
|
||||
const packageJsonObject = JSON.parse(packageJson);
|
||||
if (packageJsonObject.version) {
|
||||
return packageJsonObject.version;
|
||||
}
|
||||
} catch {
|
||||
// Continue to next location if this one fails
|
||||
}
|
||||
let packageJsonPath: string;
|
||||
try {
|
||||
const packageJsonUrl = import.meta.resolve(location);
|
||||
packageJsonPath = fileURLToPath(packageJsonUrl);
|
||||
} catch {
|
||||
// Fallback (e.g. for local development): resolve the path relative to this module
|
||||
packageJsonPath = fileURLToPath(new URL(location, import.meta.url));
|
||||
}
|
||||
|
||||
// If we get here, we couldn't find a valid package.json in any location
|
||||
console.error('Could not find package.json in any of the expected locations');
|
||||
return undefined;
|
||||
} catch (error) {
|
||||
console.error('Failed to load package.json:', error);
|
||||
const packageJsonRaw = readFileSync(packageJsonPath, 'utf-8');
|
||||
return JSON.parse(packageJsonRaw) as PackageJson;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the Unraid API package.json. Throws if unable to find.
|
||||
* This should be considered a fatal error.
|
||||
*
|
||||
* @returns The package.json object
|
||||
*/
|
||||
export const getPackageJson = () => {
|
||||
const packageJson = readPackageJson('../package.json') || readPackageJson('../../package.json');
|
||||
if (!packageJson) {
|
||||
throw new Error('Could not find package.json in any of the expected locations');
|
||||
}
|
||||
return packageJson as SetRequired<PackageJson, 'version' | 'dependencies'>;
|
||||
};
|
||||
|
||||
export const API_VERSION =
|
||||
process.env.npm_package_version ?? getPackageJsonVersion() ?? new Error('API_VERSION not set');
|
||||
/**
|
||||
* Returns list of runtime dependencies from the Unraid-API package.json. Returns undefined if
|
||||
* the package.json or its dependency object cannot be found or read.
|
||||
*
|
||||
* Does not log or produce side effects.
|
||||
* @returns The names of all runtime dependencies. Undefined if failed.
|
||||
*/
|
||||
export const getPackageJsonDependencies = (): string[] | undefined => {
|
||||
const { dependencies } = getPackageJson();
|
||||
return Object.keys(dependencies);
|
||||
};
|
||||
|
||||
export const API_VERSION = process.env.npm_package_version ?? getPackageJson().version;
|
||||
|
||||
export const NODE_ENV =
|
||||
(process.env.NODE_ENV as 'development' | 'test' | 'staging' | 'production') ?? 'production';
|
||||
|
||||
@@ -611,6 +611,7 @@ export function LogFileContentSchema(): z.ZodObject<Properties<LogFileContent>>
|
||||
__typename: z.literal('LogFileContent').optional(),
|
||||
content: z.string(),
|
||||
path: z.string(),
|
||||
startLine: z.number().nullish(),
|
||||
totalLines: z.number()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -665,6 +665,8 @@ export type LogFileContent = {
|
||||
content: Scalars['String']['output'];
|
||||
/** Path to the log file */
|
||||
path: Scalars['String']['output'];
|
||||
/** Starting line number of the content (1-indexed) */
|
||||
startLine?: Maybe<Scalars['Int']['output']>;
|
||||
/** Total number of lines in the file */
|
||||
totalLines: Scalars['Int']['output'];
|
||||
};
|
||||
@@ -1156,6 +1158,7 @@ export type Query = {
|
||||
* Get the content of a specific log file
|
||||
* @param path Path to the log file
|
||||
* @param lines Number of lines to read from the end of the file (default: 100)
|
||||
* @param startLine Optional starting line number (1-indexed)
|
||||
*/
|
||||
logFile: LogFileContent;
|
||||
/** List all available log files */
|
||||
@@ -1213,6 +1216,7 @@ export type QuerydockerNetworksArgs = {
|
||||
export type QuerylogFileArgs = {
|
||||
lines?: InputMaybe<Scalars['Int']['input']>;
|
||||
path: Scalars['String']['input'];
|
||||
startLine?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
|
||||
@@ -2559,6 +2563,7 @@ export type LogFileResolvers<ContextType = Context, ParentType extends Resolvers
|
||||
export type LogFileContentResolvers<ContextType = Context, ParentType extends ResolversParentTypes['LogFileContent'] = ResolversParentTypes['LogFileContent']> = ResolversObject<{
|
||||
content?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
path?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
startLine?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
totalLines?: Resolver<ResolversTypes['Int'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
@@ -2,7 +2,7 @@ import { mergeTypeDefs } from '@graphql-tools/merge';
|
||||
|
||||
import { logger } from '@app/core/log.js';
|
||||
|
||||
export const loadTypeDefs = async () => {
|
||||
export const loadTypeDefs = async (additionalTypeDefs: string[] = []) => {
|
||||
// TypeScript now knows this returns Record<string, () => Promise<string>>
|
||||
const typeModules = import.meta.glob('./types/**/*.graphql', { query: '?raw', import: 'default' });
|
||||
|
||||
@@ -19,6 +19,7 @@ export const loadTypeDefs = async () => {
|
||||
if (!files.length) {
|
||||
throw new Error('No GraphQL type definitions found');
|
||||
}
|
||||
files.push(...additionalTypeDefs);
|
||||
return mergeTypeDefs(files);
|
||||
} catch (error) {
|
||||
logger.error('Failed to load GraphQL type definitions:', error);
|
||||
|
||||
@@ -68,7 +68,7 @@ export const createSubscription = (channel: string, resource?: string) => ({
|
||||
});
|
||||
|
||||
hasSubscribedToChannel(context.websocketId, channel);
|
||||
return pubsub.asyncIterator(channel);
|
||||
return pubsub.asyncIterableIterator(channel);
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ export const store = configureStore({
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>;
|
||||
export type AppDispatch = typeof store.dispatch;
|
||||
export type ApiStore = typeof store;
|
||||
|
||||
export const getters = {
|
||||
cache: () => store.getState().cache,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { type IniStringBoolean, type IniStringBooleanOrAuto } from '@app/core/ty
|
||||
import { toNumber } from '@app/core/utils/index.js';
|
||||
import {
|
||||
ArrayState,
|
||||
ConfigErrorState,
|
||||
DiskFsType,
|
||||
RegistrationState,
|
||||
registrationType,
|
||||
@@ -23,7 +24,7 @@ export type VarIni = {
|
||||
cacheSbNumDisks: string;
|
||||
comment: string;
|
||||
configValid: string;
|
||||
configState: string;
|
||||
configErrorState: string;
|
||||
csrfToken: string;
|
||||
defaultFormat: string;
|
||||
defaultFsType: string;
|
||||
@@ -200,6 +201,10 @@ const safeParseMdState = (mdState: string | undefined): ArrayState => {
|
||||
return attemptedParse;
|
||||
};
|
||||
|
||||
export const convertconfigErrorStateToEnum = (configErrorState: string): ConfigErrorState => {
|
||||
return ConfigErrorState[configErrorState.toUpperCase()];
|
||||
};
|
||||
|
||||
export const parse: StateFileToIniParserMap['var'] = (iniFile) => {
|
||||
return {
|
||||
...iniFile,
|
||||
@@ -209,7 +214,9 @@ export const parse: StateFileToIniParserMap['var'] = (iniFile) => {
|
||||
cacheNumDevices: toNumber(iniFile.cacheNumDevices),
|
||||
cacheSbNumDisks: toNumber(iniFile.cacheSbNumDisks),
|
||||
configValid: iniBooleanToJsBoolean(iniFile.configValid, false),
|
||||
configState: iniFile.configValid,
|
||||
configErrorState: iniBooleanToJsBoolean(iniFile.configValid, false)
|
||||
? null
|
||||
: convertconfigErrorStateToEnum(iniFile.configValid),
|
||||
deviceCount: toNumber(iniFile.deviceCount),
|
||||
fsCopyPrcnt: toNumber(iniFile.fsCopyPrcnt),
|
||||
fsNumMounted: toNumber(iniFile.fsNumMounted),
|
||||
|
||||
@@ -11,6 +11,7 @@ import { GraphqlAuthGuard } from '@app/unraid-api/auth/auth.guard.js';
|
||||
import { AuthModule } from '@app/unraid-api/auth/auth.module.js';
|
||||
import { CronModule } from '@app/unraid-api/cron/cron.module.js';
|
||||
import { GraphModule } from '@app/unraid-api/graph/graph.module.js';
|
||||
import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
|
||||
import { RestModule } from '@app/unraid-api/rest/rest.module.js';
|
||||
import { UnraidFileModifierModule } from '@app/unraid-api/unraid-file-modifier/unraid-file-modifier.module.js';
|
||||
|
||||
@@ -46,6 +47,7 @@ import { UnraidFileModifierModule } from '@app/unraid-api/unraid-file-modifier/u
|
||||
},
|
||||
]),
|
||||
UnraidFileModifierModule,
|
||||
PluginModule.registerPlugins(),
|
||||
],
|
||||
controllers: [],
|
||||
providers: [
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { DynamicModule, Module, Provider, Type } from '@nestjs/common';
|
||||
|
||||
import { CommandRunner } from 'nest-commander';
|
||||
|
||||
import { ApiKeyService } from '@app/unraid-api/auth/api-key.service.js';
|
||||
import { AddApiKeyQuestionSet } from '@app/unraid-api/cli/apikey/add-api-key.questions.js';
|
||||
@@ -23,32 +25,76 @@ import { StatusCommand } from '@app/unraid-api/cli/status.command.js';
|
||||
import { StopCommand } from '@app/unraid-api/cli/stop.command.js';
|
||||
import { SwitchEnvCommand } from '@app/unraid-api/cli/switch-env.command.js';
|
||||
import { VersionCommand } from '@app/unraid-api/cli/version.command.js';
|
||||
import { ApiPluginDefinition } from '@app/unraid-api/plugin/plugin.interface.js';
|
||||
import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
|
||||
import { PluginService } from '@app/unraid-api/plugin/plugin.service.js';
|
||||
|
||||
const DEFAULT_COMMANDS = [
|
||||
ApiKeyCommand,
|
||||
ConfigCommand,
|
||||
DeveloperCommand,
|
||||
LogsCommand,
|
||||
ReportCommand,
|
||||
RestartCommand,
|
||||
StartCommand,
|
||||
StatusCommand,
|
||||
StopCommand,
|
||||
SwitchEnvCommand,
|
||||
VersionCommand,
|
||||
SSOCommand,
|
||||
ValidateTokenCommand,
|
||||
AddSSOUserCommand,
|
||||
RemoveSSOUserCommand,
|
||||
ListSSOUserCommand,
|
||||
] as const;
|
||||
|
||||
const DEFAULT_PROVIDERS = [
|
||||
AddApiKeyQuestionSet,
|
||||
AddSSOUserQuestionSet,
|
||||
RemoveSSOUserQuestionSet,
|
||||
DeveloperQuestions,
|
||||
LogService,
|
||||
PM2Service,
|
||||
ApiKeyService,
|
||||
] as const;
|
||||
|
||||
type PluginProvider = Provider & {
|
||||
provide: string | symbol | Type<any>;
|
||||
useValue?: ApiPluginDefinition;
|
||||
};
|
||||
|
||||
@Module({
|
||||
providers: [
|
||||
AddSSOUserCommand,
|
||||
AddSSOUserQuestionSet,
|
||||
RemoveSSOUserCommand,
|
||||
RemoveSSOUserQuestionSet,
|
||||
ListSSOUserCommand,
|
||||
LogService,
|
||||
PM2Service,
|
||||
StartCommand,
|
||||
StopCommand,
|
||||
RestartCommand,
|
||||
ReportCommand,
|
||||
ApiKeyService,
|
||||
ApiKeyCommand,
|
||||
AddApiKeyQuestionSet,
|
||||
SwitchEnvCommand,
|
||||
VersionCommand,
|
||||
StatusCommand,
|
||||
SSOCommand,
|
||||
ValidateTokenCommand,
|
||||
LogsCommand,
|
||||
ConfigCommand,
|
||||
DeveloperCommand,
|
||||
DeveloperQuestions,
|
||||
],
|
||||
imports: [PluginModule],
|
||||
providers: [...DEFAULT_COMMANDS, ...DEFAULT_PROVIDERS],
|
||||
})
|
||||
export class CliModule {}
|
||||
export class CliModule {
|
||||
/**
|
||||
* Get all registered commands
|
||||
* @returns Array of registered command classes
|
||||
*/
|
||||
static getCommands(): Type<CommandRunner>[] {
|
||||
return [...DEFAULT_COMMANDS];
|
||||
}
|
||||
|
||||
/**
|
||||
* Register the module with plugin support
|
||||
* @returns DynamicModule configuration including plugin commands
|
||||
*/
|
||||
static async registerWithPlugins(): Promise<DynamicModule> {
|
||||
const pluginModule = await PluginModule.registerPlugins();
|
||||
|
||||
// Get commands from plugins
|
||||
const pluginCommands: Type<CommandRunner>[] = [];
|
||||
for (const provider of (pluginModule.providers || []) as PluginProvider[]) {
|
||||
if (provider.provide !== PluginService && provider.useValue?.commands) {
|
||||
pluginCommands.push(...provider.useValue.commands);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
module: CliModule,
|
||||
imports: [pluginModule],
|
||||
providers: [...DEFAULT_COMMANDS, ...DEFAULT_PROVIDERS, ...pluginCommands],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -24,38 +24,46 @@ import { ResolversModule } from '@app/unraid-api/graph/resolvers/resolvers.modul
|
||||
import { sandboxPlugin } from '@app/unraid-api/graph/sandbox-plugin.js';
|
||||
import { ServicesResolver } from '@app/unraid-api/graph/services/services.resolver.js';
|
||||
import { SharesResolver } from '@app/unraid-api/graph/shares/shares.resolver.js';
|
||||
import { PluginModule } from '@app/unraid-api/plugin/plugin.module.js';
|
||||
import { PluginService } from '@app/unraid-api/plugin/plugin.service.js';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
ResolversModule,
|
||||
GraphQLModule.forRootAsync<ApolloDriverConfig>({
|
||||
driver: ApolloDriver,
|
||||
useFactory: async () => ({
|
||||
introspection: getters.config()?.local?.sandbox === 'yes',
|
||||
playground: false,
|
||||
context: ({ req, connectionParams, extra }: any) => ({
|
||||
req,
|
||||
connectionParams,
|
||||
extra,
|
||||
}),
|
||||
plugins: [sandboxPlugin, idPrefixPlugin] as any[],
|
||||
subscriptions: {
|
||||
'graphql-ws': {
|
||||
path: '/graphql',
|
||||
imports: [PluginModule],
|
||||
inject: [PluginService],
|
||||
useFactory: async (pluginService: PluginService) => {
|
||||
const plugins = await pluginService.getGraphQLConfiguration();
|
||||
return {
|
||||
introspection: getters.config()?.local?.sandbox === 'yes',
|
||||
playground: false,
|
||||
context: ({ req, connectionParams, extra }: any) => ({
|
||||
req,
|
||||
connectionParams,
|
||||
extra,
|
||||
}),
|
||||
plugins: [sandboxPlugin, idPrefixPlugin] as any[],
|
||||
subscriptions: {
|
||||
'graphql-ws': {
|
||||
path: '/graphql',
|
||||
},
|
||||
},
|
||||
},
|
||||
path: '/graphql',
|
||||
typeDefs: print(await loadTypeDefs()),
|
||||
resolvers: {
|
||||
JSON: JSONResolver,
|
||||
Long: GraphQLLong,
|
||||
UUID: UUIDResolver,
|
||||
DateTime: DateTimeResolver,
|
||||
Port: PortResolver,
|
||||
URL: URLResolver,
|
||||
},
|
||||
validationRules: [NoUnusedVariablesRule],
|
||||
}),
|
||||
path: '/graphql',
|
||||
typeDefs: [print(await loadTypeDefs([plugins.typeDefs]))],
|
||||
resolvers: {
|
||||
JSON: JSONResolver,
|
||||
Long: GraphQLLong,
|
||||
UUID: UUIDResolver,
|
||||
DateTime: DateTimeResolver,
|
||||
Port: PortResolver,
|
||||
URL: URLResolver,
|
||||
...plugins.resolvers,
|
||||
},
|
||||
validationRules: [NoUnusedVariablesRule],
|
||||
};
|
||||
},
|
||||
}),
|
||||
],
|
||||
providers: [
|
||||
|
||||
@@ -21,9 +21,7 @@ export class ConfigResolver {
|
||||
return {
|
||||
id: 'config',
|
||||
valid: emhttp.var.configValid,
|
||||
error: emhttp.var.configValid
|
||||
? null
|
||||
: (ConfigErrorState[emhttp.var.configState] ?? ConfigErrorState.UNKNOWN_ERROR),
|
||||
error: emhttp.var.configValid ? null : emhttp.var.configErrorState,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
61
api/src/unraid-api/plugin/plugin.interface.ts
Normal file
61
api/src/unraid-api/plugin/plugin.interface.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { Logger, Type } from '@nestjs/common';
|
||||
|
||||
import { CommandRunner } from 'nest-commander';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { ApiStore } from '@app/store/index.js';
|
||||
|
||||
export interface PluginMetadata {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
const asyncArray = () => z.function().returns(z.promise(z.array(z.any())));
|
||||
const asyncString = () => z.function().returns(z.promise(z.string()));
|
||||
const asyncVoid = () => z.function().returns(z.promise(z.void()));
|
||||
|
||||
// GraphQL resolver type definitions
|
||||
const resolverFunction = z
|
||||
.function()
|
||||
.args(
|
||||
z.any().optional(), // parent
|
||||
z.any().optional(), // args
|
||||
z.any().optional(), // context
|
||||
z.any().optional() // info
|
||||
)
|
||||
.returns(z.any());
|
||||
|
||||
const resolverFieldMap = z.record(z.string(), resolverFunction);
|
||||
const resolverTypeMap = z.record(
|
||||
z.enum(['Query', 'Mutation', 'Subscription']).or(z.string()),
|
||||
resolverFieldMap
|
||||
);
|
||||
const asyncResolver = () => z.function().returns(z.promise(resolverTypeMap));
|
||||
|
||||
/** Warning: unstable API. The config mechanism and API may soon change. */
|
||||
export const apiPluginSchema = z.object({
|
||||
_type: z.literal('UnraidApiPlugin'),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
commands: z.array(z.custom<Type<CommandRunner>>()),
|
||||
registerGraphQLResolvers: asyncResolver().optional(),
|
||||
registerGraphQLTypeDefs: asyncString().optional(),
|
||||
registerRESTControllers: asyncArray().optional(),
|
||||
registerRESTRoutes: asyncArray().optional(),
|
||||
registerServices: asyncArray().optional(),
|
||||
registerCronJobs: asyncArray().optional(),
|
||||
// These schema definitions are picked up as nest modules as well.
|
||||
onModuleInit: asyncVoid().optional(),
|
||||
onModuleDestroy: asyncVoid().optional(),
|
||||
});
|
||||
|
||||
/** Warning: unstable API. The config mechanism and API may soon change. */
|
||||
export type ApiPluginDefinition = z.infer<typeof apiPluginSchema>;
|
||||
|
||||
// todo: the blocker to publishing this type is the 'ApiStore' type.
|
||||
// It pulls in a lot of irrelevant types (e.g. graphql types) and triggers js transpilation of everything related to the store.
|
||||
// If we can isolate the type, we can publish it to npm and developers can use it as a dev dependency.
|
||||
/**
|
||||
* Represents a subclass of UnraidAPIPlugin that can be instantiated.
|
||||
*/
|
||||
export type ConstructablePlugin = (options: { store: ApiStore; logger: Logger }) => ApiPluginDefinition;
|
||||
20
api/src/unraid-api/plugin/plugin.module.ts
Normal file
20
api/src/unraid-api/plugin/plugin.module.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { DynamicModule, Logger, Module } from '@nestjs/common';
|
||||
|
||||
import { PluginService } from '@app/unraid-api/plugin/plugin.service.js';
|
||||
|
||||
@Module({})
|
||||
export class PluginModule {
|
||||
private static readonly logger = new Logger(PluginModule.name);
|
||||
constructor(private readonly pluginService: PluginService) {}
|
||||
|
||||
static async registerPlugins(): Promise<DynamicModule> {
|
||||
const plugins = await PluginService.getPlugins();
|
||||
const providers = plugins.map((result) => result.provider);
|
||||
return {
|
||||
module: PluginModule,
|
||||
providers: [PluginService, ...providers],
|
||||
exports: [PluginService, ...providers.map((p) => p.provide)],
|
||||
global: true,
|
||||
};
|
||||
}
|
||||
}
|
||||
225
api/src/unraid-api/plugin/plugin.service.ts
Normal file
225
api/src/unraid-api/plugin/plugin.service.ts
Normal file
@@ -0,0 +1,225 @@
|
||||
import { Injectable, Logger, Provider, Type } from '@nestjs/common';
|
||||
|
||||
import { pascalCase } from 'change-case';
|
||||
import { parse } from 'graphql';
|
||||
|
||||
import type {
|
||||
ApiPluginDefinition,
|
||||
ConstructablePlugin,
|
||||
} from '@app/unraid-api/plugin/plugin.interface.js';
|
||||
import { getPackageJsonDependencies as getPackageDependencies } from '@app/environment.js';
|
||||
import { store } from '@app/store/index.js';
|
||||
import { apiPluginSchema } from '@app/unraid-api/plugin/plugin.interface.js';
|
||||
import { batchProcess } from '@app/utils.js';
|
||||
|
||||
type CustomProvider = Provider & {
|
||||
provide: string | symbol | Type<any>;
|
||||
};
|
||||
|
||||
type PluginProvider = {
|
||||
provider: CustomProvider;
|
||||
pluginInstance: ApiPluginDefinition;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class PluginService {
|
||||
private pluginProviders: PluginProvider[] | undefined;
|
||||
private loadingPromise: Promise<PluginProvider[]> | undefined;
|
||||
private static readonly logger = new Logger(PluginService.name);
|
||||
constructor() {
|
||||
this.loadPlugins();
|
||||
}
|
||||
|
||||
private get plugins() {
|
||||
return this.pluginProviders?.map((plugin) => plugin.pluginInstance) ?? [];
|
||||
}
|
||||
|
||||
async loadPlugins() {
|
||||
// If plugins are already loaded, return them
|
||||
if (this.pluginProviders?.length) {
|
||||
return this.pluginProviders;
|
||||
}
|
||||
|
||||
// If getPlugins() is already loading, return its promise
|
||||
if (this.loadingPromise) {
|
||||
return this.loadingPromise;
|
||||
}
|
||||
|
||||
this.loadingPromise = PluginService.getPlugins()
|
||||
.then((plugins) => {
|
||||
if (!this.pluginProviders?.length) {
|
||||
this.pluginProviders = plugins;
|
||||
const pluginNames = this.plugins.map((plugin) => plugin.name);
|
||||
PluginService.logger.debug(
|
||||
`Registered ${pluginNames.length} plugins: ${pluginNames.join(', ')}`
|
||||
);
|
||||
} else {
|
||||
PluginService.logger.debug(
|
||||
`${plugins.length} plugins already registered. Skipping registration.`
|
||||
);
|
||||
}
|
||||
return this.pluginProviders;
|
||||
})
|
||||
.catch((error) => {
|
||||
PluginService.logger.error('Error registering plugins', error);
|
||||
return [];
|
||||
})
|
||||
.finally(() => {
|
||||
// clear loading state
|
||||
this.loadingPromise = undefined;
|
||||
});
|
||||
|
||||
return this.loadingPromise;
|
||||
}
|
||||
|
||||
private static isPluginFactory(factory: unknown): factory is ConstructablePlugin {
|
||||
return typeof factory === 'function';
|
||||
}
|
||||
|
||||
private static async getPluginFromPackage(pluginPackage: string): Promise<{
|
||||
provider: CustomProvider;
|
||||
pluginInstance: ApiPluginDefinition;
|
||||
}> {
|
||||
const moduleImport = await import(/* @vite-ignore */ pluginPackage);
|
||||
const pluginName = pascalCase(pluginPackage);
|
||||
const PluginFactory = moduleImport.default || moduleImport[pluginName];
|
||||
|
||||
if (!PluginService.isPluginFactory(PluginFactory)) {
|
||||
throw new Error(`Invalid plugin from ${pluginPackage}. Must export a factory function.`);
|
||||
}
|
||||
|
||||
const logger = new Logger(PluginFactory.name);
|
||||
const validation = apiPluginSchema.safeParse(PluginFactory({ store, logger }));
|
||||
if (!validation.success) {
|
||||
throw new Error(`Invalid plugin from ${pluginPackage}: ${validation.error}`);
|
||||
}
|
||||
const pluginInstance = validation.data;
|
||||
|
||||
return {
|
||||
provider: {
|
||||
provide: PluginFactory.name,
|
||||
useValue: pluginInstance,
|
||||
},
|
||||
pluginInstance,
|
||||
};
|
||||
}
|
||||
|
||||
static async getPlugins() {
|
||||
/** All api plugins must be npm packages whose name starts with this prefix */
|
||||
const pluginPrefix = 'unraid-api-plugin-';
|
||||
// All api plugins must be installed as dependencies of the unraid-api package
|
||||
/** list of npm packages that are unraid-api plugins */
|
||||
const plugins = getPackageDependencies()?.filter((pkgName) => pkgName.startsWith(pluginPrefix));
|
||||
if (!plugins) {
|
||||
PluginService.logger.warn('Could not load dependencies from the Unraid-API package.json');
|
||||
// Fail silently: Return the module without plugins
|
||||
return [];
|
||||
}
|
||||
|
||||
const failedPlugins: string[] = [];
|
||||
const { data: pluginProviders } = await batchProcess(plugins, async (pluginPackage) => {
|
||||
try {
|
||||
return await PluginService.getPluginFromPackage(pluginPackage);
|
||||
} catch (error) {
|
||||
failedPlugins.push(pluginPackage);
|
||||
PluginService.logger.warn(error);
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
if (failedPlugins.length > 0) {
|
||||
PluginService.logger.warn(
|
||||
`${failedPlugins.length} plugins failed to load. Ignoring them: ${failedPlugins.join(', ')}`
|
||||
);
|
||||
}
|
||||
|
||||
return pluginProviders;
|
||||
}
|
||||
|
||||
async getGraphQLConfiguration() {
|
||||
await this.loadPlugins();
|
||||
const plugins = this.plugins;
|
||||
|
||||
let combinedResolvers = {};
|
||||
const typeDefs: string[] = [];
|
||||
|
||||
for (const plugin of plugins) {
|
||||
if (plugin.registerGraphQLResolvers) {
|
||||
const pluginResolvers = await plugin.registerGraphQLResolvers();
|
||||
combinedResolvers = {
|
||||
...combinedResolvers,
|
||||
...pluginResolvers,
|
||||
};
|
||||
}
|
||||
|
||||
if (plugin.registerGraphQLTypeDefs) {
|
||||
const pluginTypeDefs = await plugin.registerGraphQLTypeDefs();
|
||||
try {
|
||||
// Validate schema by parsing it - this will throw if invalid
|
||||
parse(pluginTypeDefs);
|
||||
typeDefs.push(pluginTypeDefs);
|
||||
} catch (error) {
|
||||
const errorMessage = `Plugin ${plugin.name} returned an unusable GraphQL type definition: ${JSON.stringify(
|
||||
pluginTypeDefs
|
||||
)}`;
|
||||
PluginService.logger.warn(errorMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
resolvers: combinedResolvers,
|
||||
typeDefs: typeDefs.join('\n'),
|
||||
};
|
||||
}
|
||||
|
||||
async getRESTConfiguration() {
|
||||
await this.loadPlugins();
|
||||
const controllers: Type<any>[] = [];
|
||||
const routes: Record<string, any>[] = [];
|
||||
|
||||
for (const plugin of this.plugins) {
|
||||
if (plugin.registerRESTControllers) {
|
||||
const pluginControllers = await plugin.registerRESTControllers();
|
||||
controllers.push(...pluginControllers);
|
||||
}
|
||||
|
||||
if (plugin.registerRESTRoutes) {
|
||||
const pluginRoutes = await plugin.registerRESTRoutes();
|
||||
routes.push(...pluginRoutes);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
controllers,
|
||||
routes,
|
||||
};
|
||||
}
|
||||
|
||||
async getServices() {
|
||||
await this.loadPlugins();
|
||||
const services: Type<any>[] = [];
|
||||
|
||||
for (const plugin of this.plugins) {
|
||||
if (plugin.registerServices) {
|
||||
const pluginServices = await plugin.registerServices();
|
||||
services.push(...pluginServices);
|
||||
}
|
||||
}
|
||||
|
||||
return services;
|
||||
}
|
||||
|
||||
async getCronJobs() {
|
||||
await this.loadPlugins();
|
||||
const cronJobs: Record<string, any>[] = [];
|
||||
|
||||
for (const plugin of this.plugins) {
|
||||
if (plugin.registerCronJobs) {
|
||||
const pluginCronJobs = await plugin.registerCronJobs();
|
||||
cronJobs.push(...pluginCronJobs);
|
||||
}
|
||||
}
|
||||
|
||||
return cronJobs;
|
||||
}
|
||||
}
|
||||
@@ -1 +1 @@
|
||||
1741960696861
|
||||
1742838682466
|
||||
@@ -1 +1 @@
|
||||
1741960696132
|
||||
1742838681735
|
||||
@@ -1 +1 @@
|
||||
1741960696408
|
||||
1742838682135
|
||||
@@ -1 +1 @@
|
||||
1741960697108
|
||||
1742838682936
|
||||
@@ -0,0 +1,61 @@
|
||||
import { Logger } from '@nestjs/common';
|
||||
import { readFile, rm, writeFile } from 'node:fs/promises';
|
||||
|
||||
import { fileExists } from '@app/core/utils/files/file-exists.js';
|
||||
import {
|
||||
FileModification,
|
||||
ShouldApplyWithReason,
|
||||
} from '@app/unraid-api/unraid-file-modifier/file-modification.js';
|
||||
|
||||
export class LogViewerModification extends FileModification {
|
||||
id: string = 'log-viewer';
|
||||
public readonly filePath: string =
|
||||
'/usr/local/emhttp/plugins/dynamix.my.servers/LogViewer.page' as const;
|
||||
|
||||
private readonly logViewerConfig: string = `
|
||||
Menu="UNRAID-OS"
|
||||
Title="Log Viewer (new)"
|
||||
Icon="icon-log"
|
||||
Tag="list"
|
||||
---
|
||||
<unraid-i18n-host>
|
||||
<unraid-log-viewer></unraid-log-viewer>
|
||||
</unraid-i18n-host>
|
||||
|
||||
`.trimStart();
|
||||
|
||||
constructor(logger: Logger) {
|
||||
super(logger);
|
||||
}
|
||||
|
||||
protected async generatePatch(overridePath?: string): Promise<string> {
|
||||
const currentContent = (await fileExists(this.filePath))
|
||||
? await readFile(this.filePath, 'utf8')
|
||||
: '';
|
||||
|
||||
return this.createPatchWithDiff(
|
||||
overridePath ?? this.filePath,
|
||||
currentContent,
|
||||
this.logViewerConfig
|
||||
);
|
||||
}
|
||||
|
||||
async shouldApply(): Promise<ShouldApplyWithReason> {
|
||||
const alreadyConfigured = await fileExists(this.filePath);
|
||||
if (alreadyConfigured) {
|
||||
return { shouldApply: false, reason: 'LogViewer configuration already exists' };
|
||||
}
|
||||
return { shouldApply: true, reason: 'No LogViewer config for the API configured yet' };
|
||||
}
|
||||
|
||||
async apply(): Promise<string> {
|
||||
await this.rollback();
|
||||
await writeFile(this.filePath, this.logViewerConfig, { mode: 0o644 });
|
||||
return this.logViewerConfig;
|
||||
}
|
||||
|
||||
async rollback(): Promise<void> {
|
||||
await rm(this.getPathToAppliedPatch(), { force: true });
|
||||
await rm(this.filePath, { force: true });
|
||||
}
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { FileModification } from '@app/unraid-api/unraid-file-modifier/file-modi
|
||||
import AuthRequestModification from '@app/unraid-api/unraid-file-modifier/modifications/auth-request.modification.js';
|
||||
import DefaultPageLayoutModification from '@app/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification.js';
|
||||
import { LogRotateModification } from '@app/unraid-api/unraid-file-modifier/modifications/log-rotate.modification.js';
|
||||
import { LogViewerModification } from '@app/unraid-api/unraid-file-modifier/modifications/log-viewer.modification.js';
|
||||
import NotificationsPageModification from '@app/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.js';
|
||||
import SSOFileModification from '@app/unraid-api/unraid-file-modifier/modifications/sso.modification.js';
|
||||
|
||||
@@ -42,6 +43,7 @@ export class UnraidFileModificationService implements OnModuleInit, OnModuleDest
|
||||
async loadModifications(): Promise<FileModification[]> {
|
||||
const modifications: FileModification[] = [];
|
||||
const modificationClasses: Array<new (logger: Logger) => FileModification> = [
|
||||
LogViewerModification,
|
||||
LogRotateModification,
|
||||
AuthRequestModification,
|
||||
SSOFileModification,
|
||||
|
||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1742069588,
|
||||
"narHash": "sha256-C7jVfohcGzdZRF6DO+ybyG/sqpo1h6bZi9T56sxLy+k=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "c80f6a7e10b39afcc1894e02ef785b1ad0b0d7e5",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
47
flake.nix
Normal file
47
flake.nix
Normal file
@@ -0,0 +1,47 @@
|
||||
{
|
||||
description = "Unraid Connect Monorepo Development Environment";
|
||||
|
||||
inputs = {
|
||||
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, flake-utils }:
|
||||
flake-utils.lib.eachDefaultSystem (system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
buildInputs = with pkgs; [
|
||||
# Node.js and pnpm
|
||||
nodejs_22
|
||||
nodePackages.pnpm
|
||||
|
||||
# Development tools
|
||||
just
|
||||
git
|
||||
|
||||
# libvirt (for development)
|
||||
libvirt
|
||||
|
||||
# Docker (for development)
|
||||
docker
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
echo "🚀 Unraid API Development Environment"
|
||||
echo ""
|
||||
echo "✔︎ BASH version: $BASH_VERSION"
|
||||
echo "✔︎ Node.js version: $(node --version)"
|
||||
echo "✔︎ pnpm version: $(pnpm --version)"
|
||||
echo "✔︎ just version: $(just --version)"
|
||||
echo "✔︎ git version: $(git --version)"
|
||||
echo "✔︎ docker version: $(docker --version)"
|
||||
echo "✔︎ libvirt version: $(virsh --version)"
|
||||
echo ""
|
||||
'';
|
||||
};
|
||||
}
|
||||
);
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "unraid-monorepo",
|
||||
"private": true,
|
||||
"version": "4.2.0",
|
||||
"version": "4.4.1",
|
||||
"scripts": {
|
||||
"build": "pnpm -r build",
|
||||
"build:watch": "pnpm -r build:watch",
|
||||
@@ -35,5 +35,5 @@
|
||||
"dependencies": {
|
||||
"@manypkg/cli": "^0.23.0"
|
||||
},
|
||||
"packageManager": "pnpm@10.4.1"
|
||||
"packageManager": "pnpm@10.6.5"
|
||||
}
|
||||
|
||||
33
packages/unraid-api-plugin-health/index.js
Normal file
33
packages/unraid-api-plugin-health/index.js
Normal file
@@ -0,0 +1,33 @@
|
||||
export default ({ store, logger }) => ({
|
||||
_type: "UnraidApiPlugin",
|
||||
name: "HealthPlugin",
|
||||
description: "Health plugin",
|
||||
|
||||
commands: [],
|
||||
|
||||
async registerGraphQLResolvers() {
|
||||
return {
|
||||
Query: {
|
||||
health: () => {
|
||||
logger.log("Pinged health");
|
||||
return "OK";
|
||||
}
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
async registerGraphQLTypeDefs() {
|
||||
return `
|
||||
type Query {
|
||||
health: String
|
||||
}
|
||||
`;
|
||||
},
|
||||
async onModuleInit() {
|
||||
logger.log("Health plugin initialized");
|
||||
},
|
||||
|
||||
async onModuleDestroy() {
|
||||
logger.log("Health plugin destroyed");
|
||||
},
|
||||
});
|
||||
13
packages/unraid-api-plugin-health/package.json
Normal file
13
packages/unraid-api-plugin-health/package.json
Normal file
@@ -0,0 +1,13 @@
|
||||
{
|
||||
"name": "unraid-api-plugin-health",
|
||||
"version": "1.0.0",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"keywords": [],
|
||||
"author": "Lime Technology, Inc. <unraid.net>",
|
||||
"license": "GPL-2.0-only",
|
||||
"description": "Example Health plugin for Unraid API"
|
||||
}
|
||||
6
plugin/.gitignore
vendored
6
plugin/.gitignore
vendored
@@ -13,4 +13,8 @@ source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-com
|
||||
!source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/.gitkeep
|
||||
|
||||
source/dynamix.unraid.net/usr/local/unraid-api/*
|
||||
!source/dynamix.unraid.net/usr/local/unraid-api/.gitkeep
|
||||
!source/dynamix.unraid.net/usr/local/unraid-api/.gitkeep
|
||||
|
||||
source/dynamix.unraid.net/install/doinst.sh
|
||||
|
||||
packed-pnpm-store.txz
|
||||
|
||||
@@ -53,7 +53,7 @@ Then install the plugin on your Unraid development machine by visiting:
|
||||
|
||||
Then paste the following URL into the Unraid Plugins page:
|
||||
|
||||
`http://YOUR_LOCAL_DEV_MACHINE_IP:8080/plugins/local/dynamix.unraid.net.plg`
|
||||
`http://YOUR_LOCAL_DEV_MACHINE_IP:5858/plugins/local/dynamix.unraid.net.plg`
|
||||
|
||||
Replace `SERVER_NAME` with your development machine's hostname.
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@ import { $ } from "zx";
|
||||
import { escape as escapeHtml } from "html-sloppy-escaper";
|
||||
import { dirname, join } from "node:path";
|
||||
import { getTxzName, pluginName, startingDir } from "./utils/consts";
|
||||
import { getPluginUrl } from "./utils/bucket-urls";
|
||||
import { getAssetUrl, getPluginUrl } from "./utils/bucket-urls";
|
||||
import { getMainTxzUrl } from "./utils/bucket-urls";
|
||||
import {
|
||||
deployDir,
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
} from "./utils/paths";
|
||||
import { PluginEnv, setupPluginEnv } from "./cli/setup-plugin-environment";
|
||||
import { cleanupPluginFiles } from "./utils/cleanup";
|
||||
import { bundlePnpmStore, getPnpmBundleName } from "./build-pnpm-store";
|
||||
|
||||
/**
|
||||
* Check if git is available
|
||||
@@ -60,6 +61,8 @@ const buildPlugin = async ({
|
||||
pluginURL: getPluginUrl({ baseUrl, tag }),
|
||||
MAIN_TXZ: getMainTxzUrl({ baseUrl, pluginVersion, tag }),
|
||||
TXZ_SHA256: txzSha256,
|
||||
VENDOR_STORE_URL: getAssetUrl({ baseUrl, tag }, getPnpmBundleName()),
|
||||
VENDOR_STORE_FILENAME: getPnpmBundleName(),
|
||||
...(tag ? { TAG: tag } : {}),
|
||||
};
|
||||
|
||||
@@ -67,7 +70,9 @@ const buildPlugin = async ({
|
||||
// Iterate over entities and update them
|
||||
Object.entries(entities).forEach(([key, value]) => {
|
||||
if (!value) {
|
||||
throw new Error(`Entity ${key} not set in entities: ${JSON.stringify(entities)}`);
|
||||
throw new Error(
|
||||
`Entity ${key} not set in entities: ${JSON.stringify(entities)}`
|
||||
);
|
||||
}
|
||||
plgContent = updateEntityValue(plgContent, key, value);
|
||||
});
|
||||
@@ -94,11 +99,16 @@ const buildPlugin = async ({
|
||||
const main = async () => {
|
||||
try {
|
||||
const validatedEnv = await setupPluginEnv(process.argv);
|
||||
await checkGit();
|
||||
if (validatedEnv.tag === "LOCAL_PLUGIN_BUILD") {
|
||||
console.log("Skipping git check for LOCAL_PLUGIN_BUILD");
|
||||
} else {
|
||||
await checkGit();
|
||||
}
|
||||
await cleanupPluginFiles();
|
||||
|
||||
await buildPlugin(validatedEnv);
|
||||
await moveTxzFile(validatedEnv.txzPath, validatedEnv.pluginVersion);
|
||||
await bundlePnpmStore();
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
process.exit(1);
|
||||
|
||||
42
plugin/builder/build-pnpm-store.ts
Normal file
42
plugin/builder/build-pnpm-store.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { apiDir, deployDir } from "./utils/paths";
|
||||
import { join } from "path";
|
||||
import { readFileSync } from "node:fs";
|
||||
import { startingDir } from "./utils/consts";
|
||||
import { copyFile } from "node:fs/promises";
|
||||
|
||||
/**
|
||||
* Get the version of the API from the package.json file
|
||||
*
|
||||
* Throws if package.json is not found or is invalid JSON.
|
||||
* @returns The version of the API
|
||||
*/
|
||||
function getVersion(): string {
|
||||
const packageJsonPath = join(apiDir, "package.json");
|
||||
const packageJsonString = readFileSync(packageJsonPath, "utf8");
|
||||
const packageJson = JSON.parse(packageJsonString);
|
||||
return packageJson.version;
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the pnpm store archive that will be vendored with the plugin.
|
||||
* @returns The name of the pnpm store bundle file
|
||||
*/
|
||||
export function getPnpmBundleName(): string {
|
||||
const version = getVersion();
|
||||
return `pnpm-store-for-v${version}.txz`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare a versioned bundle of the API's pnpm store to vendor dependencies.
|
||||
*
|
||||
* It expects a generic `packed-pnpm-store.txz` archive to be available in the `startingDir`.
|
||||
* It copies this archive to the `deployDir` directory and adds a version to the filename.
|
||||
* It does not actually create the packed pnpm store archive; that is done inside the API's build script.
|
||||
*
|
||||
* After this operation, the vendored store will be available inside the `deployDir`.
|
||||
*/
|
||||
export async function bundlePnpmStore(): Promise<void> {
|
||||
const storeArchive = join(startingDir, "packed-pnpm-store.txz");
|
||||
const pnpmStoreTarPath = join(deployDir, getPnpmBundleName());
|
||||
await copyFile(storeArchive, pnpmStoreTarPath);
|
||||
}
|
||||
@@ -5,6 +5,7 @@ import { readdir } from "node:fs/promises";
|
||||
import { getTxzName, pluginName, startingDir } from "./utils/consts";
|
||||
import { setupTxzEnv, TxzEnv } from "./cli/setup-txz-environment";
|
||||
import { cleanupTxzFiles } from "./utils/cleanup";
|
||||
import { apiDir } from "./utils/paths";
|
||||
|
||||
// Recursively search for manifest files
|
||||
const findManifestFiles = async (dir: string): Promise<string[]> => {
|
||||
@@ -74,14 +75,6 @@ const validateSourceDir = async (validatedEnv: TxzEnv) => {
|
||||
);
|
||||
}
|
||||
|
||||
const apiDir = join(
|
||||
startingDir,
|
||||
"source",
|
||||
pluginName,
|
||||
"usr",
|
||||
"local",
|
||||
"unraid-api"
|
||||
);
|
||||
if (!existsSync(apiDir)) {
|
||||
throw new Error(`API directory ${apiDir} does not exist`);
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ export const setupPluginEnv = async (argv: string[]): Promise<PluginEnv> => {
|
||||
"Base URL - will be used to determine the bucket, and combined with the tag (if set) to form the final URL",
|
||||
process.env.CI === "true"
|
||||
? "This is a CI build, please set the base URL manually"
|
||||
: `http://${process.env.HOST_LAN_IP}:8080`
|
||||
: `http://${process.env.HOST_LAN_IP}:5858`
|
||||
)
|
||||
.option(
|
||||
"--txz-path <path>",
|
||||
|
||||
@@ -26,24 +26,25 @@ const getRootBucketPath = ({ baseUrl, tag }: UrlParams): URL => {
|
||||
return url;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get the URL for an asset from the root bucket
|
||||
* ex. returns = BASE_URL/TAG/dynamix.unraid.net.plg
|
||||
*/
|
||||
export const getAssetUrl = (params: UrlParams, assetName: string): string => {
|
||||
const rootUrl = getRootBucketPath(params);
|
||||
rootUrl.pathname = rootUrl.pathname.replace(/\/?$/, "/") + assetName;
|
||||
return rootUrl.toString();
|
||||
};
|
||||
/**
|
||||
* Get the URL for the plugin file
|
||||
* ex. returns = BASE_URL/TAG/dynamix.unraid.net.plg
|
||||
*/
|
||||
export const getPluginUrl = (params: UrlParams): string => {
|
||||
const rootUrl = getRootBucketPath(params);
|
||||
// Ensure the path ends with a slash and join with the plugin name
|
||||
rootUrl.pathname = rootUrl.pathname.replace(/\/?$/, "/") + pluginNameWithExt;
|
||||
return rootUrl.toString();
|
||||
};
|
||||
export const getPluginUrl = (params: UrlParams): string =>
|
||||
getAssetUrl(params, pluginNameWithExt);
|
||||
|
||||
/**
|
||||
* Get the URL for the main TXZ file
|
||||
* ex. returns = BASE_URL/TAG/dynamix.unraid.net-4.1.3.txz
|
||||
*/
|
||||
export const getMainTxzUrl = (params: TxzUrlParams): string => {
|
||||
const rootUrl = getRootBucketPath(params);
|
||||
// Ensure the path ends with a slash and join with the txz name
|
||||
rootUrl.pathname = rootUrl.pathname.replace(/\/?$/, "/") + getTxzName(params.pluginVersion);
|
||||
return rootUrl.toString();
|
||||
};
|
||||
export const getMainTxzUrl = (params: TxzUrlParams): string =>
|
||||
getAssetUrl(params, getTxzName(params.pluginVersion));
|
||||
|
||||
@@ -34,6 +34,7 @@ export const getStagingChangelogFromGit = async ({
|
||||
// Encode HTML entities using the 'he' library
|
||||
return changelog ?? "";
|
||||
} catch (err) {
|
||||
throw new Error(`Failed to get changelog from git: ${err}`);
|
||||
console.log('Non-fatal error: Failed to get changelog from git:', err);
|
||||
return tag;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
import { join } from "path";
|
||||
import { getTxzName, pluginNameWithExt } from "./consts";
|
||||
import {
|
||||
getTxzName,
|
||||
pluginName,
|
||||
pluginNameWithExt,
|
||||
startingDir,
|
||||
} from "./consts";
|
||||
|
||||
export interface PathConfig {
|
||||
startingDir: string;
|
||||
@@ -11,6 +16,15 @@ export interface TxzPathConfig extends PathConfig {
|
||||
|
||||
export const deployDir = "deploy" as const;
|
||||
|
||||
export const apiDir = join(
|
||||
startingDir,
|
||||
"source",
|
||||
pluginName,
|
||||
"usr",
|
||||
"local",
|
||||
"unraid-api"
|
||||
);
|
||||
|
||||
/**
|
||||
* Get the path to the root plugin directory
|
||||
* @param startingDir - The starting directory
|
||||
|
||||
@@ -12,6 +12,7 @@ services:
|
||||
- ../unraid-ui/dist-wc:/app/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/uui
|
||||
- ../web/.nuxt/nuxt-custom-elements/dist/unraid-components:/app/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components/nuxt
|
||||
- ../api/deploy/pack/:/app/source/dynamix.unraid.net/usr/local/unraid-api
|
||||
- ../api/deploy/packed-pnpm-store.txz:/app/packed-pnpm-store.txz
|
||||
stdin_open: true # equivalent to -i
|
||||
tty: true # equivalent to -t
|
||||
environment:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/connect-plugin",
|
||||
"version": "4.2.0",
|
||||
"version": "4.4.1",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"commander": "^13.1.0",
|
||||
@@ -21,7 +21,7 @@
|
||||
"build:txz": "tsx builder/build-txz.ts",
|
||||
"build:plugin": "tsx builder/build-plugin.ts",
|
||||
"build:validate": "npm run env:validate && npm run build",
|
||||
"build:watcher": "nodemon --verbose --watch 'source/**/*' --ext ts,js --ignore '*.test.ts' --ignore 'node_modules/**' --ignore 'source/dynamix.unraid.net/install/**' --delay 5s --exec 'pnpm run build'",
|
||||
"build:watcher": "nodemon --verbose --watch 'source/**/*' --watch 'plugins/dynamix.unraid.net.plg' --ext ts,js,plg --ignore '*.test.ts' --ignore 'node_modules/**' --ignore 'source/dynamix.unraid.net/install/**' --delay 5s --exec 'pnpm run build'",
|
||||
"// Docker commands": "",
|
||||
"build:watch": "./scripts/dc.sh pnpm run build:watcher",
|
||||
"docker:build": "docker compose build",
|
||||
@@ -39,5 +39,5 @@
|
||||
"nodemon": "^3.1.7",
|
||||
"vitest": "^3.0.7"
|
||||
},
|
||||
"packageManager": "pnpm@10.4.1"
|
||||
"packageManager": "pnpm@10.6.5"
|
||||
}
|
||||
|
||||
@@ -7,15 +7,28 @@
|
||||
<!ENTITY pluginURL "">
|
||||
<!ENTITY source "/boot/config/plugins/dynamix.my.servers/&name;">
|
||||
<!ENTITY TXZ_SHA256 "">
|
||||
<!-- Node.js Runtime. Required to run the Unraid API. -->
|
||||
<!ENTITY NODEJS_VERSION "22.14.0">
|
||||
<!-- Version is omitted from filename, so we don't need to search/delete other versions when updating the plugin. -->
|
||||
<!ENTITY NODEJS_FILENAME "node-linux-x64.tar.xz">
|
||||
<!-- To get SHA256:
|
||||
wget https://nodejs.org/download/release/v22.14.0/node-v22.14.0-linux-x64.tar.xz
|
||||
sha256sum node-v22.14.0-linux-x64.tar.xz
|
||||
-->
|
||||
<!ENTITY NODEJS_FILENAME "node-v&NODEJS_VERSION;-linux-x64.tar.xz">
|
||||
<!ENTITY NODEJS_SHA256 "69b09dba5c8dcb05c4e4273a4340db1005abeafe3927efda2bc5b249e80437ec">
|
||||
<!ENTITY NODEJS_TXZ "https://nodejs.org/download/release/v&NODEJS_VERSION;/node-v&NODEJS_VERSION;-linux-x64.tar.xz">
|
||||
<!ENTITY MAIN_TXZ "">
|
||||
<!-- PNPM package manager for Node.js. Decouples dependencies from MAIN_TXZ. Prevents supply chain attacks. -->
|
||||
<!-- PNPM_BINARY is the filename of the binary on the boot drive. (In)validated via SHA256. -->
|
||||
<!ENTITY PNPM_BINARY "/boot/config/plugins/dynamix.my.servers/pnpm-linuxstatic-x64">
|
||||
<!ENTITY PNPM_BINARY_URL "https://github.com/pnpm/pnpm/releases/download/v10.7.0/pnpm-linuxstatic-x64">
|
||||
<!ENTITY PNPM_BINARY_SHA256 "714f4c21b63f47ed415f2e59f4bf5c699aa4f58b4d88e15ce6c66cda5631ebb2">
|
||||
<!-- VENDOR_STORE_URL points to an XZ tarball of vendored dependencies (i.e. global pnpm store), specific to the plugin version.
|
||||
This archive may be updated after installation (e.g. when adding api plugins), so we don't verify its hash.
|
||||
It is replaced only when the plugin/api is updated. -->
|
||||
<!ENTITY VENDOR_STORE_URL "">
|
||||
<!-- The archive's filename on the boot drive. Enables reproducible offline installs of the Unraid API. -->
|
||||
<!ENTITY VENDOR_STORE_FILENAME "">
|
||||
<!ENTITY TAG "">
|
||||
]>
|
||||
|
||||
@@ -88,8 +101,6 @@ DNSERR=no
|
||||
|
||||
echo "Checking DNS..."
|
||||
dnscheck "mothership.unraid.net"
|
||||
#dnscheck "wanip4.unraid.net"
|
||||
#dnscheck "backup.unraid.net"
|
||||
|
||||
[[ "${DNSERR}" == "yes" && "${DNS_SERVER1}" != "8.8.8.8" ]] && echo " Recommend navigating to Settings -> Network Settings and changing your DNS server to 8.8.8.8"
|
||||
# Note: DNS checks will fail if the network is not available at boot. Cannot exit the install when DNS checks fail.
|
||||
@@ -105,9 +116,17 @@ exit 0
|
||||
<URL>&NODEJS_TXZ;</URL>
|
||||
<SHA256>&NODEJS_SHA256;</SHA256>
|
||||
</FILE>
|
||||
<FILE Name="&PNPM_BINARY;">
|
||||
<URL>&PNPM_BINARY_URL;</URL>
|
||||
<SHA256>&PNPM_BINARY_SHA256;</SHA256>
|
||||
</FILE>
|
||||
<FILE Name="/boot/config/plugins/dynamix.my.servers/&VENDOR_STORE_FILENAME;">
|
||||
<URL>&VENDOR_STORE_URL;</URL>
|
||||
</FILE>
|
||||
<FILE Run="/bin/bash" Method="install">
|
||||
<INLINE>
|
||||
NODE_FILE="&NODEJS_FILENAME;"
|
||||
VENDOR_ARCHIVE="&VENDOR_STORE_FILENAME;"
|
||||
<![CDATA[
|
||||
# Check if the Node.js archive exists
|
||||
if [[ ! -f "/boot/config/plugins/dynamix.my.servers/${NODE_FILE}" ]]; then
|
||||
@@ -134,8 +153,11 @@ exit 0
|
||||
fi
|
||||
|
||||
# Remove all node js archives from the flashdrive that do not match the expected version
|
||||
find /boot/config/plugins/dynamix.my.servers/ -name "node-v*-linux-x64.tar.xz" ! -name "&NODEJS_FILENAME;" -delete
|
||||
# deprecated Apr 2025. kept to remove unused archives for users upgrading from versioned node downloads.
|
||||
find /boot/config/plugins/dynamix.my.servers/ -name "node-v*-linux-x64.tar.xz" ! -name "${NODE_FILE}" -delete
|
||||
|
||||
# Remove stale pnpm store archives from the boot drive
|
||||
find /boot/config/plugins/dynamix.my.servers/ -name "pnpm-store-for-v*.txz" ! -name "${VENDOR_ARCHIVE}" -delete
|
||||
|
||||
echo "Node.js installation successful"
|
||||
|
||||
@@ -150,18 +172,6 @@ exit 0
|
||||
<SHA256>&TXZ_SHA256;</SHA256>
|
||||
</FILE>
|
||||
|
||||
<FILE Run="/bin/bash" Method="install">
|
||||
<INLINE>
|
||||
MAINTXZ="&source;.txz"
|
||||
<![CDATA[
|
||||
# before proceeding with install, doubly confirm downloaded files exist. just being pedantic.
|
||||
FILE=${MAINTXZ} && [[ ! -f "$FILE" ]] && echo "⚠️ file missing - $FILE" && exit 1
|
||||
|
||||
exit 0
|
||||
]]>
|
||||
</INLINE>
|
||||
</FILE>
|
||||
|
||||
<FILE Run="/bin/bash" Method="remove">
|
||||
<INLINE>
|
||||
<![CDATA[
|
||||
@@ -172,6 +182,10 @@ source /etc/unraid-version
|
||||
# Undo some activation / partner setup
|
||||
source /usr/local/emhttp/plugins/dynamix.my.servers/scripts/activation_code_remove
|
||||
|
||||
# Run cleanup operations
|
||||
echo "Performing cleanup operations..."
|
||||
/usr/bin/php /usr/local/emhttp/plugins/dynamix.my.servers/scripts/cleanup_operations.php
|
||||
|
||||
echo
|
||||
echo "⚠️ Do not close this window yet"
|
||||
echo
|
||||
@@ -181,144 +195,28 @@ exit 0
|
||||
</INLINE>
|
||||
</FILE>
|
||||
|
||||
<!-- disable features on uninstall -->
|
||||
<!-- NOTE: this script is PHP not bash -->
|
||||
<FILE Run="/usr/bin/php" Method="remove">
|
||||
<INLINE>
|
||||
<![CDATA[
|
||||
<?
|
||||
$msini = @parse_ini_file('/boot/config/plugins/dynamix.my.servers/myservers.cfg', true);
|
||||
|
||||
echo "\n";
|
||||
echo "**********************************\n";
|
||||
echo "🧹 CLEANING UP - may take a minute\n";
|
||||
echo "**********************************\n";
|
||||
|
||||
if (file_exists("/boot/.git")) {
|
||||
if (file_exists("/etc/rc.d/rc.flash_backup")) {
|
||||
# stop flash backup service
|
||||
echo "\nStopping flash backup service. Please wait…";
|
||||
exec("/etc/rc.d/rc.flash_backup stop &>/dev/null");
|
||||
}
|
||||
if (file_exists("/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php")) {
|
||||
# deactivate and delete local flash backup
|
||||
echo "\nDeactivating flash backup. Please wait…";
|
||||
passthru("/usr/bin/php /usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php deactivate");
|
||||
}
|
||||
}
|
||||
|
||||
if (file_exists("/etc/rc.d/rc.unraid-api")) {
|
||||
echo "\nStopping unraid-api. Please wait…";
|
||||
$output = shell_exec("/etc/rc.d/rc.unraid-api stop --delete 2>&1'");
|
||||
if (!$output) {
|
||||
echo "Waiting for unraid-api to stop...\n";
|
||||
sleep(5); // Give it a few seconds to fully stop
|
||||
}
|
||||
echo "Stopped unraid-api: $output";
|
||||
|
||||
# Find all PIDs referencing main.js and kill them, excluding grep process
|
||||
$pids = shell_exec("ps aux | grep 'node /usr/local/unraid-api/dist/main.js' | grep -v grep | awk '{print $2}'");
|
||||
foreach(explode("\n", trim($pids)) as $pid) {
|
||||
if ($pid) {
|
||||
posix_kill((int)$pid, 9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# set "Allow Remote Access" to "No" and sign out from Unraid Connect
|
||||
if ($msini !== false) {
|
||||
if (!empty($msini['remote']['username'])) {
|
||||
$var = parse_ini_file("/var/local/emhttp/var.ini");
|
||||
$keyfile = @file_get_contents($var['regFILE']);
|
||||
if ($keyfile !== false) {
|
||||
echo "\nSigning out of Unraid Connect\n";
|
||||
$ch = curl_init('https://keys.lime-technology.com/account/server/unregister');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, ['keyfile' => @base64_encode($keyfile)]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
|
||||
# remove myservers.cfg
|
||||
unlink('/boot/config/plugins/dynamix.my.servers/myservers.cfg');
|
||||
|
||||
# reload nginx to disable Remote Access
|
||||
echo "\n⚠️ Reloading Web Server. If this window stops updating for two minutes please close it.\n";
|
||||
exec("/etc/rc.d/rc.nginx reload &>/dev/null");
|
||||
}
|
||||
exit(0);
|
||||
]]>
|
||||
</INLINE>
|
||||
</FILE>
|
||||
|
||||
<!-- disable features when upgrading on unsupported OS versions -->
|
||||
<!-- duplicated from above because the script can't distinguish between install and remove -->
|
||||
<!-- NOTE: this script is PHP not bash -->
|
||||
<!-- Cleanup for install on unsupported OS -->
|
||||
<FILE Run="/usr/bin/php" Method="install">
|
||||
<INLINE>
|
||||
<![CDATA[
|
||||
<?
|
||||
$ver = @parse_ini_file('/etc/unraid-version', true)['version'];
|
||||
$msini = @parse_ini_file('/boot/config/plugins/dynamix.my.servers/myservers.cfg', true);
|
||||
<?php
|
||||
// Check Unraid version
|
||||
$version = @parse_ini_file('/etc/unraid-version', true)['version'];
|
||||
|
||||
// exit this install block if NOT isUnsupportedVersion
|
||||
// must be 6.12.0 or higher (not 6.12.0-[beta|rc])
|
||||
if (version_compare($ver,'6.12.0','>=')) {
|
||||
// Check if this is a supported version
|
||||
// - Must be 6.12.0 or higher
|
||||
// - Must not be a 6.12.0 beta/rc version
|
||||
$is_stable_6_12_or_higher = version_compare($version, '6.12.0', '>=') && !preg_match('/^6\\.12\\.0-/', $version);
|
||||
|
||||
if ($is_stable_6_12_or_higher) {
|
||||
echo "Running on supported version {$version}, skipping cleanup\n";
|
||||
exit(0);
|
||||
}
|
||||
|
||||
echo "\n";
|
||||
echo "**********************************\n";
|
||||
echo "🧹 CLEANING UP - may take a minute\n";
|
||||
echo "**********************************\n";
|
||||
echo "Running on unsupported version {$version}, performing cleanup\n";
|
||||
echo "Running cleanup operations...\n";
|
||||
include_once("/usr/local/emhttp/plugins/dynamix.my.servers/scripts/cleanup_operations.php");
|
||||
|
||||
if (file_exists("/boot/.git")) {
|
||||
if (file_exists("/etc/rc.d/rc.flash_backup")) {
|
||||
# stop flash backup service
|
||||
echo "\nStopping flash backup service. Please wait…";
|
||||
exec("/etc/rc.d/rc.flash_backup stop &>/dev/null");
|
||||
}
|
||||
if (file_exists("/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php")) {
|
||||
# deactivate and delete local flash backup
|
||||
echo "\nDeactivating flash backup. Please wait…";
|
||||
passthru("/usr/bin/php /usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php deactivate");
|
||||
}
|
||||
}
|
||||
|
||||
# set "Allow Remote Access" to "No" and sign out from Unraid Connect
|
||||
if ($msini !== false) {
|
||||
# stop unraid-api
|
||||
echo "\nStopping unraid-api. Please wait…";
|
||||
$output = shell_exec("/etc/rc.d/rc.unraid-api stop --delete 2>&1'");
|
||||
if (!$output) {
|
||||
echo "Waiting for unraid-api to stop...\n";
|
||||
sleep(5); // Give it a few seconds to fully stop
|
||||
}
|
||||
echo "Stopped unraid-api: $output";
|
||||
|
||||
if (!empty($msini['remote']['username'])) {
|
||||
$var = parse_ini_file("/var/local/emhttp/var.ini");
|
||||
$keyfile = @file_get_contents($var['regFILE']);
|
||||
if ($keyfile !== false) {
|
||||
echo "\nSigning out of Unraid Connect\n";
|
||||
$ch = curl_init('https://keys.lime-technology.com/account/server/unregister');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, ['keyfile' => @base64_encode($keyfile)]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
|
||||
# remove myservers.cfg
|
||||
unlink('/boot/config/plugins/dynamix.my.servers/myservers.cfg');
|
||||
|
||||
# reload nginx to disable Remote Access
|
||||
echo "\n⚠️ Reloading Web Server. If this window stops updating for two minutes please close it.\n";
|
||||
exec("/etc/rc.d/rc.nginx reload &>/dev/null");
|
||||
}
|
||||
exit(0);
|
||||
]]>
|
||||
</INLINE>
|
||||
@@ -355,6 +253,7 @@ if [ -e /etc/rc.d/rc.unraid-api ]; then
|
||||
# uninstall the api
|
||||
rm -rf /usr/local/unraid-api
|
||||
rm -rf /var/run/unraid-api.sock
|
||||
rm -rf /usr/.pnpm-store
|
||||
fi
|
||||
]]>
|
||||
</INLINE>
|
||||
@@ -384,6 +283,7 @@ if [ -f /tmp/restore-files-dynamix-unraid-net ]; then
|
||||
"/usr/local/emhttp/plugins/dynamix/include/DefaultPageLayout.php"
|
||||
"/usr/local/emhttp/plugins/dynamix/include/ProvisionCert.php"
|
||||
"/usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php"
|
||||
"/usr/local/emhttp/plugins/dynamix/include/ReplaceKey.php"
|
||||
"/usr/local/emhttp/plugins/dynamix/include/Wrappers.php"
|
||||
"/usr/local/emhttp/plugins/dynamix.plugin.manager/Downgrade.page"
|
||||
"/usr/local/emhttp/plugins/dynamix.plugin.manager/Update.page"
|
||||
@@ -391,6 +291,7 @@ if [ -f /tmp/restore-files-dynamix-unraid-net ]; then
|
||||
"/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/showchanges"
|
||||
"/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck"
|
||||
"/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php"
|
||||
"/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/MyServers.page"
|
||||
"/usr/local/emhttp/plugins/dynamix.my.servers/Registration.page"
|
||||
@@ -456,6 +357,8 @@ exit 0
|
||||
<FILE Run="/bin/bash" Method="install">
|
||||
<INLINE>
|
||||
TAG="&TAG;" MAINTXZ="&source;.txz"
|
||||
VENDOR_ARCHIVE="/boot/config/plugins/dynamix.my.servers/&VENDOR_STORE_FILENAME;"
|
||||
PNPM_BINARY_FILE="&PNPM_BINARY;"
|
||||
<![CDATA[
|
||||
appendTextIfMissing() {
|
||||
FILE="$1" TEXT="$2"
|
||||
@@ -505,10 +408,12 @@ echo
|
||||
preserveFilesDirs=(
|
||||
"move:/usr/local/emhttp/plugins/dynamix/Registration.page:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix/include/ReplaceKey.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/Downgrade.page:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/Update.page:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheckExec.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/MyServers.page:skip"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page:skip"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/Registration.page:preventDowngrade"
|
||||
@@ -525,6 +430,7 @@ preserveFilesDirs=(
|
||||
preserveAction() {
|
||||
local action="$1"
|
||||
local path="$2"
|
||||
local preventType="$3" # preventDowngrade or skip
|
||||
|
||||
if [[ "$action" == "move" ]]; then
|
||||
[[ -f "$path" ]] && mv -f "$path" "$path-"
|
||||
@@ -621,6 +527,7 @@ CFG_OLD=/boot/config/plugins/Unraid.net
|
||||
CFG_NEW=/boot/config/plugins/dynamix.my.servers
|
||||
[[ -d "$CFG_OLD" ]] && [[ ! -d "$CFG_NEW" ]] && mv "$CFG_OLD" "$CFG_NEW"
|
||||
|
||||
|
||||
# relax restrictions on built-in Firefox so it can sign in to Unraid Connect
|
||||
# brings older versions of Unraid in sync with 6.12.0
|
||||
# no need to restore original file on uninstall
|
||||
@@ -908,9 +815,24 @@ fi
|
||||
|
||||
# Create symlink to unraid-api binary (to allow usage elsewhere)
|
||||
ln -sf /usr/local/node/bin/node /usr/local/bin/node
|
||||
ln -sf /usr/local/node/bin/npm /usr/local/bin/npm
|
||||
ln -sf /usr/local/node/bin/corepack /usr/local/bin/corepack
|
||||
|
||||
ln -sf ${unraid_binary_path} /usr/local/sbin/unraid-api
|
||||
ln -sf ${unraid_binary_path} /usr/bin/unraid-api
|
||||
|
||||
cp -f "${PNPM_BINARY_FILE}" /usr/local/bin/pnpm
|
||||
chmod +x /usr/local/bin/pnpm
|
||||
|
||||
/etc/rc.d/rc.unraid-api restore-dependencies "$VENDOR_ARCHIVE"
|
||||
echo
|
||||
echo "⚠️ Do not close this window yet"
|
||||
/etc/rc.d/rc.unraid-api pnpm-install
|
||||
echo
|
||||
echo "⚠️ Do not close this window yet"
|
||||
echo
|
||||
echo "About to start the Unraid API"
|
||||
|
||||
|
||||
logger "Starting flash backup (if enabled)"
|
||||
echo "/etc/rc.d/rc.flash_backup start" | at -M now &>/dev/null
|
||||
|
||||
@@ -391,6 +391,9 @@ if [ "$CHOWN" = "y" ]; then
|
||||
fi
|
||||
|
||||
$SUDO find . -type d -exec chmod 755 {} + || exit 1
|
||||
$SUDO find . -type d -name scripts -exec find {} -type f \; | while read -r file; do
|
||||
$SUDO chmod +x "$file"
|
||||
done
|
||||
$SUDO find . -exec chown 0:0 {} + || exit 1
|
||||
set +e
|
||||
fi
|
||||
|
||||
@@ -8,7 +8,9 @@ flash="/boot/config/plugins/dynamix.my.servers"
|
||||
[[ ! -d "${flash}" ]] && echo "Please reinstall the Unraid Connect plugin" && exit 1
|
||||
[[ ! -f "${flash}/env" ]] && echo 'env=production' >"${flash}/env"
|
||||
unraid_binary_path="/usr/local/bin/unraid-api"
|
||||
pnpm_store_dir="/usr/.pnpm-store"
|
||||
|
||||
# Placeholder functions for plugin installation/uninstallation
|
||||
install() {
|
||||
true;
|
||||
}
|
||||
@@ -16,6 +18,115 @@ uninstall() {
|
||||
true;
|
||||
}
|
||||
|
||||
# Creates a backup of the global pnpm store directory
|
||||
# Args:
|
||||
# $1 - Path to the backup file (tar.xz format)
|
||||
# Returns:
|
||||
# 0 on success, 1 on failure
|
||||
backup_pnpm_store() {
|
||||
# Check if backup file path is provided
|
||||
if [ -z "$1" ]; then
|
||||
echo "Error: Backup file path is required"
|
||||
return 1
|
||||
fi
|
||||
|
||||
local backup_file="$1"
|
||||
|
||||
# Check if pnpm command exists
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "pnpm is not installed. Skipping backup."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Determine the global pnpm store directory
|
||||
mkdir -p "$pnpm_store_dir"
|
||||
|
||||
echo "Backing up pnpm store from '$pnpm_store_dir' to '$backup_file'"
|
||||
|
||||
# Create a tar.gz archive of the global pnpm store
|
||||
if tar -cJf "$backup_file" -C "$(dirname "$pnpm_store_dir")" "$(basename "$pnpm_store_dir")"; then
|
||||
echo "pnpm store backup completed successfully."
|
||||
else
|
||||
echo "Error: Failed to create pnpm store backup."
|
||||
return 1
|
||||
fi
|
||||
}
|
||||
|
||||
# Restores the pnpm store from a backup file
|
||||
# Args:
|
||||
# $1 - Path to the backup file (tar.xz format)
|
||||
# Returns:
|
||||
# 0 on success, 1 on failure
|
||||
# Note: Requires 1.5x the backup size in free space for safe extraction
|
||||
restore_pnpm_store() {
|
||||
# Check if pnpm command exists
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "pnpm is not installed. Cannot restore store."
|
||||
return 1
|
||||
fi
|
||||
|
||||
local backup_file="$1"
|
||||
# Check if backup file exists
|
||||
if [ ! -f "$backup_file" ]; then
|
||||
echo "Backup file not found at '$backup_file'. Skipping restore."
|
||||
return 0
|
||||
fi
|
||||
|
||||
# Check available disk space in destination
|
||||
local backup_size
|
||||
backup_size=$(stat -c%s "$backup_file")
|
||||
local dest_space
|
||||
dest_space=$(df --output=avail "$(dirname "$pnpm_store_dir")" | tail -n1)
|
||||
dest_space=$((dest_space * 1024)) # Convert KB to bytes
|
||||
|
||||
# Require 1.5x the backup size for safe extraction
|
||||
local required_space=$((backup_size + (backup_size / 2)))
|
||||
|
||||
if [ "$dest_space" -lt "$required_space" ]; then
|
||||
echo "Error: Insufficient disk space in destination. Need at least $((required_space / 1024 / 1024))MB, have $((dest_space / 1024 / 1024))MB"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Restoring pnpm store from '$backup_file' to '$pnpm_store_dir'"
|
||||
# Remove existing store directory if it exists and ensure its parent directory exists
|
||||
rm -rf "$pnpm_store_dir"
|
||||
mkdir -p "$(dirname "$pnpm_store_dir")"
|
||||
|
||||
# Extract directly to final location
|
||||
if ! tar -xJf "$backup_file" -C "$(dirname "$pnpm_store_dir")" --preserve-permissions; then
|
||||
echo "Error: Failed to extract backup to final location."
|
||||
rm -rf "$pnpm_store_dir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "pnpm store restored successfully."
|
||||
}
|
||||
|
||||
# Installs production dependencies for the unraid-api using pnpm. Prefers offline mode.
|
||||
# Uses the api_base_directory variable or defaults to /usr/local/unraid-api
|
||||
# Returns:
|
||||
# 0 on success, 1 on failure
|
||||
pnpm_install_unraid_api() {
|
||||
# Check if pnpm command exists
|
||||
if ! command -v pnpm >/dev/null 2>&1; then
|
||||
echo "Error: pnpm command not found. Cannot install dependencies."
|
||||
return 1
|
||||
fi
|
||||
|
||||
# Use the api_base_directory variable if set, otherwise default to /usr/local/unraid-api
|
||||
local unraid_api_dir="${api_base_directory:-/usr/local/unraid-api}"
|
||||
if [ ! -d "$unraid_api_dir" ]; then
|
||||
echo "Error: unraid API directory '$unraid_api_dir' does not exist."
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo "Executing 'pnpm install' in $unraid_api_dir"
|
||||
rm -rf /usr/local/unraid-api/node_modules
|
||||
# Run pnpm install in a subshell to prevent changing the current working directory of the script
|
||||
( cd "$unraid_api_dir" && pnpm install --prod --prefer-offline )
|
||||
}
|
||||
|
||||
|
||||
case "$1" in
|
||||
'install')
|
||||
install "$2"
|
||||
@@ -26,6 +137,15 @@ case "$1" in
|
||||
'uninstall')
|
||||
uninstall
|
||||
;;
|
||||
'pnpm-install')
|
||||
pnpm_install_unraid_api
|
||||
;;
|
||||
'backup-dependencies')
|
||||
backup_pnpm_store "$2"
|
||||
;;
|
||||
'restore-dependencies')
|
||||
restore_pnpm_store "$2"
|
||||
;;
|
||||
*)
|
||||
# Pass all other commands to unraid-api
|
||||
"${unraid_binary_path}" "$@"
|
||||
|
||||
@@ -1,316 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This runs both during package removal and installation
|
||||
# $1 will be "remove" during package removal
|
||||
# $1 will be "install" during package installation
|
||||
|
||||
if [ "$1" = "remove" ]; then
|
||||
# Clean up node_modules before package removal
|
||||
rm -rf /usr/local/unraid-api/node_modules
|
||||
fi
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../blessed/bin/tput.js blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esbuild/bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/escodegen.js escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/esgenerate.js esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esparse.js esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esvalidate.js esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../fast-xml-parser/src/cli/cli.js fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../glob/dist/esm/bin.mjs glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../js-yaml/bin/js-yaml.js js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../jsesc/bin/jsesc jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../loose-envify/cli.js loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mime/cli.js mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mkdirp/bin/cmd.js mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mustache/bin/mustache mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../needle/bin/needle needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../which/bin/node-which node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@babel/parser/bin/babel-parser.js parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino/bin.js pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino-pretty/bin.js pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2 pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-dev pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-docker pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-runtime pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../prettier/bin/prettier.cjs prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@ardatan/relay-compiler/bin/relay-compiler relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../resolve/bin/resolve resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sha.js/bin.js sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-conv sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-sign sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-verify sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../systeminformation/lib/cli.js systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsc tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsserver tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../tsx/dist/cli.mjs tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../ua-parser-js/script/cli.js ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf xss )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../xss/bin/xss xss )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; ln -sf ../uuid/dist/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; ln -sf ../../../../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/schedule/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/schedule/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; ln -sf ../../bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; ln -sf ../uuid/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../blessed/bin/tput.js blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esbuild/bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/escodegen.js escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/esgenerate.js esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esparse.js esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esvalidate.js esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../fast-xml-parser/src/cli/cli.js fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../glob/dist/esm/bin.mjs glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../js-yaml/bin/js-yaml.js js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../jsesc/bin/jsesc jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../loose-envify/cli.js loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mime/cli.js mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mkdirp/bin/cmd.js mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mustache/bin/mustache mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../needle/bin/needle needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../which/bin/node-which node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@babel/parser/bin/babel-parser.js parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino/bin.js pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino-pretty/bin.js pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2 pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-dev pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-docker pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-runtime pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../prettier/bin/prettier.cjs prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@ardatan/relay-compiler/bin/relay-compiler relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../resolve/bin/resolve resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sha.js/bin.js sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-conv sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-sign sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-verify sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../systeminformation/lib/cli.js systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsc tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsserver tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../tsx/dist/cli.mjs tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../ua-parser-js/script/cli.js ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf xss )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../xss/bin/xss xss )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; ln -sf ../uuid/dist/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; ln -sf ../../../../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; ln -sf ../../bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/nestjs-pino/node_modules/.bin ; rm -rf pino )
|
||||
( cd usr/local/unraid-api/node_modules/nestjs-pino/node_modules/.bin ; ln -sf ../../../pino/bin.js pino )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; ln -sf ../uuid/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@apollo/protobufjs/bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../blessed/bin/tput.js blessed )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esbuild/bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/escodegen.js escodegen )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../escodegen/bin/esgenerate.js esgenerate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esparse.js esparse )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../esprima/bin/esvalidate.js esvalidate )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../fast-xml-parser/src/cli/cli.js fxparser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../glob/dist/esm/bin.mjs glob )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../js-yaml/bin/js-yaml.js js-yaml )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../jsesc/bin/jsesc jsesc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../loose-envify/cli.js loose-envify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mime/cli.js mime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mkdirp/bin/cmd.js mkdirp )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../mustache/bin/mustache mustache )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../needle/bin/needle needle )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../which/bin/node-which node-which )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@babel/parser/bin/babel-parser.js parser )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino/bin.js pino )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pino-pretty/bin.js pino-pretty )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2 pm2 )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-dev pm2-dev )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-docker pm2-docker )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../pm2/bin/pm2-runtime pm2-runtime )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../prettier/bin/prettier.cjs prettier )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../@ardatan/relay-compiler/bin/relay-compiler relay-compiler )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../resolve/bin/resolve resolve )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sha.js/bin.js sha.js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-conv sshpk-conv )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-sign sshpk-sign )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../sshpk/bin/sshpk-verify sshpk-verify )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../systeminformation/lib/cli.js systeminformation )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsc tsc )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../typescript/bin/tsserver tsserver )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../tsx/dist/cli.mjs tsx )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../ua-parser-js/script/cli.js ua-parser-js )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; rm -rf xss )
|
||||
( cd usr/local/unraid-api/node_modules/.bin ; ln -sf ../xss/bin/xss xss )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbjs apollo-pbjs )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; rm -rf apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/protobufjs/node_modules/.bin ; ln -sf ../../bin/pbts apollo-pbts )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@apollo/server/node_modules/.bin ; ln -sf ../uuid/dist/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; rm -rf opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/core/node_modules/.bin ; ln -sf ../../../../@nuxtjs/opencollective/bin/opencollective.js opencollective )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@nestjs/graphql/node_modules/.bin ; ln -sf ../uuid/dist/esm/bin/uuid uuid )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/agent/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; rm -rf semver )
|
||||
( cd usr/local/unraid-api/node_modules/@pm2/io/node_modules/.bin ; ln -sf ../semver/bin/semver.js semver )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; rm -rf esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/esbuild/node_modules/.bin ; ln -sf ../../bin/esbuild esbuild )
|
||||
( cd usr/local/unraid-api/node_modules/nestjs-pino/node_modules/.bin ; rm -rf pino )
|
||||
( cd usr/local/unraid-api/node_modules/nestjs-pino/node_modules/.bin ; ln -sf ../../../pino/bin.js pino )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; rm -rf uuid )
|
||||
( cd usr/local/unraid-api/node_modules/request/node_modules/.bin ; ln -sf ../uuid/bin/uuid uuid )
|
||||
@@ -1,19 +0,0 @@
|
||||
Menu="UNRAID-OS"
|
||||
Title="Log Viewer (new)"
|
||||
Icon="icon-log"
|
||||
Tag="list"
|
||||
---
|
||||
<?php
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
* as published by the Free Software Foundation.
|
||||
*
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<unraid-i18n-host>
|
||||
<unraid-log-viewer></unraid-log-viewer>
|
||||
</unraid-i18n-host>
|
||||
@@ -14,6 +14,9 @@ Tag="pencil"
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
require_once "$docroot/plugins/dynamix/include/ReplaceKey.php";
|
||||
$replaceKey = new ReplaceKey();
|
||||
$replaceKey->check(true);
|
||||
?>
|
||||
<unraid-i18n-host>
|
||||
<unraid-registration></unraid-registration>
|
||||
|
||||
@@ -96,10 +96,17 @@ class ServerState
|
||||
|
||||
$this->var = $webguiGlobals['var'];
|
||||
|
||||
// If we're on a patch, we need to use the combinedVersion to check for updates
|
||||
$patcherVersion = null;
|
||||
if (file_exists('/tmp/Patcher/patches.json')) {
|
||||
$patchJson = @json_decode(@file_get_contents('/tmp/Patcher/patches.json'), true) ?: [];
|
||||
$this->var['version'] = $patchJson['combinedVersion'] ?? $this->var['version'];
|
||||
$patcherData = @json_decode(file_get_contents('/tmp/Patcher/patches.json'), true);
|
||||
$unraidVersionInfo = parse_ini_file('/etc/unraid-version');
|
||||
if ($patcherData['unraidVersion'] === $unraidVersionInfo['version']) {
|
||||
$patcherVersion = $patcherData['combinedVersion'] ?? null;
|
||||
}
|
||||
}
|
||||
// If we're on a patch, we need to use the combinedVersion to check for updates
|
||||
if ($patcherVersion) {
|
||||
$this->var['version'] = $patcherVersion;
|
||||
}
|
||||
|
||||
$this->nginxCfg = @parse_ini_file('/var/local/emhttp/nginx.ini') ?? [];
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
#!/usr/bin/php
|
||||
<?php
|
||||
// Script Name: cleanup_operations.php
|
||||
// Purpose: Handles cleanup operations for both install on unsupported OS and during removal
|
||||
//
|
||||
// Usage:
|
||||
// ./cleanup_operations.php
|
||||
// ./cleanup_operations.php --debug
|
||||
|
||||
// Parse command line arguments
|
||||
$debug = false;
|
||||
|
||||
if (isset($argv)) {
|
||||
foreach ($argv as $arg) {
|
||||
if ($arg === '--debug') {
|
||||
$debug = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Debug function
|
||||
function debug_log($message) {
|
||||
global $debug;
|
||||
if ($debug) {
|
||||
echo "[DEBUG] [cleanup_operations]: $message\n";
|
||||
}
|
||||
}
|
||||
|
||||
// Get Unraid version and myservers config
|
||||
$ver = @parse_ini_file('/etc/unraid-version', true)['version'];
|
||||
$msini = @parse_ini_file('/boot/config/plugins/dynamix.my.servers/myservers.cfg', true);
|
||||
|
||||
debug_log("Unraid version: $ver");
|
||||
debug_log("myservers.cfg exists: " . ($msini !== false ? "Yes" : "No"));
|
||||
|
||||
echo "\n";
|
||||
echo "**********************************\n";
|
||||
echo "🧹 CLEANING UP - may take a minute\n";
|
||||
echo "**********************************\n";
|
||||
|
||||
if (file_exists("/boot/.git")) {
|
||||
if (file_exists("/etc/rc.d/rc.flash_backup")) {
|
||||
# stop flash backup service
|
||||
echo "\nStopping flash backup service. Please wait…";
|
||||
exec("/etc/rc.d/rc.flash_backup stop &>/dev/null");
|
||||
}
|
||||
if (file_exists("/usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php")) {
|
||||
# deactivate and delete local flash backup
|
||||
echo "\nDeactivating flash backup. Please wait…";
|
||||
passthru("/usr/bin/php /usr/local/emhttp/plugins/dynamix.my.servers/include/UpdateFlashBackup.php deactivate");
|
||||
}
|
||||
}
|
||||
|
||||
# set "Allow Remote Access" to "No" and sign out from Unraid Connect
|
||||
if ($msini !== false) {
|
||||
# stop unraid-api
|
||||
echo "\nStopping unraid-api. Please wait…";
|
||||
$output = shell_exec("/etc/rc.d/rc.unraid-api stop --delete 2>&1");
|
||||
if (!$output) {
|
||||
echo "Waiting for unraid-api to stop...\n";
|
||||
sleep(5); // Give it a few seconds to fully stop
|
||||
}
|
||||
echo "Stopped unraid-api: $output";
|
||||
|
||||
if (!empty($msini['remote']['username'])) {
|
||||
$var = parse_ini_file("/var/local/emhttp/var.ini");
|
||||
$keyfile = @file_get_contents($var['regFILE']);
|
||||
if ($keyfile !== false) {
|
||||
echo "\nSigning out of Unraid Connect\n";
|
||||
$ch = curl_init('https://keys.lime-technology.com/account/server/unregister');
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, ['keyfile' => @base64_encode($keyfile)]);
|
||||
curl_exec($ch);
|
||||
curl_close($ch);
|
||||
}
|
||||
}
|
||||
|
||||
# remove myservers.cfg
|
||||
unlink('/boot/config/plugins/dynamix.my.servers/myservers.cfg');
|
||||
|
||||
# reload nginx to disable Remote Access
|
||||
echo "\n⚠️ Reloading Web Server. If this window stops updating for two minutes please close it.\n";
|
||||
exec("/etc/rc.d/rc.nginx reload &>/dev/null");
|
||||
}
|
||||
|
||||
exit(0);
|
||||
0
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/scripts/gitflash_log
Normal file → Executable file
0
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/scripts/gitflash_log
Normal file → Executable file
@@ -16,11 +16,12 @@ Tag="upload"
|
||||
/**
|
||||
* @note icon-update is rotated via CSS in myservers1.php
|
||||
*/
|
||||
require_once "$docroot/plugins/dynamix/include/ReplaceKey.php";
|
||||
$replaceKey = new ReplaceKey();
|
||||
$replaceKey->check();
|
||||
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/reboot-details.php";
|
||||
// Create an instance of the RebootDetails class
|
||||
$rebootDetails = new RebootDetails();
|
||||
// Get the current reboot details if there are any
|
||||
$rebootDetails->setPrevious();
|
||||
|
||||
$serverNameEscaped = htmlspecialchars(str_replace(' ', '_', strtolower($var['NAME'])));
|
||||
|
||||
@@ -13,6 +13,10 @@ Tag="upload"
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
require_once "$docroot/plugins/dynamix/include/ReplaceKey.php";
|
||||
$replaceKey = new ReplaceKey();
|
||||
$replaceKey->check();
|
||||
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/reboot-details.php";
|
||||
$rebootDetails = new RebootDetails();
|
||||
?>
|
||||
|
||||
@@ -119,7 +119,10 @@ class UnraidOsCheck
|
||||
$patcherVersion = null;
|
||||
if (file_exists('/tmp/Patcher/patches.json')) {
|
||||
$patcherData = @json_decode(file_get_contents('/tmp/Patcher/patches.json'), true);
|
||||
$patcherVersion = $patcherData['combinedVersion'] ?? null;
|
||||
$unraidVersionInfo = parse_ini_file('/etc/unraid-version');
|
||||
if ($patcherData['unraidVersion'] === $unraidVersionInfo['version']) {
|
||||
$patcherVersion = $patcherData['combinedVersion'] ?? null;
|
||||
}
|
||||
}
|
||||
|
||||
$params['current_version'] = $patcherVersion ?: plugin('version', self::PLG_PATH) ?: _var($var, 'version');
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
<?php
|
||||
/**
|
||||
* This file exists to maintain separation of concerns between UnraidCheck and ReplaceKey.
|
||||
* Instead of combining both classes directly, we utilize the unraidcheck script which already
|
||||
* handles both operations in a simplified manner.
|
||||
*
|
||||
* It's called via the WebguiCheckForUpdate function in composables/services/webgui.ts of the web components.
|
||||
* Handles WebguiUnraidCheckExecPayload interface parameters:
|
||||
* - altUrl?: string
|
||||
* - json?: boolean
|
||||
*/
|
||||
class UnraidCheckExec
|
||||
{
|
||||
private const SCRIPT_PATH = '/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck';
|
||||
private const ALLOWED_DOMAIN = 'releases.unraid.net';
|
||||
|
||||
private function setupEnvironment(): void
|
||||
{
|
||||
header('Content-Type: application/json');
|
||||
header('X-Content-Type-Options: nosniff');
|
||||
header('X-Frame-Options: DENY');
|
||||
header('Content-Security-Policy: default-src \'none\'');
|
||||
|
||||
$params = [
|
||||
'json' => 'true',
|
||||
];
|
||||
|
||||
if (isset($_GET['altUrl'])) {
|
||||
$url = filter_var($_GET['altUrl'], FILTER_VALIDATE_URL);
|
||||
if ($url !== false) {
|
||||
$host = parse_url($url, PHP_URL_HOST);
|
||||
$scheme = parse_url($url, PHP_URL_SCHEME);
|
||||
|
||||
if ($host && $scheme === 'https' && (
|
||||
$host === self::ALLOWED_DOMAIN ||
|
||||
str_ends_with($host, '.' . self::ALLOWED_DOMAIN)
|
||||
)) {
|
||||
$params['altUrl'] = $url;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
putenv('QUERY_STRING=' . http_build_query($params));
|
||||
}
|
||||
|
||||
public function execute(): string
|
||||
{
|
||||
// Validate script with all necessary permissions
|
||||
if (!is_file(self::SCRIPT_PATH) ||
|
||||
!is_readable(self::SCRIPT_PATH) ||
|
||||
!is_executable(self::SCRIPT_PATH)) {
|
||||
throw new RuntimeException('Script not found or not executable');
|
||||
}
|
||||
|
||||
$this->setupEnvironment();
|
||||
$output = [];
|
||||
$command = escapeshellcmd(self::SCRIPT_PATH);
|
||||
if (exec($command, $output) === false) {
|
||||
throw new RuntimeException('Script execution failed');
|
||||
}
|
||||
|
||||
return implode("\n", $output);
|
||||
}
|
||||
}
|
||||
|
||||
// Usage
|
||||
$checker = new UnraidCheckExec();
|
||||
echo $checker->execute();
|
||||
9
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck
Normal file → Executable file
9
plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck
Normal file → Executable file
@@ -13,7 +13,14 @@
|
||||
?>
|
||||
<?
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php";
|
||||
|
||||
require_once "$docroot/plugins/dynamix/include/ReplaceKey.php";
|
||||
$replaceKey = new ReplaceKey();
|
||||
$replaceKey->check();
|
||||
|
||||
// utilized by UnraidCheckExec.php to have UnraidCheck.php return a json response when this script is called directly
|
||||
parse_str(getenv('QUERY_STRING') ?? '', $_GET);
|
||||
|
||||
require_once "$docroot/plugins/dynamix.plugin.manager/include/UnraidCheck.php";
|
||||
$unraidOsCheck = new UnraidOsCheck();
|
||||
$unraidOsCheck->checkForUpdate();
|
||||
|
||||
@@ -0,0 +1,237 @@
|
||||
<?php
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
class ReplaceKey
|
||||
{
|
||||
private const KEY_SERVER_URL = 'https://keys.lime-technology.com';
|
||||
|
||||
private $docroot;
|
||||
private $var;
|
||||
private $guid;
|
||||
private $keyfile;
|
||||
private $regExp;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->docroot = $GLOBALS['docroot'] ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
|
||||
$this->var = (array)@parse_ini_file('/var/local/emhttp/var.ini');
|
||||
$this->guid = @$this->var['regGUID'] ?? null;
|
||||
|
||||
$keyfileBase64 = empty($this->var['regFILE']) ? null : @file_get_contents($this->var['regFILE']);
|
||||
if ($keyfileBase64 !== false) {
|
||||
$keyfileBase64 = @base64_encode($keyfileBase64);
|
||||
$this->keyfile = str_replace(['+', '/', '='], ['-', '_', ''], trim($keyfileBase64));
|
||||
}
|
||||
|
||||
$this->regExp = @$this->var['regExp'] ?? null;
|
||||
}
|
||||
|
||||
private function request($url, $method, $payload = null, $headers = null)
|
||||
{
|
||||
$ch = curl_init($url);
|
||||
|
||||
// Set the request method
|
||||
curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method);
|
||||
// store the response in a variable instead of printing it
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
|
||||
// Set the payload if present
|
||||
if ($payload !== null) {
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $payload);
|
||||
}
|
||||
|
||||
if ($headers !== null) {
|
||||
// Set the headers
|
||||
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
|
||||
}
|
||||
|
||||
// Set additional options as needed
|
||||
|
||||
// Execute the request
|
||||
$response = curl_exec($ch);
|
||||
|
||||
// Check for errors
|
||||
if (curl_errno($ch)) {
|
||||
$error = [
|
||||
'heading' => 'CurlError',
|
||||
'message' => curl_error($ch),
|
||||
'level' => 'error',
|
||||
'ref' => 'curlError',
|
||||
'type' => 'request',
|
||||
];
|
||||
// @todo store error
|
||||
}
|
||||
|
||||
// Close the cURL session
|
||||
curl_close($ch);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
||||
private function validateGuid()
|
||||
{
|
||||
$headers = [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
];
|
||||
|
||||
$params = [
|
||||
'guid' => $this->guid,
|
||||
'keyfile' => $this->keyfile,
|
||||
];
|
||||
|
||||
/**
|
||||
* returns {JSON}
|
||||
* hasNewerKeyfile : boolean;
|
||||
* purchaseable: true;
|
||||
* registered: false;
|
||||
* replaceable: false;
|
||||
* upgradeable: false;
|
||||
* upgradeAllowed: string[];
|
||||
* updatesRenewable: false;
|
||||
*/
|
||||
$response = $this->request(
|
||||
self::KEY_SERVER_URL . '/validate/guid',
|
||||
'POST',
|
||||
http_build_query($params),
|
||||
$headers,
|
||||
);
|
||||
|
||||
// Handle the response as needed (parsing JSON, etc.)
|
||||
$decodedResponse = json_decode($response, true);
|
||||
|
||||
if (!empty($decodedResponse)) {
|
||||
return $decodedResponse;
|
||||
}
|
||||
|
||||
// @todo save error response somewhere
|
||||
return [];
|
||||
}
|
||||
|
||||
private function getLatestKey()
|
||||
{
|
||||
$headers = [
|
||||
'Content-Type: application/x-www-form-urlencoded',
|
||||
];
|
||||
|
||||
$params = [
|
||||
'keyfile' => $this->keyfile,
|
||||
];
|
||||
|
||||
/**
|
||||
* returns {JSON}
|
||||
* license: string;
|
||||
*/
|
||||
$response = $this->request(
|
||||
self::KEY_SERVER_URL . '/key/latest',
|
||||
'POST',
|
||||
http_build_query($params),
|
||||
$headers,
|
||||
);
|
||||
|
||||
// Handle the response as needed (parsing JSON, etc.)
|
||||
$decodedResponse = json_decode($response, true);
|
||||
|
||||
if (!empty($decodedResponse) && !empty($decodedResponse['license'])) {
|
||||
return $decodedResponse['license'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private function installNewKey($key)
|
||||
{
|
||||
require_once "$this->docroot/webGui/include/InstallKey.php";
|
||||
|
||||
$KeyInstaller = new KeyInstaller();
|
||||
$installResponse = $KeyInstaller->installKey($key);
|
||||
|
||||
$installSuccess = false;
|
||||
|
||||
if (!empty($installResponse)) {
|
||||
$decodedResponse = json_decode($installResponse, true);
|
||||
if (isset($decodedResponse['error'])) {
|
||||
$this->writeJsonFile(
|
||||
'/tmp/ReplaceKey/error.json',
|
||||
[
|
||||
'error' => $decodedResponse['error'],
|
||||
'ts' => time(),
|
||||
]
|
||||
);
|
||||
$installSuccess = false;
|
||||
} else {
|
||||
$installSuccess = true;
|
||||
}
|
||||
}
|
||||
|
||||
$keyType = basename($key, '.key');
|
||||
$output = isset($GLOBALS['notify']) ? _var($GLOBALS['notify'],'plugin') : '';
|
||||
$script = '/usr/local/emhttp/webGui/scripts/notify';
|
||||
|
||||
if ($installSuccess) {
|
||||
$event = "Installed New $keyType License";
|
||||
$subject = "Your new $keyType license key has been automatically installed";
|
||||
$description = "";
|
||||
$importance = "normal $output";
|
||||
} else {
|
||||
$event = "Failed to Install New $keyType License";
|
||||
$subject = "Failed to automatically install your new $keyType license key";
|
||||
$description = isset($decodedResponse['error']) ? $decodedResponse['error'] : "Unknown error occurred";
|
||||
$importance = "alert $output";
|
||||
}
|
||||
|
||||
exec("$script -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg($importance)." -l '/Tools/Registration' -x");
|
||||
|
||||
return $installSuccess;
|
||||
}
|
||||
|
||||
private function writeJsonFile($file, $data)
|
||||
{
|
||||
if (!is_dir(dirname($file))) {
|
||||
mkdir(dirname($file));
|
||||
}
|
||||
|
||||
file_put_contents($file, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES));
|
||||
}
|
||||
|
||||
public function check(bool $forceCheck = false)
|
||||
{
|
||||
// we don't need to check
|
||||
if (empty($this->guid) || empty($this->keyfile) || empty($this->regExp)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if we're within the 7-day window before and after regExp
|
||||
$now = time();
|
||||
$sevenDaysBefore = strtotime('-7 days', $this->regExp);
|
||||
$sevenDaysAfter = strtotime('+7 days', $this->regExp);
|
||||
|
||||
$isWithinWindow = ($now >= $sevenDaysBefore && $now <= $sevenDaysAfter);
|
||||
|
||||
if (!$forceCheck && !$isWithinWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
// see if we have a new key
|
||||
$validateGuidResponse = $this->validateGuid();
|
||||
|
||||
$hasNewerKeyfile = @$validateGuidResponse['hasNewerKeyfile'] ?? false;
|
||||
if (!$hasNewerKeyfile) {
|
||||
return; // if there is no newer keyfile, we don't need to do anything
|
||||
}
|
||||
|
||||
$latestKey = $this->getLatestKey();
|
||||
if (!$latestKey) {
|
||||
// we supposedly have a new key, but didn't get it back…
|
||||
$this->writeJsonFile(
|
||||
'/tmp/ReplaceKey/error.json',
|
||||
[
|
||||
'error' => 'Failed to retrieve latest key after getting a `hasNewerKeyfile` in the validation response.',
|
||||
'ts' => time(),
|
||||
]
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
$this->installNewKey($latestKey);
|
||||
}
|
||||
}
|
||||
6664
pnpm-lock.yaml
generated
6664
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -4,3 +4,4 @@ packages:
|
||||
- "./plugin"
|
||||
- "./unraid-ui"
|
||||
- "./web"
|
||||
- "./packages/*"
|
||||
|
||||
13
readme.md
13
readme.md
@@ -130,7 +130,8 @@ Once you have your key pair, add your public SSH key to your Unraid server:
|
||||
|
||||
Navigate to Plugins->Install and install the local plugin file that is output to the console.
|
||||
|
||||
## View other workflows (local dev, etc.) in the [Developer Workflows](./api/docs/developer/workflows.md)
|
||||
> [!TIP]
|
||||
> View other workflows (local dev, etc.) in the [Developer Workflows](./api/docs/developer/workflows.md)
|
||||
|
||||
<p align="right">(<a href="#readme-top">back to top</a>)</p>
|
||||
|
||||
@@ -158,6 +159,16 @@ See the [open issues](https://github.com/unraid/api/issues) for a full list of p
|
||||
<!-- CONTRIBUTING -->
|
||||
## Contributing
|
||||
|
||||
For a complete guide on contributing to the project, including our code of conduct and development process, please see our [Contributing Guide](./CONTRIBUTING.md). Please read this before contributing.
|
||||
|
||||
### Developer Documentation
|
||||
|
||||
For more information about development workflows, repository organization, and other technical details, please refer to the developer documentation inside this repository:
|
||||
|
||||
* [Development Guide](./api/docs/developer/development.md) - Setup, building, and debugging instructions
|
||||
* [Development Workflows](./api/docs/developer/workflows.md) - Detailed workflows for local development, building, and deployment
|
||||
* [Repository Organization](./api/docs/developer/repo-organization.md) - High-level architecture and project structure
|
||||
|
||||
### Work Intent Process
|
||||
|
||||
Before starting development work on this project, you must submit a Work Intent and have it approved by a core developer. This helps prevent duplicate work and ensures changes align with the project's goals.
|
||||
|
||||
@@ -14,6 +14,18 @@ const preview: Preview = {
|
||||
},
|
||||
},
|
||||
},
|
||||
// Add decorator to include modals container in every story
|
||||
decorators: [
|
||||
(story) => ({
|
||||
components: { story },
|
||||
template: `
|
||||
<div>
|
||||
<div id="modals"></div>
|
||||
<story />
|
||||
</div>
|
||||
`,
|
||||
}),
|
||||
],
|
||||
};
|
||||
|
||||
export default preview;
|
||||
|
||||
@@ -2,17 +2,18 @@
|
||||
"$schema": "https://shadcn-vue.com/schema.json",
|
||||
"style": "default",
|
||||
"typescript": true,
|
||||
"tsConfigPath": "./tsconfig.json",
|
||||
"tailwind": {
|
||||
"config": "./tailwind.config.ts",
|
||||
"css": "./src/styles/globals.css",
|
||||
"config": "tailwind.config.ts",
|
||||
"css": "src/styles/globals.css",
|
||||
"baseColor": "neutral",
|
||||
"cssVariables": true,
|
||||
"prefix": ""
|
||||
},
|
||||
"framework": "vite",
|
||||
"aliases": {
|
||||
"components": "@/components/common",
|
||||
"utils": "@/lib/utils"
|
||||
}
|
||||
"composables": "@/composables",
|
||||
"utils": "@/lib/utils",
|
||||
"lib": "@/lib"
|
||||
},
|
||||
"iconLibrary": "lucide"
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/ui",
|
||||
"version": "4.2.0",
|
||||
"version": "4.4.1",
|
||||
"private": true,
|
||||
"license": "GPL-2.0-only",
|
||||
"type": "module",
|
||||
@@ -44,14 +44,13 @@
|
||||
"@jsonforms/core": "^3.5.1",
|
||||
"@jsonforms/vue": "^3.5.1",
|
||||
"@jsonforms/vue-vanilla": "^3.5.1",
|
||||
"@vueuse/core": "^12.0.0",
|
||||
"@vueuse/core": "^13.0.0",
|
||||
"class-variance-authority": "^0.7.1",
|
||||
"clsx": "^2.1.1",
|
||||
"kebab-case": "^2.0.1",
|
||||
"lucide-vue-next": "^0.475.0",
|
||||
"radix-vue": "^1.9.13",
|
||||
"reka-ui": "^2.0.2",
|
||||
"shadcn-vue": "^0.11.3",
|
||||
"lucide-vue-next": "^0.483.0",
|
||||
"reka-ui": "^2.1.0",
|
||||
"shadcn-vue": "^1.0.0",
|
||||
"tailwind-merge": "^2.6.0",
|
||||
"vue-sonner": "^1.3.0"
|
||||
},
|
||||
@@ -77,10 +76,10 @@
|
||||
"autoprefixer": "^10.4.20",
|
||||
"eslint": "^9.17.0",
|
||||
"eslint-config-prettier": "^10.0.0",
|
||||
"eslint-plugin-vue": "^9.32.0",
|
||||
"eslint-plugin-vue": "^10.0.0",
|
||||
"happy-dom": "^17.0.0",
|
||||
"postcss": "^8.4.49",
|
||||
"prettier": "3.5.1",
|
||||
"prettier": "3.5.3",
|
||||
"prettier-plugin-tailwindcss": "^0.6.11",
|
||||
"storybook": "^8.5.8",
|
||||
"tailwind-rem-to-rem": "github:unraid/tailwind-rem-to-rem",
|
||||
@@ -119,5 +118,5 @@
|
||||
"import": "./dist/theme/preset.js"
|
||||
}
|
||||
},
|
||||
"packageManager": "pnpm@10.4.1"
|
||||
"packageManager": "pnpm@10.6.5"
|
||||
}
|
||||
|
||||
@@ -1,11 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import { computed } from 'vue';
|
||||
import { brandLoadingVariants, markAnimations } from './brand-loading.variants';
|
||||
import {
|
||||
brandLoadingVariants,
|
||||
markAnimations,
|
||||
type BrandLoadingVariants,
|
||||
} from './brand-loading.variants';
|
||||
|
||||
export interface Props {
|
||||
variant?: 'default' | 'black' | 'white';
|
||||
size?: 'sm' | 'md' | 'lg' | 'full';
|
||||
variant?: BrandLoadingVariants['variant'];
|
||||
size?: BrandLoadingVariants['size'];
|
||||
class?: string;
|
||||
title?: string;
|
||||
}
|
||||
@@ -22,7 +26,7 @@ const GRADIENT_COLORS = {
|
||||
default: { start: '#e32929', stop: '#ff8d30' },
|
||||
} as const;
|
||||
|
||||
const gradientColors = computed(() => GRADIENT_COLORS[props.variant]);
|
||||
const gradientColors = computed(() => GRADIENT_COLORS[props.variant as keyof typeof GRADIENT_COLORS]);
|
||||
|
||||
const classes = computed(() => {
|
||||
return cn(brandLoadingVariants({ variant: props.variant, size: props.size }), props.class);
|
||||
|
||||
@@ -1,23 +1,27 @@
|
||||
import { cva } from 'class-variance-authority';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
|
||||
export const brandLoadingVariants = cva('inline-flex items-center justify-center w-full h-full aspect-[7/4]', {
|
||||
variants: {
|
||||
variant: {
|
||||
default: '',
|
||||
black: 'text-black fill-black',
|
||||
white: 'text-white fill-white',
|
||||
export const brandLoadingVariants = cva(
|
||||
'inline-flex items-center justify-center w-full h-full aspect-[7/4]',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: '',
|
||||
black: 'text-black fill-black',
|
||||
white: 'text-white fill-white',
|
||||
},
|
||||
size: {
|
||||
sm: 'w-12',
|
||||
md: 'w-16',
|
||||
lg: 'w-20',
|
||||
full: 'w-full',
|
||||
custom: '',
|
||||
},
|
||||
},
|
||||
size: {
|
||||
sm: 'w-12',
|
||||
md: 'w-16',
|
||||
lg: 'w-20',
|
||||
full: 'w-full',
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
},
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
export const markAnimations = {
|
||||
mark_2_4: 'animate-mark-2',
|
||||
@@ -25,3 +29,5 @@ export const markAnimations = {
|
||||
mark_6_8: 'animate-mark-6',
|
||||
mark_7: 'animate-mark-7',
|
||||
};
|
||||
|
||||
export type BrandLoadingVariants = VariantProps<typeof brandLoadingVariants>;
|
||||
|
||||
@@ -1,25 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue';
|
||||
import type { Component } from 'vue';
|
||||
import { badgeVariants } from './badge.variants';
|
||||
import { badgeVariants, type BadgeVariants } from './badge.variants';
|
||||
|
||||
export interface BadgeProps {
|
||||
variant?:
|
||||
| 'red'
|
||||
| 'yellow'
|
||||
| 'green'
|
||||
| 'blue'
|
||||
| 'indigo'
|
||||
| 'purple'
|
||||
| 'pink'
|
||||
| 'orange'
|
||||
| 'black'
|
||||
| 'white'
|
||||
| 'transparent'
|
||||
| 'current'
|
||||
| 'gray'
|
||||
| 'custom';
|
||||
size?: 'xs' | 'sm' | 'md' | 'lg' | 'xl' | '2xl';
|
||||
variant?: BadgeVariants['variant'];
|
||||
size?: BadgeVariants['size'];
|
||||
icon?: Component;
|
||||
iconRight?: Component;
|
||||
iconStyles?: string;
|
||||
|
||||
@@ -1,37 +1,39 @@
|
||||
import { cva } from "class-variance-authority";
|
||||
import { cva, VariantProps } from 'class-variance-authority';
|
||||
|
||||
export const badgeVariants = cva(
|
||||
"inline-flex items-center rounded-full font-semibold leading-none transition-all duration-200 ease-in-out unraid-ui-badge-test",
|
||||
'inline-flex items-center rounded-full font-semibold leading-none transition-all duration-200 ease-in-out unraid-ui-badge-test',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
red: "bg-unraid-red text-white hover:bg-orange-dark",
|
||||
yellow: "bg-yellow-100 text-black hover:bg-yellow-200",
|
||||
green: "bg-green-200 text-green-800 hover:bg-green-300",
|
||||
blue: "bg-blue-100 text-blue-800 hover:bg-blue-200",
|
||||
indigo: "bg-indigo-100 text-indigo-800 hover:bg-indigo-200",
|
||||
purple: "bg-purple-100 text-purple-800 hover:bg-purple-200",
|
||||
pink: "bg-pink-100 text-pink-800 hover:bg-pink-200",
|
||||
orange: "bg-orange text-white hover:bg-orange-dark",
|
||||
black: "bg-black text-white hover:bg-gray-800",
|
||||
white: "bg-white text-black hover:bg-gray-100",
|
||||
transparent: "bg-transparent text-black hover:bg-gray-100",
|
||||
current: "bg-current text-current hover:bg-gray-100",
|
||||
gray: "bg-gray-200 text-gray-800 hover:bg-gray-300",
|
||||
custom: "",
|
||||
red: 'bg-unraid-red text-white hover:bg-orange-dark',
|
||||
yellow: 'bg-yellow-100 text-black hover:bg-yellow-200',
|
||||
green: 'bg-green-200 text-green-800 hover:bg-green-300',
|
||||
blue: 'bg-blue-100 text-blue-800 hover:bg-blue-200',
|
||||
indigo: 'bg-indigo-100 text-indigo-800 hover:bg-indigo-200',
|
||||
purple: 'bg-purple-100 text-purple-800 hover:bg-purple-200',
|
||||
pink: 'bg-pink-100 text-pink-800 hover:bg-pink-200',
|
||||
orange: 'bg-orange text-white hover:bg-orange-dark',
|
||||
black: 'bg-black text-white hover:bg-gray-800',
|
||||
white: 'bg-white text-black hover:bg-gray-100',
|
||||
transparent: 'bg-transparent text-black hover:bg-gray-100',
|
||||
current: 'bg-current text-current hover:bg-gray-100',
|
||||
gray: 'bg-gray-200 text-gray-800 hover:bg-gray-300',
|
||||
custom: '',
|
||||
},
|
||||
size: {
|
||||
xs: "text-12px px-8px py-4px gap-4px",
|
||||
sm: "text-14px px-8px py-4px gap-8px",
|
||||
md: "text-16px px-12px py-8px gap-8px",
|
||||
lg: "text-18px px-12px py-8px gap-8px",
|
||||
xl: "text-20px px-16px py-12px gap-8px",
|
||||
"2xl": "text-24px px-16px py-12px gap-8px",
|
||||
xs: 'text-12px px-8px py-4px gap-4px',
|
||||
sm: 'text-14px px-8px py-4px gap-8px',
|
||||
md: 'text-16px px-12px py-8px gap-8px',
|
||||
lg: 'text-18px px-12px py-8px gap-8px',
|
||||
xl: 'text-20px px-16px py-12px gap-8px',
|
||||
'2xl': 'text-24px px-16px py-12px gap-8px',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "gray",
|
||||
size: "md",
|
||||
variant: 'gray',
|
||||
size: 'md',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export type BadgeVariants = VariantProps<typeof badgeVariants>;
|
||||
|
||||
@@ -1,30 +1,21 @@
|
||||
<script setup lang="ts">
|
||||
import { computed } from "vue";
|
||||
import { buttonVariants } from "./button.variants";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { computed } from 'vue';
|
||||
import { buttonVariants, type ButtonVariants } from './button.variants';
|
||||
|
||||
export interface ButtonProps {
|
||||
variant?:
|
||||
| "primary"
|
||||
| "destructive"
|
||||
| "outline"
|
||||
| "secondary"
|
||||
| "ghost"
|
||||
| "link";
|
||||
size?: "sm" | "md" | "lg" | "icon";
|
||||
variant?: ButtonVariants['variant'];
|
||||
size?: ButtonVariants['size'];
|
||||
class?: string;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||
variant: "primary",
|
||||
size: "md",
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
});
|
||||
|
||||
const buttonClass = computed(() => {
|
||||
return cn(
|
||||
buttonVariants({ variant: props.variant, size: props.size }),
|
||||
props.class
|
||||
);
|
||||
return cn(buttonVariants({ variant: props.variant, size: props.size }), props.class);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,30 +1,29 @@
|
||||
import { cva } from "class-variance-authority";
|
||||
import { cva, VariantProps } from 'class-variance-authority';
|
||||
|
||||
export const buttonVariants = cva(
|
||||
"inline-flex items-center justify-center rounded-md text-base font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
|
||||
'inline-flex items-center justify-center rounded-md text-base font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
primary: "bg-primary text-primary-foreground hover:bg-primary/90",
|
||||
destructive:
|
||||
"bg-destructive text-destructive-foreground hover:bg-destructive/90",
|
||||
outline:
|
||||
"border border-input bg-background hover:bg-accent hover:text-accent-foreground",
|
||||
secondary:
|
||||
"bg-secondary text-secondary-foreground hover:bg-secondary/80",
|
||||
ghost: "hover:bg-accent hover:text-accent-foreground",
|
||||
link: "text-primary underline-offset-4 hover:underline",
|
||||
primary: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive: 'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline: 'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary: 'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
sm: "h-9 rounded-md px-3",
|
||||
md: "h-10 px-4 py-2",
|
||||
lg: "h-11 rounded-md px-8",
|
||||
icon: "h-10 w-10",
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
md: 'h-10 px-4 py-2',
|
||||
lg: 'h-11 rounded-md px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "primary",
|
||||
size: "md",
|
||||
variant: 'primary',
|
||||
size: 'md',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export type ButtonVariants = VariantProps<typeof buttonVariants>;
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DropdownMenuRoot,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuRootEmits,
|
||||
type DropdownMenuRootProps,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuRootProps>();
|
||||
const emits = defineEmits<DropdownMenuRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
const forwarded = useForwardPropsEmits<DropdownMenuRootProps, 'update:open'>(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
<script setup lang="ts">
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
DropdownMenuArrow as RekaDropdownMenuArrow,
|
||||
useForwardProps,
|
||||
type DropdownMenuArrowProps,
|
||||
} from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<DropdownMenuArrowProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwardedProps = useForwardProps(delegatedProps);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<RekaDropdownMenuArrow
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'fill-popover data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,18 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Check } from 'lucide-vue-next';
|
||||
import {
|
||||
DropdownMenuCheckboxItem,
|
||||
type DropdownMenuCheckboxItemEmits,
|
||||
type DropdownMenuCheckboxItemProps,
|
||||
DropdownMenuItemIndicator,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
import { Check } from "lucide-vue-next";
|
||||
import { cn } from "@/lib/utils";
|
||||
type DropdownMenuCheckboxItemEmits,
|
||||
type DropdownMenuCheckboxItemProps,
|
||||
} from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuCheckboxItemProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
const props = defineProps<DropdownMenuCheckboxItemProps & { class?: HTMLAttributes['class'] }>();
|
||||
const emits = defineEmits<DropdownMenuCheckboxItemEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -1,26 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import useTeleport from '@/composables/useTeleport';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
DropdownMenuContent,
|
||||
type DropdownMenuContentEmits,
|
||||
type DropdownMenuContentProps,
|
||||
DropdownMenuPortal,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
type DropdownMenuContentEmits,
|
||||
type DropdownMenuContentProps,
|
||||
} from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const { teleportTarget } = useTeleport();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes["class"] }>(),
|
||||
defineProps<DropdownMenuContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
sideOffset: 4,
|
||||
class: undefined,
|
||||
}
|
||||
);
|
||||
const emits = defineEmits<DropdownMenuContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated;
|
||||
});
|
||||
|
||||
@@ -28,17 +29,20 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuPortal>
|
||||
<DropdownMenuPortal :to="teleportTarget">
|
||||
<DropdownMenuContent
|
||||
v-bind="forwarded"
|
||||
side="bottom"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 min-w-32 overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'z-50 min-w-32 rounded-lg bg-popover p-1 text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
<div class="overflow-hidden">
|
||||
<slot />
|
||||
</div>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenuPortal>
|
||||
</template>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuGroup, type DropdownMenuGroupProps } from "radix-vue";
|
||||
import { DropdownMenuGroup, type DropdownMenuGroupProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuGroupProps>();
|
||||
</script>
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import {
|
||||
DropdownMenuItem,
|
||||
type DropdownMenuItemProps,
|
||||
useForwardProps,
|
||||
} from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DropdownMenuItem, useForwardProps, type DropdownMenuItemProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuItemProps & { class?: HTMLAttributes["class"]; inset?: boolean }
|
||||
DropdownMenuItemProps & { class?: HTMLAttributes['class']; inset?: boolean }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
@@ -25,7 +21,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn(
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'relative flex cursor-default select-none items-center rounded-sm gap-2 px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&>svg]:size-4 [&>svg]:shrink-0',
|
||||
inset && 'pl-8',
|
||||
props.class
|
||||
)
|
||||
|
||||
@@ -1,14 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import {
|
||||
DropdownMenuLabel,
|
||||
type DropdownMenuLabelProps,
|
||||
useForwardProps,
|
||||
} from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DropdownMenuLabel, useForwardProps, type DropdownMenuLabelProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuLabelProps & { class?: HTMLAttributes["class"]; inset?: boolean }
|
||||
DropdownMenuLabelProps & { class?: HTMLAttributes['class']; inset?: boolean }
|
||||
>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
@@ -23,9 +19,7 @@ const forwardedProps = useForwardProps(delegatedProps);
|
||||
<template>
|
||||
<DropdownMenuLabel
|
||||
v-bind="forwardedProps"
|
||||
:class="
|
||||
cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)
|
||||
"
|
||||
:class="cn('px-2 py-1.5 text-sm font-semibold', inset && 'pl-8', props.class)"
|
||||
>
|
||||
<slot />
|
||||
</DropdownMenuLabel>
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DropdownMenuRadioGroup,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuRadioGroupEmits,
|
||||
type DropdownMenuRadioGroupProps,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuRadioGroupProps>();
|
||||
const emits = defineEmits<DropdownMenuRadioGroupEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
const forwarded = useForwardPropsEmits<DropdownMenuRadioGroupProps, 'update:modelValue'>(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,18 +1,16 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { Circle } from 'lucide-vue-next';
|
||||
import {
|
||||
DropdownMenuItemIndicator,
|
||||
DropdownMenuRadioItem,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuRadioItemEmits,
|
||||
type DropdownMenuRadioItemProps,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
import { Circle } from "lucide-vue-next";
|
||||
import { cn } from "@/lib/utils";
|
||||
} from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuRadioItemProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
const props = defineProps<DropdownMenuRadioItemProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const emits = defineEmits<DropdownMenuRadioItemEmits>();
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import {
|
||||
DropdownMenuSeparator,
|
||||
type DropdownMenuSeparatorProps,
|
||||
} from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { DropdownMenuSeparator, type DropdownMenuSeparatorProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuSeparatorProps & {
|
||||
class?: HTMLAttributes["class"];
|
||||
class?: HTMLAttributes['class'];
|
||||
}
|
||||
>();
|
||||
|
||||
@@ -20,8 +17,5 @@ const delegatedProps = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<DropdownMenuSeparator
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('-mx-1 my-1 h-px bg-muted', props.class)"
|
||||
/>
|
||||
<DropdownMenuSeparator v-bind="delegatedProps" :class="cn('-mx-1 my-1 h-px bg-muted', props.class)" />
|
||||
</template>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import type { HTMLAttributes } from "vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import type { HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
class?: HTMLAttributes["class"];
|
||||
class?: HTMLAttributes['class'];
|
||||
}>();
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
DropdownMenuSub,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuSubEmits,
|
||||
type DropdownMenuSubProps,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
} from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuSubProps>();
|
||||
const emits = defineEmits<DropdownMenuSubEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits);
|
||||
const forwarded = useForwardPropsEmits<DropdownMenuSubProps, 'update:open'>(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,16 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
DropdownMenuSubContent,
|
||||
useForwardPropsEmits,
|
||||
type DropdownMenuSubContentEmits,
|
||||
type DropdownMenuSubContentProps,
|
||||
useForwardPropsEmits,
|
||||
} from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
} from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuSubContentProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
const props = defineProps<DropdownMenuSubContentProps & { class?: HTMLAttributes['class'] }>();
|
||||
const emits = defineEmits<DropdownMenuSubContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
|
||||
@@ -1,16 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import {
|
||||
DropdownMenuSubTrigger,
|
||||
type DropdownMenuSubTriggerProps,
|
||||
useForwardProps,
|
||||
} from "radix-vue";
|
||||
import { ChevronRight } from "lucide-vue-next";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ChevronRight } from 'lucide-vue-next';
|
||||
import { DropdownMenuSubTrigger, useForwardProps, type DropdownMenuSubTriggerProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = defineProps<
|
||||
DropdownMenuSubTriggerProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
const props = defineProps<DropdownMenuSubTriggerProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script setup lang="ts">
|
||||
import { DropdownMenuTrigger, type DropdownMenuTriggerProps, useForwardProps } from 'radix-vue'
|
||||
import { DropdownMenuTrigger, useForwardProps, type DropdownMenuTriggerProps } from 'reka-ui';
|
||||
|
||||
const props = defineProps<DropdownMenuTriggerProps>()
|
||||
const props = defineProps<DropdownMenuTriggerProps>();
|
||||
|
||||
const forwardedProps = useForwardProps(props)
|
||||
const forwardedProps = useForwardProps<DropdownMenuTriggerProps>(props);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,16 +1,17 @@
|
||||
export { DropdownMenuPortal } from 'radix-vue'
|
||||
export { default as DropdownMenu } from './DropdownMenu.vue';
|
||||
|
||||
export { default as DropdownMenu } from './DropdownMenu.vue'
|
||||
export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue'
|
||||
export { default as DropdownMenuContent } from './DropdownMenuContent.vue'
|
||||
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue'
|
||||
export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue'
|
||||
export { default as DropdownMenuItem } from './DropdownMenuItem.vue'
|
||||
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue'
|
||||
export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue'
|
||||
export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue'
|
||||
export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue'
|
||||
export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue'
|
||||
export { default as DropdownMenuSub } from './DropdownMenuSub.vue'
|
||||
export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue'
|
||||
export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue'
|
||||
export { default as DropdownMenuCheckboxItem } from './DropdownMenuCheckboxItem.vue';
|
||||
export { default as DropdownMenuContent } from './DropdownMenuContent.vue';
|
||||
export { default as DropdownMenuGroup } from './DropdownMenuGroup.vue';
|
||||
export { default as DropdownMenuItem } from './DropdownMenuItem.vue';
|
||||
export { default as DropdownMenuLabel } from './DropdownMenuLabel.vue';
|
||||
export { default as DropdownMenuRadioGroup } from './DropdownMenuRadioGroup.vue';
|
||||
export { default as DropdownMenuRadioItem } from './DropdownMenuRadioItem.vue';
|
||||
export { default as DropdownMenuSeparator } from './DropdownMenuSeparator.vue';
|
||||
export { default as DropdownMenuShortcut } from './DropdownMenuShortcut.vue';
|
||||
export { default as DropdownMenuSub } from './DropdownMenuSub.vue';
|
||||
export { default as DropdownMenuSubContent } from './DropdownMenuSubContent.vue';
|
||||
export { default as DropdownMenuSubTrigger } from './DropdownMenuSubTrigger.vue';
|
||||
export { default as DropdownMenuTrigger } from './DropdownMenuTrigger.vue';
|
||||
export { default as DropdownMenuArrow } from './DropdownMenuArrow.vue';
|
||||
export { DropdownMenuPortal } from 'reka-ui';
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverRootEmits, PopoverRootProps } from 'radix-vue'
|
||||
import { PopoverRoot, useForwardPropsEmits } from 'radix-vue'
|
||||
import type { PopoverRootEmits, PopoverRootProps } from 'reka-ui';
|
||||
import { PopoverRoot, useForwardPropsEmits } from 'reka-ui';
|
||||
|
||||
const props = defineProps<PopoverRootProps>()
|
||||
const emits = defineEmits<PopoverRootEmits>()
|
||||
const props = defineProps<PopoverRootProps>();
|
||||
const emits = defineEmits<PopoverRootEmits>();
|
||||
|
||||
const forwarded = useForwardPropsEmits(props, emits)
|
||||
const forwarded = useForwardPropsEmits<PopoverRootProps, 'update:open'>(props, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
|
||||
@@ -1,35 +1,32 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverContentEmits, PopoverContentProps } from 'radix-vue'
|
||||
import type { HTMLAttributes } from 'vue'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
PopoverArrow,
|
||||
PopoverContent,
|
||||
|
||||
PopoverPortal,
|
||||
useForwardPropsEmits,
|
||||
} from 'radix-vue'
|
||||
import { computed } from 'vue'
|
||||
type PopoverContentEmits,
|
||||
type PopoverContentProps,
|
||||
} from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
defineOptions({
|
||||
inheritAttrs: false,
|
||||
})
|
||||
});
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<PopoverContentProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
align: 'center',
|
||||
sideOffset: 4,
|
||||
},
|
||||
)
|
||||
const emits = defineEmits<PopoverContentEmits>()
|
||||
const props = withDefaults(defineProps<PopoverContentProps & { class?: HTMLAttributes['class'] }>(), {
|
||||
align: 'center',
|
||||
sideOffset: 4,
|
||||
});
|
||||
const emits = defineEmits<PopoverContentEmits>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props
|
||||
const { class: _, ...delegated } = props;
|
||||
|
||||
return delegated
|
||||
})
|
||||
return delegated;
|
||||
});
|
||||
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
const forwarded = useForwardPropsEmits(delegatedProps, emits);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -38,12 +35,13 @@ const forwarded = useForwardPropsEmits(delegatedProps, emits)
|
||||
v-bind="{ ...forwarded, ...$attrs }"
|
||||
:class="
|
||||
cn(
|
||||
'z-50 w-72 rounded-md border bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
props.class,
|
||||
'z-50 w-72 rounded-md bg-popover p-4 text-popover-foreground shadow-md outline-none data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
>
|
||||
<slot />
|
||||
<PopoverArrow :rounded="true" class="fill-popover" />
|
||||
</PopoverContent>
|
||||
</PopoverPortal>
|
||||
</template>
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
<script setup lang="ts">
|
||||
import type { PopoverTriggerProps } from 'radix-vue'
|
||||
import { PopoverTrigger } from 'radix-vue'
|
||||
import { PopoverTrigger, type PopoverTriggerProps } from 'reka-ui'
|
||||
|
||||
const props = defineProps<PopoverTriggerProps>()
|
||||
</script>
|
||||
|
||||
@@ -1,17 +1,10 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import {
|
||||
ScrollAreaCorner,
|
||||
ScrollAreaRoot,
|
||||
type ScrollAreaRootProps,
|
||||
ScrollAreaViewport,
|
||||
} from "radix-vue";
|
||||
import ScrollBar from "./ScrollBar.vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ScrollAreaCorner, ScrollAreaRoot, ScrollAreaViewport, type ScrollAreaRootProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
import ScrollBar from './ScrollBar.vue';
|
||||
|
||||
const props = defineProps<
|
||||
ScrollAreaRootProps & { class?: HTMLAttributes["class"] }
|
||||
>();
|
||||
const props = defineProps<ScrollAreaRootProps & { class?: HTMLAttributes['class'] }>();
|
||||
|
||||
const delegatedProps = computed(() => {
|
||||
const { class: _, ...delegated } = props;
|
||||
@@ -21,10 +14,7 @@ const delegatedProps = computed(() => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ScrollAreaRoot
|
||||
v-bind="delegatedProps"
|
||||
:class="cn('relative overflow-hidden', props.class)"
|
||||
>
|
||||
<ScrollAreaRoot v-bind="delegatedProps" :class="cn('relative overflow-hidden', props.class)">
|
||||
<ScrollAreaViewport class="h-full w-full rounded-[inherit]">
|
||||
<slot />
|
||||
</ScrollAreaViewport>
|
||||
|
||||
@@ -1,16 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { type HTMLAttributes, computed } from "vue";
|
||||
import {
|
||||
ScrollAreaScrollbar,
|
||||
type ScrollAreaScrollbarProps,
|
||||
ScrollAreaThumb,
|
||||
} from "radix-vue";
|
||||
import { cn } from "@/lib/utils";
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ScrollAreaScrollbar, ScrollAreaThumb, type ScrollAreaScrollbarProps } from 'reka-ui';
|
||||
import { computed, type HTMLAttributes } from 'vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<ScrollAreaScrollbarProps & { class?: HTMLAttributes["class"] }>(),
|
||||
defineProps<ScrollAreaScrollbarProps & { class?: HTMLAttributes['class'] }>(),
|
||||
{
|
||||
orientation: "vertical",
|
||||
orientation: 'vertical',
|
||||
class: undefined,
|
||||
}
|
||||
);
|
||||
@@ -28,10 +24,8 @@ const delegatedProps = computed(() => {
|
||||
:class="
|
||||
cn(
|
||||
'flex touch-none select-none transition-colors',
|
||||
orientation === 'vertical' &&
|
||||
'h-full w-2.5 border-l border-l-transparent p-px',
|
||||
orientation === 'horizontal' &&
|
||||
'h-2.5 flex-col border-t border-t-transparent p-px',
|
||||
orientation === 'vertical' && 'h-full w-2.5 border-l border-l-transparent p-px',
|
||||
orientation === 'horizontal' && 'h-2.5 flex-col border-t border-t-transparent p-px',
|
||||
props.class
|
||||
)
|
||||
"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user