mirror of
https://github.com/unraid/api.git
synced 2026-01-02 14:40:01 -06:00
Compare commits
306 Commits
refactor/d
...
test/prime
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbbda5926b | ||
|
|
8a0a053a80 | ||
|
|
026d643b49 | ||
|
|
d2a34acfb9 | ||
|
|
3dc60b6106 | ||
|
|
57587b9175 | ||
|
|
5ee7cb2647 | ||
|
|
911a3f8f1a | ||
|
|
d426001372 | ||
|
|
2d0c65aaf4 | ||
|
|
fd4605b956 | ||
|
|
3f84b6bbfd | ||
|
|
5ad10af303 | ||
|
|
9aa11faaaa | ||
|
|
bfa98574f1 | ||
|
|
dd2dc40ff1 | ||
|
|
8a3265d7b1 | ||
|
|
a240a031a8 | ||
|
|
979e41fe41 | ||
|
|
03dc404aa7 | ||
|
|
364320ffc9 | ||
|
|
2492f4cec9 | ||
|
|
1a643b3eef | ||
|
|
58ee3b958b | ||
|
|
2928cf5821 | ||
|
|
b21b276151 | ||
|
|
d80f25dc96 | ||
|
|
f5f5a081e6 | ||
|
|
f60474b4d7 | ||
|
|
364373df0c | ||
|
|
bb38533bb2 | ||
|
|
836801c524 | ||
|
|
b54cf5ede9 | ||
|
|
1a20c66c02 | ||
|
|
587bbb3b4d | ||
|
|
e95f7a1a03 | ||
|
|
9c75f6e2ca | ||
|
|
822042ab9c | ||
|
|
3a843b6e16 | ||
|
|
6072387c37 | ||
|
|
313162dbf2 | ||
|
|
495515abac | ||
|
|
09087040e9 | ||
|
|
4423829911 | ||
|
|
c8f469c4fb | ||
|
|
bc61b45f9f | ||
|
|
f530d9ea82 | ||
|
|
2046fa5310 | ||
|
|
9ea2327fa0 | ||
|
|
ff67b54a1b | ||
|
|
e6bd7a54be | ||
|
|
5827b5ffa3 | ||
|
|
572a1310e0 | ||
|
|
c1403d3826 | ||
|
|
29afe9b9e8 | ||
|
|
e9ff33d263 | ||
|
|
a62f60a436 | ||
|
|
838964c6ef | ||
|
|
800fc12c15 | ||
|
|
80175241e3 | ||
|
|
5d801f22f5 | ||
|
|
ba772add54 | ||
|
|
ff24f12cae | ||
|
|
487f5c1865 | ||
|
|
e0c90037fb | ||
|
|
aa5f603cba | ||
|
|
409db43973 | ||
|
|
cef1b29355 | ||
|
|
045750c87e | ||
|
|
85802e7af7 | ||
|
|
4bfdb66d46 | ||
|
|
81a6a52d9f | ||
|
|
7759fe1dc3 | ||
|
|
3b2acb29b5 | ||
|
|
5f2b949ecf | ||
|
|
1b956d563e | ||
|
|
c6a97f5082 | ||
|
|
7f512e47e9 | ||
|
|
5d725b0e76 | ||
|
|
fe63607260 | ||
|
|
0a1d4daf6e | ||
|
|
9e9e385bef | ||
|
|
6fed39e05b | ||
|
|
3dec53d13d | ||
|
|
f0ded9f5be | ||
|
|
7d55a1c2cd | ||
|
|
f3dc9663b8 | ||
|
|
05c7c481a9 | ||
|
|
adcc1543f0 | ||
|
|
95f873c752 | ||
|
|
ec90f8b295 | ||
|
|
f84195a98d | ||
|
|
5e98a68e2e | ||
|
|
b91dbca144 | ||
|
|
79a01da18d | ||
|
|
14951d3004 | ||
|
|
64c2061bea | ||
|
|
e3adc9a29a | ||
|
|
6b689ffcce | ||
|
|
c995a4c5c8 | ||
|
|
8d1e0f67d1 | ||
|
|
7877a5dca2 | ||
|
|
16db278ffd | ||
|
|
521b4381f2 | ||
|
|
9ae9d40f94 | ||
|
|
1d562d404c | ||
|
|
7ac1b268d9 | ||
|
|
4833e9dccf | ||
|
|
f28b7510fa | ||
|
|
37b717b142 | ||
|
|
fd8b40d9aa | ||
|
|
1d944781cf | ||
|
|
1f4c64d022 | ||
|
|
f69b5130a3 | ||
|
|
f8b143904b | ||
|
|
31a5413643 | ||
|
|
a95fc5ed07 | ||
|
|
fcd7bb790e | ||
|
|
008e10948e | ||
|
|
c97a4f1268 | ||
|
|
3eba95b8cc | ||
|
|
2bf8f0b937 | ||
|
|
9ae45d1258 | ||
|
|
1835a4cf3f | ||
|
|
2ab44b894d | ||
|
|
1108f49b07 | ||
|
|
cc69213beb | ||
|
|
460e557dd8 | ||
|
|
05e29468d2 | ||
|
|
4d3a311fb4 | ||
|
|
bc62d210ec | ||
|
|
43d3ea6553 | ||
|
|
882e3e1ef4 | ||
|
|
b33c86c99c | ||
|
|
cd0248e4c9 | ||
|
|
ecb3ed5003 | ||
|
|
0569339a41 | ||
|
|
3e9faead43 | ||
|
|
6e700b2385 | ||
|
|
464fc4993c | ||
|
|
4316c72809 | ||
|
|
ce0cebe09c | ||
|
|
23b90a0d56 | ||
|
|
3f8b3536b5 | ||
|
|
0dcf785b45 | ||
|
|
8cf4aff622 | ||
|
|
cefda7c42b | ||
|
|
0393b2382c | ||
|
|
23e900f7fd | ||
|
|
2d6aafc257 | ||
|
|
b191efece1 | ||
|
|
2a7f0043f5 | ||
|
|
607c7e3704 | ||
|
|
c246a443c5 | ||
|
|
fd495e1f5c | ||
|
|
621a06cafa | ||
|
|
f890b05151 | ||
|
|
567d8fdd6d | ||
|
|
7d996906ad | ||
|
|
b5ec076279 | ||
|
|
de8dfe3dba | ||
|
|
7249956d40 | ||
|
|
e6eb56466e | ||
|
|
8954700bcb | ||
|
|
eb595cea9e | ||
|
|
9a1a0a54e6 | ||
|
|
134396b602 | ||
|
|
2aa65fdb68 | ||
|
|
a9c4d7d5dd | ||
|
|
2cbbd5ee40 | ||
|
|
c84c55761c | ||
|
|
77eed36990 | ||
|
|
5c2d84d8b4 | ||
|
|
9883f0f82f | ||
|
|
e62b05b6f6 | ||
|
|
8e6ee8b770 | ||
|
|
666b51a28a | ||
|
|
1962097a66 | ||
|
|
7f010854b5 | ||
|
|
17288a4c02 | ||
|
|
ea48def9fc | ||
|
|
a1d5c29ffb | ||
|
|
bf99eb25c8 | ||
|
|
b35a440792 | ||
|
|
58f9eec8b1 | ||
|
|
26841aa10d | ||
|
|
e18a8d670e | ||
|
|
49d077db97 | ||
|
|
9dafe165b0 | ||
|
|
cce1953cb8 | ||
|
|
7e33b25593 | ||
|
|
78fb49a6fc | ||
|
|
f1e0d93bc5 | ||
|
|
195a178d15 | ||
|
|
b9257fce28 | ||
|
|
41eaf4ef1b | ||
|
|
93d0c08955 | ||
|
|
c5bc3454ff | ||
|
|
c33b4ef709 | ||
|
|
ce3ba7d070 | ||
|
|
639eb08291 | ||
|
|
6d109b4c4c | ||
|
|
6f3971dc47 | ||
|
|
2ccb503dc8 | ||
|
|
3cb9fdf102 | ||
|
|
40d81a4081 | ||
|
|
5a85f55be8 | ||
|
|
5455e211bc | ||
|
|
cb4cc989c7 | ||
|
|
037aa479bf | ||
|
|
08567f287a | ||
|
|
a57f1d890d | ||
|
|
3ab406e012 | ||
|
|
f36f4702a2 | ||
|
|
62697f7972 | ||
|
|
ec8d2bc0e8 | ||
|
|
d83664b6a3 | ||
|
|
6910a020d2 | ||
|
|
60e5c6e3e8 | ||
|
|
90b1432875 | ||
|
|
f1059aa381 | ||
|
|
01b4937f35 | ||
|
|
3e051815c5 | ||
|
|
e976daf8b0 | ||
|
|
422046dc03 | ||
|
|
9a270971d1 | ||
|
|
0742382ae1 | ||
|
|
763c38430e | ||
|
|
4d926bba8e | ||
|
|
4acc4ea9a9 | ||
|
|
565bf47818 | ||
|
|
176a0f30be | ||
|
|
6f4d983d89 | ||
|
|
6a0e258cf2 | ||
|
|
8d82064888 | ||
|
|
4300179b67 | ||
|
|
7e31ae2ebf | ||
|
|
7a27560b0d | ||
|
|
93655fef62 | ||
|
|
a581a95cb4 | ||
|
|
261fdda47c | ||
|
|
7a2a243a21 | ||
|
|
bead4256af | ||
|
|
e8dfd7e3b3 | ||
|
|
e456b7fcac | ||
|
|
fbe5e417ef | ||
|
|
5f80053a33 | ||
|
|
fa520a2d3e | ||
|
|
cf54f01945 | ||
|
|
44d2d58f12 | ||
|
|
daba2a352f | ||
|
|
d1ff2b1fad | ||
|
|
b1bd71f2e2 | ||
|
|
7f49816275 | ||
|
|
d73d460e88 | ||
|
|
ab1e852b6c | ||
|
|
117b7430db | ||
|
|
2e73f9e37a | ||
|
|
d3158983b4 | ||
|
|
dae7baa6ad | ||
|
|
e29f5e1adf | ||
|
|
e8d15c7dbb | ||
|
|
58be009da4 | ||
|
|
d4eb0ce3f2 | ||
|
|
d73324a141 | ||
|
|
7061be60f4 | ||
|
|
2a65f64ac1 | ||
|
|
120ba3e447 | ||
|
|
a527c7183a | ||
|
|
a0dfbb4e15 | ||
|
|
764f65ff61 | ||
|
|
1615e8623c | ||
|
|
d7bb9ff073 | ||
|
|
d896581e12 | ||
|
|
f833fa1fab | ||
|
|
2823517b26 | ||
|
|
3e0a8d0070 | ||
|
|
b3768d65aa | ||
|
|
d2e17c0051 | ||
|
|
d0354c2ef2 | ||
|
|
17fc1181c2 | ||
|
|
dbe7c5fb93 | ||
|
|
54b421d01f | ||
|
|
0e9611f802 | ||
|
|
4743a2439d | ||
|
|
242c167f82 | ||
|
|
e071b994cf | ||
|
|
60bb8aa0fa | ||
|
|
9f56f34ea4 | ||
|
|
d66b33e600 | ||
|
|
63b7c0361e | ||
|
|
dea66ff49d | ||
|
|
c29621741e | ||
|
|
6d7d013f7a | ||
|
|
ed8e2420a5 | ||
|
|
92ad66dd59 | ||
|
|
5699e34ae9 | ||
|
|
5f9dc26173 | ||
|
|
6d29ec2b90 | ||
|
|
f91ae5c7a0 | ||
|
|
33c69bf76f | ||
|
|
3e95bb259f | ||
|
|
64b6bee559 | ||
|
|
61cb029780 | ||
|
|
c02d823618 | ||
|
|
fec36919c2 |
10
.github/workflows/lint-test-build-web.yml
vendored
10
.github/workflows/lint-test-build-web.yml
vendored
@@ -11,7 +11,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create env file
|
||||
run: |
|
||||
@@ -23,7 +23,7 @@ jobs:
|
||||
cat .env
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "web/package-lock.json"
|
||||
@@ -43,7 +43,7 @@ jobs:
|
||||
needs: [lint-web]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create env file
|
||||
run: |
|
||||
@@ -55,7 +55,7 @@ jobs:
|
||||
cat .env
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "web/package-lock.json"
|
||||
@@ -68,7 +68,7 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Upload build to Github artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unraid-web
|
||||
path: web/.nuxt/nuxt-custom-elements/dist/unraid-components
|
||||
|
||||
67
.github/workflows/main.yml
vendored
67
.github/workflows/main.yml
vendored
@@ -12,6 +12,17 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
release-please:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write
|
||||
pull-requests: write
|
||||
steps:
|
||||
- id: release
|
||||
uses: googleapis/release-please-action@v4
|
||||
outputs:
|
||||
releases_created: ${{ steps.release.outputs.releases_created }}
|
||||
tag_name: ${{ steps.release.outputs.tag_name }}
|
||||
start:
|
||||
# This prevents a tag running twice as it'll have a "tag" and a "commit" event
|
||||
# We only want the tag to run the action as it'll be able to create the release notes
|
||||
@@ -29,7 +40,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Reconfigure git to use HTTP authenti:cation
|
||||
@@ -38,7 +49,7 @@ jobs:
|
||||
ssh://git@github.com/
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: "api/.nvmrc"
|
||||
|
||||
@@ -69,7 +80,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: false
|
||||
|
||||
@@ -81,10 +92,10 @@ jobs:
|
||||
- name: Build Docker Compose
|
||||
run: |
|
||||
docker network create mothership_default
|
||||
GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker-compose build builder
|
||||
GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker compose build builder
|
||||
|
||||
- name: Run Docker Compose
|
||||
run: GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker-compose run builder npm run coverage
|
||||
run: GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker compose run builder npm run coverage
|
||||
|
||||
lint-web:
|
||||
defaults:
|
||||
@@ -93,7 +104,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create env file
|
||||
run: |
|
||||
@@ -105,7 +116,7 @@ jobs:
|
||||
cat .env
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "web/package-lock.json"
|
||||
@@ -130,7 +141,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Add SSH deploy key
|
||||
uses: shimataro/ssh-key-action@v2
|
||||
@@ -139,7 +150,7 @@ jobs:
|
||||
known_hosts: ${{ secrets.KNOWN_HOSTS }}
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
node-version-file: "api/.nvmrc"
|
||||
|
||||
@@ -170,7 +181,7 @@ jobs:
|
||||
echo "::set-output name=API_SHA256::${API_SHA256}"
|
||||
|
||||
- name: Upload tgz to Github artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unraid-api
|
||||
path: ${{ github.workspace }}/api/deploy/release/*.tgz
|
||||
@@ -185,7 +196,7 @@ jobs:
|
||||
needs: [lint-web]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create env file
|
||||
run: |
|
||||
@@ -197,7 +208,7 @@ jobs:
|
||||
cat .env
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "web/package-lock.json"
|
||||
@@ -210,7 +221,7 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Upload build to Github artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unraid-web
|
||||
path: web/.nuxt/nuxt-custom-elements/dist/unraid-components
|
||||
@@ -227,9 +238,9 @@ jobs:
|
||||
with:
|
||||
timezoneLinux: "America/Los_Angeles"
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Download unraid web components
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: unraid-web
|
||||
path: ./plugin/source/dynamix.unraid.net/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components
|
||||
@@ -242,7 +253,7 @@ jobs:
|
||||
bash ./pkg_build.sh s
|
||||
bash ./pkg_build.sh p
|
||||
- name: Upload binary txz and plg to Github artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
path: |
|
||||
@@ -259,19 +270,19 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Make Staging Release Folder
|
||||
run: mkdir staging-release/
|
||||
|
||||
- name: Download unraid-api binary tgz
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: unraid-api
|
||||
path: staging-release
|
||||
|
||||
- name: Download plugin binary tgz
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
|
||||
@@ -298,6 +309,18 @@ jobs:
|
||||
source: staging-release
|
||||
out_dir: unraid-api
|
||||
|
||||
- name: Upload Staging Plugin to Cloudflare Bucket
|
||||
uses: jakejarvis/s3-sync-action@v0.5.1
|
||||
env:
|
||||
AWS_S3_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
|
||||
AWS_S3_BUCKET: ${{ secrets.CF_BUCKET_PREVIEW }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: 'auto'
|
||||
SOURCE_DIR: staging-release
|
||||
DEST_DIR: unraid-api
|
||||
|
||||
|
||||
create-draft-release:
|
||||
# Only create new draft if this is a version tag
|
||||
if: |
|
||||
@@ -307,15 +330,15 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Download unraid-api binary tgz
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: unraid-api
|
||||
|
||||
- name: Download plugin binary tgz
|
||||
uses: actions/download-artifact@v3
|
||||
uses: actions/download-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
|
||||
|
||||
10
.github/workflows/pull-request-web.yml
vendored
10
.github/workflows/pull-request-web.yml
vendored
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create env file
|
||||
run: |
|
||||
@@ -29,7 +29,7 @@ jobs:
|
||||
cat .env
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "web/package-lock.json"
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
needs: [lint-web]
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Create env file
|
||||
run: |
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
cat .env
|
||||
|
||||
- name: Install node
|
||||
uses: actions/setup-node@v3
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: "npm"
|
||||
cache-dependency-path: "web/package-lock.json"
|
||||
@@ -76,7 +76,7 @@ jobs:
|
||||
run: npm run build
|
||||
|
||||
- name: Upload build to Github artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unraid-web
|
||||
path: web/.nuxt/nuxt-custom-elements/dist/unraid-components
|
||||
|
||||
24
.github/workflows/pull-request.yml
vendored
24
.github/workflows/pull-request.yml
vendored
@@ -24,15 +24,15 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
# network=host driver-opt needed to push to local registry
|
||||
driver-opts: network=host
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: api
|
||||
target: builder
|
||||
@@ -57,16 +57,16 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
# network=host driver-opt needed to push to local registry
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: api
|
||||
target: builder
|
||||
@@ -97,16 +97,16 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
persist-credentials: true
|
||||
- uses: docker/setup-buildx-action@v2
|
||||
- uses: docker/setup-buildx-action@v3
|
||||
with:
|
||||
# network=host driver-opt needed to push to local registry
|
||||
driver-opts: network=host
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@v4
|
||||
uses: docker/build-push-action@v5
|
||||
with:
|
||||
context: api
|
||||
target: builder
|
||||
@@ -127,7 +127,7 @@ jobs:
|
||||
echo "::set-output name=API_SHA256::${API_SHA256}"
|
||||
|
||||
- name: Upload tgz to Github artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: unraid-api
|
||||
path: ${{ github.workspace }}/api/deploy/release/*.tgz
|
||||
@@ -155,7 +155,7 @@ jobs:
|
||||
with:
|
||||
timezoneLinux: "America/Los_Angeles"
|
||||
- name: Checkout repo
|
||||
uses: actions/checkout@v3
|
||||
uses: actions/checkout@v4
|
||||
- name: Build Plugin
|
||||
run: |
|
||||
cd source/dynamix.unraid.net
|
||||
@@ -173,7 +173,7 @@ jobs:
|
||||
# escapedNotes=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"${RELEASE_NOTES}")
|
||||
# sed -i -z -E "s/<CHANGES>(.*)<\/CHANGES>/<CHANGES>\n${escapedNotes}\n<\/CHANGES>/g" "plugins/dynamix.unraid.net.staging.plg"
|
||||
- name: Upload binary txz and plg to Github artifacts
|
||||
uses: actions/upload-artifact@v3
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: connect-files
|
||||
path: |
|
||||
|
||||
57
.github/workflows/release-production.yml
vendored
Normal file
57
.github/workflows/release-production.yml
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
name: Publish Release to Digital Ocean
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish-to-digital-ocean:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Download Release Artifacts (Plugins)
|
||||
uses: dsaltares/fetch-gh-release-asset@master
|
||||
with:
|
||||
file: ".*"
|
||||
regex: true
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
target: "./"
|
||||
version: "latest"
|
||||
|
||||
- uses: cardinalby/git-get-release-action@v1
|
||||
id: release-info
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
with:
|
||||
latest: true
|
||||
prerelease: false
|
||||
- name: Get Release Changelog
|
||||
run: |
|
||||
notes=$(cat << EOF
|
||||
${{ steps.release-info.outputs.body }}
|
||||
EOF
|
||||
)
|
||||
escapedNotes=$(sed -e 's/[&\\/]/\\&/g; s/$/\\/' -e '$s/\\$//' <<<"$notes")
|
||||
sed -i -z -E "s/<CHANGES>(.*)<\/CHANGES>/<CHANGES>\n${escapedNotes}\n<\/CHANGES>/g" "dynamix.unraid.net.plg"
|
||||
sed -i -z -E "s/<CHANGES>(.*)<\/CHANGES>/<CHANGES>\n${escapedNotes}\n<\/CHANGES>/g" "dynamix.unraid.net.staging.plg"
|
||||
|
||||
- name: Upload All Release Files to DO Spaces
|
||||
uses: BetaHuhn/do-spaces-action@v2
|
||||
with:
|
||||
access_key: ${{ secrets.DO_ACCESS_KEY }}
|
||||
secret_key: ${{ secrets.DO_SECRET_KEY }}
|
||||
space_name: ${{ secrets.DO_SPACE_NAME }}
|
||||
space_region: ${{ secrets.DO_SPACE_REGION }}
|
||||
source: "."
|
||||
out_dir: unraid-api
|
||||
|
||||
- name: Upload Staging Plugin to Cloudflare Bucket
|
||||
uses: jakejarvis/s3-sync-action@v0.5.1
|
||||
env:
|
||||
AWS_S3_ENDPOINT: ${{ secrets.CF_ENDPOINT }}
|
||||
AWS_S3_BUCKET: ${{ secrets.CF_BUCKET }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.CF_ACCESS_KEY_ID }}
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.CF_SECRET_ACCESS_KEY }}
|
||||
AWS_REGION: 'auto'
|
||||
SOURCE_DIR: "."
|
||||
DEST_DIR: unraid-api
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@@ -52,6 +52,9 @@ typings/
|
||||
# Visual Studio Code workspace
|
||||
.vscode/sftp.json
|
||||
|
||||
# Jetbrains
|
||||
.idea
|
||||
|
||||
# OSX
|
||||
.DS_Store
|
||||
|
||||
@@ -83,4 +86,6 @@ deploy/*
|
||||
.cache
|
||||
.output
|
||||
.env*
|
||||
!.env.example
|
||||
!.env.example
|
||||
|
||||
fb_keepalive
|
||||
1
.release-please-manifest.json
Normal file
1
.release-please-manifest.json
Normal file
@@ -0,0 +1 @@
|
||||
{"api":"3.10.0","web":"3.10.0"}
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -29,5 +29,6 @@
|
||||
"i18n-ally.localesPaths": [
|
||||
"locales"
|
||||
],
|
||||
"i18n-ally.keystyle": "flat"
|
||||
"i18n-ally.keystyle": "flat",
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
}
|
||||
@@ -13,6 +13,6 @@ PLAYGROUND=true
|
||||
INTROSPECTION=true
|
||||
MOTHERSHIP_GRAPHQL_LINK="http://authenticator:3000/graphql"
|
||||
NODE_TLS_REJECT_UNAUTHORIZED=0
|
||||
BYPASS_PERMISSION_CHECKS=true
|
||||
BYPASS_PERMISSION_CHECKS=false
|
||||
BYPASS_CORS_CHECKS=false
|
||||
CHOKIDAR_USEPOLLING=true
|
||||
|
||||
11
api/.env.test
Normal file
11
api/.env.test
Normal file
@@ -0,0 +1,11 @@
|
||||
VERSION="THIS_WILL_BE_REPLACED_WHEN_BUILT"
|
||||
|
||||
PATHS_UNRAID_DATA=./dev/data # Where we store plugin data (e.g. permissions.json)
|
||||
PATHS_STATES=./dev/states # Where .ini files live (e.g. vars.ini)
|
||||
PATHS_DYNAMIX_BASE=./dev/dynamix # Dynamix's data directory
|
||||
PATHS_DYNAMIX_CONFIG=./dev/dynamix/dynamix.cfg # Dynamix's config file
|
||||
PATHS_MY_SERVERS_CONFIG=./dev/Unraid.net/myservers.cfg # My servers config file
|
||||
PATHS_MY_SERVERS_FB=./dev/Unraid.net/fb_keepalive # My servers flashbackup timekeeper file
|
||||
PATHS_KEYFILE_BASE=./dev/Unraid.net # Keyfile location
|
||||
PORT=5000
|
||||
NODE_ENV=test
|
||||
@@ -1 +1 @@
|
||||
18.17.1
|
||||
18.19.1
|
||||
455
api/CHANGELOG.md
455
api/CHANGELOG.md
@@ -2,6 +2,459 @@
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
## [3.11.0](https://github.com/unraid/api/compare/v3.10.1...v3.11.0) (2024-09-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* reduce how often rc.flashbackup checks for changes ([793d368](https://github.com/unraid/api/commit/793d3681404018e0ae933df0ad111809220ad138))
|
||||
* send api_version to flash/activate endpoint ([d8ec20e](https://github.com/unraid/api/commit/d8ec20ea6aa35aa241abd8424c4d884bcbb8f590))
|
||||
* update ProvisionCert.php to clean hosts file when it runs ([fbe20c9](https://github.com/unraid/api/commit/fbe20c97b327849c15a4b34f5f53476edaefbeb6))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* remove local flash backup ratelimit file on uninstall/update ([abf207b](https://github.com/unraid/api/commit/abf207b077861798c53739b1965207f87d5633b3))
|
||||
|
||||
### [3.10.1](https://github.com/unraid/api/compare/v3.10.0...v3.10.1) (2024-09-03)
|
||||
|
||||
## [3.10.0](https://github.com/unraid/api/compare/v3.9.0...v3.10.0) (2024-09-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a timestamp to flash backup ([#877](https://github.com/unraid/api/issues/877)) ([b868fd4](https://github.com/unraid/api/commit/b868fd46c3886b2182245a61f20be6df65e46abe))
|
||||
* add environment to docker-compose ([2ee4683](https://github.com/unraid/api/commit/2ee46839095e3b8ee287cfe10f29ae9a39dcff68))
|
||||
* add global agent ([#897](https://github.com/unraid/api/issues/897)) ([8b0dc69](https://github.com/unraid/api/commit/8b0dc69f65bd3e280a21c50aab221334f7341b1c))
|
||||
* add logrotate to cron in nestjs ([#839](https://github.com/unraid/api/issues/839)) ([5c91524](https://github.com/unraid/api/commit/5c91524d849147c0ac7925f3a2f1cce67ffe75de))
|
||||
* add new staging url for connect website ([#841](https://github.com/unraid/api/issues/841)) ([4cfc07b](https://github.com/unraid/api/commit/4cfc07b6763dbb79b68cf01f7eaf7cf33370d4db))
|
||||
* add support for expiration in var.ini ([#833](https://github.com/unraid/api/issues/833)) ([0474c2e](https://github.com/unraid/api/commit/0474c2e14fa462d2e1ec6d9a7f974660385d073e))
|
||||
* always show DRA even if disabled ([ab708c0](https://github.com/unraid/api/commit/ab708c0df634e21bf81595412d7de0be3ff7c392))
|
||||
* close log on exit ([d6ede86](https://github.com/unraid/api/commit/d6ede86eca6301342cdf35bf1f9365896b5e5009))
|
||||
* create stable hash based on apikey rather than hostname ([ecf5554](https://github.com/unraid/api/commit/ecf5554e304cc7dee78cb1f206ef4e80222c3e64))
|
||||
* disable all legacy dashboard and network logic ([6784f4b](https://github.com/unraid/api/commit/6784f4b6e1a12b2f30bfa9ab4fe6310994bd18ae))
|
||||
* dynamic remote access using remote queries ([f7fc0c4](https://github.com/unraid/api/commit/f7fc0c431561978054d2ff37d1aa644865e846ec))
|
||||
* extraOrigins public, remove origin listener ([91f96ba](https://github.com/unraid/api/commit/91f96ba818773d6e71dde1ff52a4c8ec21ba6b5d))
|
||||
* fix codegen ([d0bf5bb](https://github.com/unraid/api/commit/d0bf5bb8197b11f7a250ca5392890184a1dbeff7))
|
||||
* fix exit hook and cleanup docker scripts ([#758](https://github.com/unraid/api/issues/758)) ([a9ff73e](https://github.com/unraid/api/commit/a9ff73e0a04c67e9ec9d5551cf0b1f124be6f381))
|
||||
* fix logging format on start and stop ([c6720c3](https://github.com/unraid/api/commit/c6720c331df055480d2d65b37290f4978fe429da))
|
||||
* local start command ([99b6007](https://github.com/unraid/api/commit/99b6007ba30353084a8bea54cc0e782fcc1bfea4))
|
||||
* log config recreation reason ([f36c72f](https://github.com/unraid/api/commit/f36c72f5ad44b7e41d1726fa181dc2b9f594c72c))
|
||||
* move dynamic remote access to be fully api controlled ([206eb6b](https://github.com/unraid/api/commit/206eb6b74aa83047237e5f6c94c46b08c6507168))
|
||||
* move FQDN urls to a generic parser ([#899](https://github.com/unraid/api/issues/899)) ([246595e](https://github.com/unraid/api/commit/246595ee7acd8370906a759cbe618def4f52c173))
|
||||
* nestjs initial query implementation ([#748](https://github.com/unraid/api/issues/748)) ([075d7f2](https://github.com/unraid/api/commit/075d7f25785bf686779b7fee1d5ea39f09ff3ea8))
|
||||
* new key types in API ([e42f9dc](https://github.com/unraid/api/commit/e42f9dc95be03e8389aac443f2147c07a316d48d))
|
||||
* regTy swapped ([564b25c](https://github.com/unraid/api/commit/564b25cf5ce0a62d40f8d63d44c81e9c8560e0be))
|
||||
* remove dashboard resolver completely in favor of direct field resolvers ([1cd1ee5](https://github.com/unraid/api/commit/1cd1ee534825ccf775208c438ae0bd777bbe4d39))
|
||||
* remove dashboard types ([2f0167d](https://github.com/unraid/api/commit/2f0167dc89835bcf8aa946425c5c6683221fd763))
|
||||
* run codegen and update build script ([07512ad](https://github.com/unraid/api/commit/07512adc13ee0d819db45ff6c5c5f58a0ba31141))
|
||||
* settings through the API ([#867](https://github.com/unraid/api/issues/867)) ([e73624b](https://github.com/unraid/api/commit/e73624be6be8bc2c70d898b8601a88cc8d20a3e4))
|
||||
* swap to docker compose from docker-compose ([ec16a6a](https://github.com/unraid/api/commit/ec16a6aab1a2d5c836387da438fbeade07d23425))
|
||||
* swap to fragement usage on webcomponent ([42733ab](https://github.com/unraid/api/commit/42733abf6e443516ff715569333422ce80d3b1d2))
|
||||
* update tests and snapshots ([c39aa17](https://github.com/unraid/api/commit/c39aa17e4302ed56b3097ab3244d840f11eb686b))
|
||||
* upgrade a ton of dependencies ([#842](https://github.com/unraid/api/issues/842)) ([94c1746](https://github.com/unraid/api/commit/94c174620c2347a3cf3d100404635f99a5b47287))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add serverName / description to dashboard payload ([9677aff](https://github.com/unraid/api/commit/9677aff1cd0942f36a2845f3f105601c494efd9e))
|
||||
* allow failure for log deletion ([eff3142](https://github.com/unraid/api/commit/eff31423927644be436a831126678719c2eb0621))
|
||||
* allowed origins check not working without spaces ([#838](https://github.com/unraid/api/issues/838)) ([b998b38](https://github.com/unraid/api/commit/b998b38355fab77ecc2f62bc64896766218db3d4))
|
||||
* **api:** readme discord url ([ffd5c6a](https://github.com/unraid/api/commit/ffd5c6afb64956e76df22c77104a21bc22798008))
|
||||
* build docker command updated to use dc.sh script ([0b40886](https://github.com/unraid/api/commit/0b40886e84f27a94dbf67ef4ca0cd8539ef3913e))
|
||||
* codegen on web run ([e2e67c2](https://github.com/unraid/api/commit/e2e67c21067a138d963f5f10760b84cf6a533542))
|
||||
* **deps:** update dependency @apollo/client to v3.9.5 ([#785](https://github.com/unraid/api/issues/785)) ([75b98bc](https://github.com/unraid/api/commit/75b98bc1cbca5b66ae72f52a0b6f5f58230a2473))
|
||||
* **deps:** update dependency graphql to v16.8.1 ([bff1b19](https://github.com/unraid/api/commit/bff1b19706bee1e3103e3a0a1d2fceb3154f9bba))
|
||||
* **deps:** update dependency graphql-ws to v5.15.0 ([#790](https://github.com/unraid/api/issues/790)) ([4773b13](https://github.com/unraid/api/commit/4773b132167d740d4c996efe22e0f1b99576fb9b))
|
||||
* **deps:** update dependency ws to v8.16.0 ([#815](https://github.com/unraid/api/issues/815)) ([212020e](https://github.com/unraid/api/commit/212020e78d4de0576137058a3374837b4a43e02d))
|
||||
* **deps:** update dependency wtfnode to v0.9.3 ([#901](https://github.com/unraid/api/issues/901)) ([a88482b](https://github.com/unraid/api/commit/a88482bfcbf134f55330f8728bc5c7f67c521773))
|
||||
* **deps:** update graphql-tools monorepo ([3447eb0](https://github.com/unraid/api/commit/3447eb047a1dcd575b88a96bbcef9946aca366a1))
|
||||
* **deps:** update graphql-tools monorepo (major) ([#693](https://github.com/unraid/api/issues/693)) ([3447eb0](https://github.com/unraid/api/commit/3447eb047a1dcd575b88a96bbcef9946aca366a1))
|
||||
* **deps:** update nest monorepo ([#816](https://github.com/unraid/api/issues/816)) ([4af3699](https://github.com/unraid/api/commit/4af36991b8b376f816ed51fd503a66e99675a3e7))
|
||||
* excessive logging ([89cb254](https://github.com/unraid/api/commit/89cb2544ed0e0edd33b59f15d487487e22c0ae32))
|
||||
* exit with process.exit not process.exitcode ([dcb6def](https://github.com/unraid/api/commit/dcb6def1cf3365dca819feed101160c8ad0125dc))
|
||||
* lint ([919873d](https://github.com/unraid/api/commit/919873d9edee304d99036a4a810db3789c734fbf))
|
||||
* local container startup commands cleaned up ([6c0ccb2](https://github.com/unraid/api/commit/6c0ccb2b24f98282be4db2e0b2e6362f4a187def))
|
||||
* logrotate not working due to invalid ownership of unraid-api folder ([ec0581a](https://github.com/unraid/api/commit/ec0581abf58a217f698d52d5337f2b312e5a645b))
|
||||
* optional check on api.version to allow fallback to save value ([0ac4455](https://github.com/unraid/api/commit/0ac4455f78407eca7aa1d6ee360830067a1c5c3e))
|
||||
* permission for dashboard payload ([704a530](https://github.com/unraid/api/commit/704a530653dac415766bded5e96f6060f931e591))
|
||||
* rearrange exit hook to try to fix closing ([843d3f4](https://github.com/unraid/api/commit/843d3f41162c5dbcfd7803912b1879d7a182231a))
|
||||
* revert myservers.cfg to fix test ([a7705be](https://github.com/unraid/api/commit/a7705beb0a5b32660367ad8de9b46b06f7a3bec7))
|
||||
* run hourly ([0425794](https://github.com/unraid/api/commit/0425794356a01262222e7dff2645d3629e00d0f6))
|
||||
* unused import ([065fe57](https://github.com/unraid/api/commit/065fe575f578a74d593805c3121dd7fbdfc3e5ae))
|
||||
* update snapshots ([c8a0a8e](https://github.com/unraid/api/commit/c8a0a8ec007abc0372464c7e2b44bd47b6babd94))
|
||||
|
||||
## [3.9.0](https://github.com/unraid/api/compare/api-v3.8.1...api-v3.9.0) (2024-09-03)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a timestamp to flash backup ([#877](https://github.com/unraid/api/issues/877)) ([b868fd4](https://github.com/unraid/api/commit/b868fd46c3886b2182245a61f20be6df65e46abe))
|
||||
* add environment to docker-compose ([2ee4683](https://github.com/unraid/api/commit/2ee46839095e3b8ee287cfe10f29ae9a39dcff68))
|
||||
* add global agent ([#897](https://github.com/unraid/api/issues/897)) ([8b0dc69](https://github.com/unraid/api/commit/8b0dc69f65bd3e280a21c50aab221334f7341b1c))
|
||||
* add logrotate to cron in nestjs ([#839](https://github.com/unraid/api/issues/839)) ([5c91524](https://github.com/unraid/api/commit/5c91524d849147c0ac7925f3a2f1cce67ffe75de))
|
||||
* add new staging url for connect website ([#841](https://github.com/unraid/api/issues/841)) ([4cfc07b](https://github.com/unraid/api/commit/4cfc07b6763dbb79b68cf01f7eaf7cf33370d4db))
|
||||
* add support for expiration in var.ini ([#833](https://github.com/unraid/api/issues/833)) ([0474c2e](https://github.com/unraid/api/commit/0474c2e14fa462d2e1ec6d9a7f974660385d073e))
|
||||
* always show DRA even if disabled ([ab708c0](https://github.com/unraid/api/commit/ab708c0df634e21bf81595412d7de0be3ff7c392))
|
||||
* close log on exit ([d6ede86](https://github.com/unraid/api/commit/d6ede86eca6301342cdf35bf1f9365896b5e5009))
|
||||
* create stable hash based on apikey rather than hostname ([ecf5554](https://github.com/unraid/api/commit/ecf5554e304cc7dee78cb1f206ef4e80222c3e64))
|
||||
* disable all legacy dashboard and network logic ([6784f4b](https://github.com/unraid/api/commit/6784f4b6e1a12b2f30bfa9ab4fe6310994bd18ae))
|
||||
* dynamic remote access using remote queries ([f7fc0c4](https://github.com/unraid/api/commit/f7fc0c431561978054d2ff37d1aa644865e846ec))
|
||||
* extraOrigins public, remove origin listener ([91f96ba](https://github.com/unraid/api/commit/91f96ba818773d6e71dde1ff52a4c8ec21ba6b5d))
|
||||
* fix codegen ([d0bf5bb](https://github.com/unraid/api/commit/d0bf5bb8197b11f7a250ca5392890184a1dbeff7))
|
||||
* fix exit hook and cleanup docker scripts ([#758](https://github.com/unraid/api/issues/758)) ([a9ff73e](https://github.com/unraid/api/commit/a9ff73e0a04c67e9ec9d5551cf0b1f124be6f381))
|
||||
* fix logging format on start and stop ([c6720c3](https://github.com/unraid/api/commit/c6720c331df055480d2d65b37290f4978fe429da))
|
||||
* local start command ([99b6007](https://github.com/unraid/api/commit/99b6007ba30353084a8bea54cc0e782fcc1bfea4))
|
||||
* log config recreation reason ([f36c72f](https://github.com/unraid/api/commit/f36c72f5ad44b7e41d1726fa181dc2b9f594c72c))
|
||||
* move dynamic remote access to be fully api controlled ([206eb6b](https://github.com/unraid/api/commit/206eb6b74aa83047237e5f6c94c46b08c6507168))
|
||||
* move FQDN urls to a generic parser ([#899](https://github.com/unraid/api/issues/899)) ([246595e](https://github.com/unraid/api/commit/246595ee7acd8370906a759cbe618def4f52c173))
|
||||
* nestjs initial query implementation ([#748](https://github.com/unraid/api/issues/748)) ([075d7f2](https://github.com/unraid/api/commit/075d7f25785bf686779b7fee1d5ea39f09ff3ea8))
|
||||
* new key types in API ([e42f9dc](https://github.com/unraid/api/commit/e42f9dc95be03e8389aac443f2147c07a316d48d))
|
||||
* regTy swapped ([564b25c](https://github.com/unraid/api/commit/564b25cf5ce0a62d40f8d63d44c81e9c8560e0be))
|
||||
* remove dashboard resolver completely in favor of direct field resolvers ([1cd1ee5](https://github.com/unraid/api/commit/1cd1ee534825ccf775208c438ae0bd777bbe4d39))
|
||||
* remove dashboard types ([2f0167d](https://github.com/unraid/api/commit/2f0167dc89835bcf8aa946425c5c6683221fd763))
|
||||
* run codegen and update build script ([07512ad](https://github.com/unraid/api/commit/07512adc13ee0d819db45ff6c5c5f58a0ba31141))
|
||||
* settings through the API ([#867](https://github.com/unraid/api/issues/867)) ([e73624b](https://github.com/unraid/api/commit/e73624be6be8bc2c70d898b8601a88cc8d20a3e4))
|
||||
* swap to docker compose from docker-compose ([ec16a6a](https://github.com/unraid/api/commit/ec16a6aab1a2d5c836387da438fbeade07d23425))
|
||||
* swap to fragement usage on webcomponent ([42733ab](https://github.com/unraid/api/commit/42733abf6e443516ff715569333422ce80d3b1d2))
|
||||
* update tests and snapshots ([c39aa17](https://github.com/unraid/api/commit/c39aa17e4302ed56b3097ab3244d840f11eb686b))
|
||||
* upgrade a ton of dependencies ([#842](https://github.com/unraid/api/issues/842)) ([94c1746](https://github.com/unraid/api/commit/94c174620c2347a3cf3d100404635f99a5b47287))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* add serverName / description to dashboard payload ([9677aff](https://github.com/unraid/api/commit/9677aff1cd0942f36a2845f3f105601c494efd9e))
|
||||
* allow failure for log deletion ([eff3142](https://github.com/unraid/api/commit/eff31423927644be436a831126678719c2eb0621))
|
||||
* allowed origins check not working without spaces ([#838](https://github.com/unraid/api/issues/838)) ([b998b38](https://github.com/unraid/api/commit/b998b38355fab77ecc2f62bc64896766218db3d4))
|
||||
* **api:** readme discord url ([ffd5c6a](https://github.com/unraid/api/commit/ffd5c6afb64956e76df22c77104a21bc22798008))
|
||||
* build docker command updated to use dc.sh script ([0b40886](https://github.com/unraid/api/commit/0b40886e84f27a94dbf67ef4ca0cd8539ef3913e))
|
||||
* codegen on web run ([e2e67c2](https://github.com/unraid/api/commit/e2e67c21067a138d963f5f10760b84cf6a533542))
|
||||
* **deps:** update dependency @apollo/client to v3.9.5 ([#785](https://github.com/unraid/api/issues/785)) ([75b98bc](https://github.com/unraid/api/commit/75b98bc1cbca5b66ae72f52a0b6f5f58230a2473))
|
||||
* **deps:** update dependency graphql to v16.8.1 ([bff1b19](https://github.com/unraid/api/commit/bff1b19706bee1e3103e3a0a1d2fceb3154f9bba))
|
||||
* **deps:** update dependency graphql-ws to v5.15.0 ([#790](https://github.com/unraid/api/issues/790)) ([4773b13](https://github.com/unraid/api/commit/4773b132167d740d4c996efe22e0f1b99576fb9b))
|
||||
* **deps:** update dependency ws to v8.16.0 ([#815](https://github.com/unraid/api/issues/815)) ([212020e](https://github.com/unraid/api/commit/212020e78d4de0576137058a3374837b4a43e02d))
|
||||
* **deps:** update dependency wtfnode to v0.9.3 ([#901](https://github.com/unraid/api/issues/901)) ([a88482b](https://github.com/unraid/api/commit/a88482bfcbf134f55330f8728bc5c7f67c521773))
|
||||
* **deps:** update graphql-tools monorepo ([3447eb0](https://github.com/unraid/api/commit/3447eb047a1dcd575b88a96bbcef9946aca366a1))
|
||||
* **deps:** update graphql-tools monorepo (major) ([#693](https://github.com/unraid/api/issues/693)) ([3447eb0](https://github.com/unraid/api/commit/3447eb047a1dcd575b88a96bbcef9946aca366a1))
|
||||
* **deps:** update nest monorepo ([#816](https://github.com/unraid/api/issues/816)) ([4af3699](https://github.com/unraid/api/commit/4af36991b8b376f816ed51fd503a66e99675a3e7))
|
||||
* excessive logging ([89cb254](https://github.com/unraid/api/commit/89cb2544ed0e0edd33b59f15d487487e22c0ae32))
|
||||
* exit with process.exit not process.exitcode ([dcb6def](https://github.com/unraid/api/commit/dcb6def1cf3365dca819feed101160c8ad0125dc))
|
||||
* lint ([919873d](https://github.com/unraid/api/commit/919873d9edee304d99036a4a810db3789c734fbf))
|
||||
* local container startup commands cleaned up ([6c0ccb2](https://github.com/unraid/api/commit/6c0ccb2b24f98282be4db2e0b2e6362f4a187def))
|
||||
* logrotate not working due to invalid ownership of unraid-api folder ([ec0581a](https://github.com/unraid/api/commit/ec0581abf58a217f698d52d5337f2b312e5a645b))
|
||||
* optional check on api.version to allow fallback to save value ([0ac4455](https://github.com/unraid/api/commit/0ac4455f78407eca7aa1d6ee360830067a1c5c3e))
|
||||
* permission for dashboard payload ([704a530](https://github.com/unraid/api/commit/704a530653dac415766bded5e96f6060f931e591))
|
||||
* rearrange exit hook to try to fix closing ([843d3f4](https://github.com/unraid/api/commit/843d3f41162c5dbcfd7803912b1879d7a182231a))
|
||||
* revert myservers.cfg to fix test ([a7705be](https://github.com/unraid/api/commit/a7705beb0a5b32660367ad8de9b46b06f7a3bec7))
|
||||
* run hourly ([0425794](https://github.com/unraid/api/commit/0425794356a01262222e7dff2645d3629e00d0f6))
|
||||
* unused import ([065fe57](https://github.com/unraid/api/commit/065fe575f578a74d593805c3121dd7fbdfc3e5ae))
|
||||
* update snapshots ([c8a0a8e](https://github.com/unraid/api/commit/c8a0a8ec007abc0372464c7e2b44bd47b6babd94))
|
||||
|
||||
### [3.8.1](https://github.com/unraid/api/compare/v3.8.0...v3.8.1) (2024-08-13)
|
||||
|
||||
## [3.8.0](https://github.com/unraid/api/compare/v3.7.1...v3.8.0) (2024-08-13)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* always force push ([662f3ce](https://github.com/unraid/api/commit/662f3ce440593e609c64364726f7da16dda0972b))
|
||||
* don't allow flash backup repos larger than 500MB ([#890](https://github.com/unraid/api/issues/890)) ([30a32f5](https://github.com/unraid/api/commit/30a32f5fe684bb32c084c4125aade5e63ffd788b))
|
||||
* downgradeOs callback for non stable osCurrentBranch ([17c4489](https://github.com/unraid/api/commit/17c4489e97bda504ca45e360591655ded166c355))
|
||||
* settings through the API ([#867](https://github.com/unraid/api/issues/867)) ([e73624b](https://github.com/unraid/api/commit/e73624be6be8bc2c70d898b8601a88cc8d20a3e4))
|
||||
* swap to docker compose from docker-compose ([ec16a6a](https://github.com/unraid/api/commit/ec16a6aab1a2d5c836387da438fbeade07d23425))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* apolloClient types ([f14c767](https://github.com/unraid/api/commit/f14c7673735b92aa167e9e8dcb14a045bcfea994))
|
||||
* **deps:** update dependency @vue/apollo-composable to v4.0.2 ([#787](https://github.com/unraid/api/issues/787)) ([edfc846](https://github.com/unraid/api/commit/edfc8464b0e0c2f38003ae8420e81532fd18351f))
|
||||
* formattedRegTm type ([748906e](https://github.com/unraid/api/commit/748906e15d30c6162e2f08f28724c9104c81d123))
|
||||
* i18n t prop type ([96d519f](https://github.com/unraid/api/commit/96d519f3e6b96ea7c4dc60616522216de20ee140))
|
||||
* lint error for web components ([bc27b20](https://github.com/unraid/api/commit/bc27b20524934cf896efb84a131cd270431c508c))
|
||||
* lint issues ([853dc19](https://github.com/unraid/api/commit/853dc195b13fff29160afb44f9ff11d4dd6a3232))
|
||||
* swap undefined to null ([ebba976](https://github.com/unraid/api/commit/ebba9769873a6536e3fce65978e6475d93280560))
|
||||
* tailwind config types ([0f77e55](https://github.com/unraid/api/commit/0f77e5596db3356b5dc05129b3ce215a8809e1dc))
|
||||
* ts-expect-error unneeded ([ee4d4e9](https://github.com/unraid/api/commit/ee4d4e9f12b4488ff39445bc72c1b83a9d93e993))
|
||||
* type check ([606aad7](https://github.com/unraid/api/commit/606aad703d91b72a14e15da3100dfa355052ed58))
|
||||
* type errors round 1 ([977d5da](https://github.com/unraid/api/commit/977d5daf04012f16e7b6602167338f0bc363735a))
|
||||
* update status button alignment ([4f2deaf](https://github.com/unraid/api/commit/4f2deaf70e5caa9f29fc5b2974b278f80b7b3a8a))
|
||||
|
||||
### [3.7.1](https://github.com/unraid/api/compare/v3.7.0...v3.7.1) (2024-05-15)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* reboot required and available edge case ([#885](https://github.com/unraid/api/issues/885)) ([76e9cdf](https://github.com/unraid/api/commit/76e9cdf81f06a19c2e4c9a40a4d8e062bad2a607))
|
||||
|
||||
## [3.7.0](https://github.com/unraid/api/compare/v3.6.0...v3.7.0) (2024-05-14)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add a timestamp to flash backup ([#877](https://github.com/unraid/api/issues/877)) ([b868fd4](https://github.com/unraid/api/commit/b868fd46c3886b2182245a61f20be6df65e46abe))
|
||||
* add support for outgoing proxies ([#863](https://github.com/unraid/api/issues/863)) ([223693e](https://github.com/unraid/api/commit/223693e0981d5f2884a1f8b8baf03d4dc58e8cb2))
|
||||
* array state on registration page ([d36fef0](https://github.com/unraid/api/commit/d36fef0545ddb820e67e8bc6cb42ea3644021d66))
|
||||
* downgradeOs callback ([154a976](https://github.com/unraid/api/commit/154a976109f0a32653a2851988420707631327ca))
|
||||
* Flash Backup requires connection to mothership ([#868](https://github.com/unraid/api/issues/868)) ([d127208](https://github.com/unraid/api/commit/d127208b5e0f7f9991f515f95b0e266d38cf3287))
|
||||
* **plg:** install prevent downgrade of shared page & php files ([#873](https://github.com/unraid/api/issues/873)) ([4ac72b1](https://github.com/unraid/api/commit/4ac72b16692c4246c9d2c0b53b23d8b2d95f5de6))
|
||||
* **plg:** plg install prevent web component downgrade ([8703bd4](https://github.com/unraid/api/commit/8703bd498108f5c05562584a708bd2306e53f7a6))
|
||||
* postbuild script to add timestamp to web component manifest ([47f08ea](https://github.com/unraid/api/commit/47f08ea3594a91098f67718c0123110c7b5f86f7))
|
||||
* registration page server error heading + subheading ([6038ebd](https://github.com/unraid/api/commit/6038ebdf39bf47f2cb5c0b1de84764795374f018))
|
||||
* remove cron to download JS daily ([#864](https://github.com/unraid/api/issues/864)) ([33f6d6b](https://github.com/unraid/api/commit/33f6d6b343de07dbe70de863926906736d42f371)), closes [#529](https://github.com/unraid/api/issues/529)
|
||||
* ui to allow second update without reboot ([b0f2d10](https://github.com/unraid/api/commit/b0f2d102917f54ab33f0ad10863522b8ff8e3ce5))
|
||||
* UI Update OS Cancel ([7c02308](https://github.com/unraid/api/commit/7c02308964d5e21990427a2c626c9db2d9e1fed0))
|
||||
* UnraidUpdateCancel script ([b73bdc0](https://github.com/unraid/api/commit/b73bdc021764762ed12dca494e1345412a45c677))
|
||||
* **web:** callback types myKeys & linkKey ([c88ee01](https://github.com/unraid/api/commit/c88ee01827396c3fa8a30bb88c4be712c80b1f4f))
|
||||
* **web:** Registration key linked to account status ([8f6182d](https://github.com/unraid/api/commit/8f6182d426453b73aa19c5f0f59469fa07571694))
|
||||
* **web:** registration page array status messaging ([23ef5a9](https://github.com/unraid/api/commit/23ef5a975e0d5ff0c246c2df5e6c2cb38979d12a))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **api:** readme discord url ([ffd5c6a](https://github.com/unraid/api/commit/ffd5c6afb64956e76df22c77104a21bc22798008))
|
||||
* keep minor enhancements from [#872](https://github.com/unraid/api/issues/872) ([#878](https://github.com/unraid/api/issues/878)) ([94a5aa8](https://github.com/unraid/api/commit/94a5aa87b9979fe0f02f884ac61298473bb3271a))
|
||||
* plugin file deployment script ([780d87d](https://github.com/unraid/api/commit/780d87d6589a5469f47ac3fdfd50610ecfc394c8))
|
||||
* prevent corrupt case model in state.php ([#874](https://github.com/unraid/api/issues/874)) ([4ad31df](https://github.com/unraid/api/commit/4ad31dfea9192146dbd2c90bc64a913c696ab0b7))
|
||||
* prevent local dev from throwing ssl error ([051f647](https://github.com/unraid/api/commit/051f6474becf3b25b242cdc6ceee67247b79f8ba))
|
||||
* rc.flashbackup needs to check both signed in and connected ([#882](https://github.com/unraid/api/issues/882)) ([ac8068c](https://github.com/unraid/api/commit/ac8068c9b084622d46fe2c9cb320b793c9ea8c52))
|
||||
* update os cancel refresh on update page ([213c16b](https://github.com/unraid/api/commit/213c16ba3d5a84ebf4965f9d2f4024c66605a613))
|
||||
* **web:** discord url ([1a6f4c6](https://github.com/unraid/api/commit/1a6f4c6db4ef0e5eefac467ec6583b14cb3546c4))
|
||||
* **web:** lint unused rebootVersion ([e198ec9](https://github.com/unraid/api/commit/e198ec9d458e262c412c2dcb5a9d279238de1730))
|
||||
* **web:** registration component remove unused ref ([76f556b](https://github.com/unraid/api/commit/76f556bd64b95ba96af795c9edfa045ebdff4444))
|
||||
|
||||
## [3.6.0](https://github.com/unraid/api/compare/v3.5.3...v3.6.0) (2024-03-26)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* server config enum message w/ ineligible support ([#861](https://github.com/unraid/api/issues/861)) ([4d3a351](https://github.com/unraid/api/commit/4d3a3510777090788573f4cee83694a0dc6f8df5))
|
||||
|
||||
### [3.5.3](https://github.com/unraid/api/compare/v3.5.2...v3.5.3) (2024-03-25)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* regDevs usage to allow more flexibility for STARTER ([#860](https://github.com/unraid/api/issues/860)) ([92a9600](https://github.com/unraid/api/commit/92a9600f3a242c5f263f1672eab81054b9cf4fae))
|
||||
|
||||
### [3.5.2](https://github.com/unraid/api/compare/v3.5.1...v3.5.2) (2024-03-06)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* **deps:** update dependency vue-i18n to v9.10.1 ([#813](https://github.com/unraid/api/issues/813)) ([69b599c](https://github.com/unraid/api/commit/69b599c5ed8d44864201a32b4d952427d454dc74))
|
||||
* **deps:** update dependency wretch to v2.8.0 ([#814](https://github.com/unraid/api/issues/814)) ([66900b4](https://github.com/unraid/api/commit/66900b495b82b923264897d38b1529a22b10aa1c))
|
||||
* update os check modal button conditionals ([282a836](https://github.com/unraid/api/commit/282a83625f417ccefe090b65cc6b73a084727a87))
|
||||
* update os check modal ineligible date format ([83083de](https://github.com/unraid/api/commit/83083de1e698f73a35635ae6047dcf49fd4b8114))
|
||||
|
||||
### [3.5.1](https://github.com/unraid/api/compare/v3.5.0...v3.5.1) (2024-02-29)
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* build docker command updated to use dc.sh script ([0b40886](https://github.com/unraid/api/commit/0b40886e84f27a94dbf67ef4ca0cd8539ef3913e))
|
||||
* date format in UnraidCheck.php ([#852](https://github.com/unraid/api/issues/852)) ([6465f2d](https://github.com/unraid/api/commit/6465f2d7b2394090f35e29cdd680d98ce37f3728))
|
||||
* **deps:** update dependency @apollo/client to v3.9.5 ([#785](https://github.com/unraid/api/issues/785)) ([75b98bc](https://github.com/unraid/api/commit/75b98bc1cbca5b66ae72f52a0b6f5f58230a2473))
|
||||
* **deps:** update dependency @heroicons/vue to v2.1.1 ([#804](https://github.com/unraid/api/issues/804)) ([a0eb7ee](https://github.com/unraid/api/commit/a0eb7ee3ec459dbe1992a7f85bf194da30395a74))
|
||||
* **deps:** update dependency focus-trap to v7.5.4 ([#788](https://github.com/unraid/api/issues/788)) ([fe000e8](https://github.com/unraid/api/commit/fe000e83825e82cac558d3277664a440e59c0e4a))
|
||||
* **deps:** update dependency graphql-ws to v5.15.0 ([#790](https://github.com/unraid/api/issues/790)) ([4773b13](https://github.com/unraid/api/commit/4773b132167d740d4c996efe22e0f1b99576fb9b))
|
||||
* display dropdown for pro key no connect installed ([#848](https://github.com/unraid/api/issues/848)) ([b559604](https://github.com/unraid/api/commit/b55960429895b46627f1cd3ed1683ee527e62944))
|
||||
* dropdown reboot link text ([#849](https://github.com/unraid/api/issues/849)) ([a8ed5e5](https://github.com/unraid/api/commit/a8ed5e5628bc71fb783a03c3db92d21805243738))
|
||||
* os updates rc to stable ([bf1bd88](https://github.com/unraid/api/commit/bf1bd887d60ac085bf4aeae90f11be3b45ee1182))
|
||||
* state connect values without connect installed ([e47de6c](https://github.com/unraid/api/commit/e47de6c2c5db7a2a1a9b24099feb02023b3a7bbf))
|
||||
* state php breaking with double quotes in server description ([c6e92aa](https://github.com/unraid/api/commit/c6e92aa3157c9fe9e7b83580881ebcc1cbd03658))
|
||||
* state php special chars for html attributes ([#853](https://github.com/unraid/api/issues/853)) ([dd4139c](https://github.com/unraid/api/commit/dd4139cf1a7ae5c6f9b00111c33ae124bb17e630))
|
||||
* unraid-api missing start command + var defaults ([ceb4c58](https://github.com/unraid/api/commit/ceb4c587d20c7527f2b36a3278c310b0e657bfba))
|
||||
* unraid-api.php $param1 fallback ([909c79c](https://github.com/unraid/api/commit/909c79c8c82500aea1a0d4d00766f788103c5fe3))
|
||||
|
||||
## [3.5.0](https://github.com/unraid/api/compare/v3.4.0...v3.5.0) (2024-02-07)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add manage account link to all versions of upc dropdown ([678e620](https://github.com/unraid/api/commit/678e620c1902a376b1866265711d5722b4119d8e))
|
||||
* add new staging url for connect website ([#841](https://github.com/unraid/api/issues/841)) ([4cfc07b](https://github.com/unraid/api/commit/4cfc07b6763dbb79b68cf01f7eaf7cf33370d4db))
|
||||
* also ship to cloudflare ([#844](https://github.com/unraid/api/issues/844)) ([41c4210](https://github.com/unraid/api/commit/41c42103685209592b272f81a877702da04d0915))
|
||||
* button add underline-hover-red style option ([f2fa5fa](https://github.com/unraid/api/commit/f2fa5fa49675ef461330be7b7eb3e3e4106983b0))
|
||||
* changelog modal ([2ddbacd](https://github.com/unraid/api/commit/2ddbacd137cc5748244c3d25ac91f82e64d77f99))
|
||||
* check update response modal ([39678f0](https://github.com/unraid/api/commit/39678f0bb0ddc5f87ea7f5ed80a0472100ea8b5d))
|
||||
* create WebguiCheckForUpdate endpoint ([41d546e](https://github.com/unraid/api/commit/41d546eea5fcf6593d7b5047274c074bb89c1802))
|
||||
* getOsReleaseBySha256 cached endpoint with keyfile header ([cd2413a](https://github.com/unraid/api/commit/cd2413abe8c5baab40e4e5974e08a5d18dce8e0d))
|
||||
* new check update buttons in dropdown ([ef5fcb9](https://github.com/unraid/api/commit/ef5fcb96a324143da864df803acaa0da1cd00eb7))
|
||||
* ship preview to different bucket ([#845](https://github.com/unraid/api/issues/845)) ([8e5d247](https://github.com/unraid/api/commit/8e5d247bca83d9e50977c9b16b212841ac9f70ad))
|
||||
* ship production to different bucket ([#846](https://github.com/unraid/api/issues/846)) ([63c0875](https://github.com/unraid/api/commit/63c08758c76425e007b1779bb2f77b75bc45896e))
|
||||
* unraidcheck callable from webgui with altUrl & json output ([ba8a67e](https://github.com/unraid/api/commit/ba8a67edfa043f442b11724227129f8d3f6cae0a))
|
||||
* update modals ([8ad7d8b](https://github.com/unraid/api/commit/8ad7d8be9437e0caa0409da8f7322050919fbbaa))
|
||||
* update os ignore release ([1955eb2](https://github.com/unraid/api/commit/1955eb23a3cdc30f0a67bc5950a047f83a860d99))
|
||||
* update os notifications enabled usage + link to enable & more options to account app ([5c82aff](https://github.com/unraid/api/commit/5c82aff80dc7e6d8f4b23e52af29abc2b8576424))
|
||||
* updateOs check response determines if update auth is required ([a9816d9](https://github.com/unraid/api/commit/a9816d9ad48ff80d87b5aeb236ff60c4979ad298))
|
||||
* updateOs store call local server-side endpoint & add modal support ([be48447](https://github.com/unraid/api/commit/be48447f943828af281095c5a092ac686e729030))
|
||||
* upgrade a ton of dependencies ([#842](https://github.com/unraid/api/issues/842)) ([94c1746](https://github.com/unraid/api/commit/94c174620c2347a3cf3d100404635f99a5b47287))
|
||||
* WebguiCheckForUpdate using server-side check ([590deb1](https://github.com/unraid/api/commit/590deb130c301d4004fecdc211270583806b5593))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* backport _var() PHP function to older versions of Unraid ([f53150e](https://github.com/unraid/api/commit/f53150e1fa33b3f45b66ad0dc5eaabc470564d45))
|
||||
* changlog relative links and external links ([a789e20](https://github.com/unraid/api/commit/a789e204ce7b966e6c935923626538ac344aeefe))
|
||||
* check update response modal expired key button styles ([92993e3](https://github.com/unraid/api/commit/92993e3e0b6240c83a6a64efedd8ddb3be3f9ef7))
|
||||
* **deps:** update dependency ws to v8.16.0 ([#815](https://github.com/unraid/api/issues/815)) ([212020e](https://github.com/unraid/api/commit/212020e78d4de0576137058a3374837b4a43e02d))
|
||||
* extraLinks when no updates available ([853a991](https://github.com/unraid/api/commit/853a9911e3fd7eec9bbc88468de78f87b448d477))
|
||||
* ignore release localStorage ([62c45ec](https://github.com/unraid/api/commit/62c45ec9d7c68498bbcfe933a5b63e4759c7129c))
|
||||
* lint ([83235f9](https://github.com/unraid/api/commit/83235f9db726f4582b9d353a66f2f5e8925b8e34))
|
||||
* lint unused value ([2c7e53b](https://github.com/unraid/api/commit/2c7e53bf67d1f214201624b39786bfb7de6aa520))
|
||||
* marked-base-url install ([416ba71](https://github.com/unraid/api/commit/416ba716aa750a094e8cd521a79f6deebcd37864))
|
||||
* missing translations ([faf17e4](https://github.com/unraid/api/commit/faf17e41e81c11443bc062d8ce35a33d9ae9ebbc))
|
||||
* regTm format after key install without page refresh ([f3ddb31](https://github.com/unraid/api/commit/f3ddb31f994de9192f7203698ecc5d7de673c6a3))
|
||||
* regTm format when already set ([5ad911f](https://github.com/unraid/api/commit/5ad911f8133daa60de53da738d41c6a59e2f02cc))
|
||||
* ServerUpdateOsResponse type ([78bdae8](https://github.com/unraid/api/commit/78bdae86c907142d3ee32d6715eaa8f5a974a1ed))
|
||||
* State Class usage in other files ([4ad7f53](https://github.com/unraid/api/commit/4ad7f53ec145b2e6d2895619523e90c1daa3f68f))
|
||||
* state data humanReadable switch fallthrus ([9144e39](https://github.com/unraid/api/commit/9144e39d39aa56af0ad897735d1a3545330920d0))
|
||||
* state php usage from cli ([46fd321](https://github.com/unraid/api/commit/46fd321707c14cd1f265ee806f673500d87132dd))
|
||||
* translations ([3fabd57](https://github.com/unraid/api/commit/3fabd5756674c06fa803729cf13d19c592d8d46a))
|
||||
* type issue with changlelog modal visibility ([e3c3f6b](https://github.com/unraid/api/commit/e3c3f6bf0f1882788291db17bd74865fefc3abf6))
|
||||
|
||||
## [3.4.0](https://github.com/unraid/api/compare/v3.3.0...v3.4.0) (2024-01-11)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add logrotate to cron in nestjs ([#839](https://github.com/unraid/api/issues/839)) ([5c91524](https://github.com/unraid/api/commit/5c91524d849147c0ac7925f3a2f1cce67ffe75de))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* allow failure for log deletion ([eff3142](https://github.com/unraid/api/commit/eff31423927644be436a831126678719c2eb0621))
|
||||
* allowed origins check not working without spaces ([#838](https://github.com/unraid/api/issues/838)) ([b998b38](https://github.com/unraid/api/commit/b998b38355fab77ecc2f62bc64896766218db3d4))
|
||||
* excessive logging ([89cb254](https://github.com/unraid/api/commit/89cb2544ed0e0edd33b59f15d487487e22c0ae32))
|
||||
* run hourly ([0425794](https://github.com/unraid/api/commit/0425794356a01262222e7dff2645d3629e00d0f6))
|
||||
|
||||
## [3.3.0](https://github.com/unraid/api/compare/v3.2.3...v3.3.0) (2024-01-09)
|
||||
|
||||
|
||||
### Features
|
||||
|
||||
* add button to add current origin to extra origins setting ([8c15163](https://github.com/unraid/api/commit/8c15163b3b072122bff1f8f25de62594b1e67992))
|
||||
* add environment to docker-compose ([2ee4683](https://github.com/unraid/api/commit/2ee46839095e3b8ee287cfe10f29ae9a39dcff68))
|
||||
* add support for expiration in var.ini ([#833](https://github.com/unraid/api/issues/833)) ([0474c2e](https://github.com/unraid/api/commit/0474c2e14fa462d2e1ec6d9a7f974660385d073e))
|
||||
* always show DRA even if disabled ([ab708c0](https://github.com/unraid/api/commit/ab708c0df634e21bf81595412d7de0be3ff7c392))
|
||||
* change sort order of Update/Downgrade ([#754](https://github.com/unraid/api/issues/754)) ([be96b3a](https://github.com/unraid/api/commit/be96b3aac709682a6517fa6e84beb586b9d8bf5c))
|
||||
* check for OS updates via PHP ([#752](https://github.com/unraid/api/issues/752)) ([4496615](https://github.com/unraid/api/commit/44966157b80a51dfe01d927c2af2d010c04becc5))
|
||||
* close log on exit ([d6ede86](https://github.com/unraid/api/commit/d6ede86eca6301342cdf35bf1f9365896b5e5009))
|
||||
* disable account & key actions when unraid-api CORS error ([1d15406](https://github.com/unraid/api/commit/1d1540646a264038ae96f4063c31a40cd048d2f9))
|
||||
* extraOrigins public, remove origin listener ([91f96ba](https://github.com/unraid/api/commit/91f96ba818773d6e71dde1ff52a4c8ec21ba6b5d))
|
||||
* fix codegen ([d0bf5bb](https://github.com/unraid/api/commit/d0bf5bb8197b11f7a250ca5392890184a1dbeff7))
|
||||
* fix exit hook and cleanup docker scripts ([#758](https://github.com/unraid/api/issues/758)) ([a9ff73e](https://github.com/unraid/api/commit/a9ff73e0a04c67e9ec9d5551cf0b1f124be6f381))
|
||||
* fix logging format on start and stop ([c6720c3](https://github.com/unraid/api/commit/c6720c331df055480d2d65b37290f4978fe429da))
|
||||
* improve check for OS updates via PHP ([cde12b2](https://github.com/unraid/api/commit/cde12b247f9bba97644750cd95a2b0db320ca1d9))
|
||||
* local start command ([99b6007](https://github.com/unraid/api/commit/99b6007ba30353084a8bea54cc0e782fcc1bfea4))
|
||||
* log config recreation reason ([f36c72f](https://github.com/unraid/api/commit/f36c72f5ad44b7e41d1726fa181dc2b9f594c72c))
|
||||
* nestjs initial query implementation ([#748](https://github.com/unraid/api/issues/748)) ([075d7f2](https://github.com/unraid/api/commit/075d7f25785bf686779b7fee1d5ea39f09ff3ea8))
|
||||
* new key types in API ([e42f9dc](https://github.com/unraid/api/commit/e42f9dc95be03e8389aac443f2147c07a316d48d))
|
||||
* npm scripts to prevent webgui builds with wrong urls ([279966a](https://github.com/unraid/api/commit/279966afa3c218fbe85bafe91ee40fff2eb59ef2))
|
||||
* patch DefaultPageLayout for web component ([629fec6](https://github.com/unraid/api/commit/629fec64f911131e4ab3810c99028b484ce18b83))
|
||||
* **plg:** WIP extra origins support ([85acaae](https://github.com/unraid/api/commit/85acaaee02dad98eeef8a8c4a09b463e84d593b4))
|
||||
* regTy swapped ([564b25c](https://github.com/unraid/api/commit/564b25cf5ce0a62d40f8d63d44c81e9c8560e0be))
|
||||
* run codegen and update build script ([07512ad](https://github.com/unraid/api/commit/07512adc13ee0d819db45ff6c5c5f58a0ba31141))
|
||||
* server store isOsVersionStable ([b5ee4d4](https://github.com/unraid/api/commit/b5ee4d4ee632a7528e6f5df079cab0cb5ea656eb))
|
||||
* stretch downgrade component buttons ([fa4f63e](https://github.com/unraid/api/commit/fa4f63e8bfca525ccfedb16f19d395bf11a68561))
|
||||
* swap to fragement usage on webcomponent ([42733ab](https://github.com/unraid/api/commit/42733abf6e443516ff715569333422ce80d3b1d2))
|
||||
* **web:** caseModel ([4174d0b](https://github.com/unraid/api/commit/4174d0bf2cac99af5db48e5642e0037d7425c952))
|
||||
* **web:** create script to move build to webgui repo ([92df453](https://github.com/unraid/api/commit/92df453255fed45210d9a192c68bb27d3b0ee981))
|
||||
* **web:** downgrade os web component ([45496ab](https://github.com/unraid/api/commit/45496ab7685d4bbfe591be46489260bac9b03474))
|
||||
* **web:** finalize api cors error & settings field ([e1d9e16](https://github.com/unraid/api/commit/e1d9e16b8e80e0940a0078131ea629559e3238ec))
|
||||
* **web:** guidValidation if new keyfile auto install ([0abb196](https://github.com/unraid/api/commit/0abb196d2c57ead4dca2adb2981ab79cdd1647c4))
|
||||
* **web:** localStorage craftUrl for dev ([e646187](https://github.com/unraid/api/commit/e646187b04548c010cf26c7ae38a82ced6270394))
|
||||
* **web:** refactor generic updateOS with date comparison ([91a753c](https://github.com/unraid/api/commit/91a753cd7018b89d53e9cd2d7c429ce53e291336))
|
||||
* **web:** registration component ui / ux ([717d873](https://github.com/unraid/api/commit/717d8733bd4b8c87b6ae6f1cd66717056c5df876))
|
||||
* **web:** registration replace eligibility docs btn ([b69285f](https://github.com/unraid/api/commit/b69285ff8ca5b896082b5f0e1aeba70f9a2c5129))
|
||||
* **web:** registration too many devices messaging ([1c0b5a3](https://github.com/unraid/api/commit/1c0b5a317aadf6173405770878e6038d4d8b448f))
|
||||
* **web:** start prep for new key type support ([5c5035a](https://github.com/unraid/api/commit/5c5035a5446516999729ddc56d1077ee512f14d3))
|
||||
* **web:** update os create flash backup button ([50ba61c](https://github.com/unraid/api/commit/50ba61cf80b7df2d121962cf4ec4b10952e8eecb))
|
||||
* **web:** WIP key expiration ([24618fe](https://github.com/unraid/api/commit/24618fe09db2109c2eb57ab1655ab0fb7d79fc90))
|
||||
* **web:** WIP registration page UI UX ([559e5b8](https://github.com/unraid/api/commit/559e5b8698d5df80ca57f530a2bf2cb6f01e30c7))
|
||||
* **web:** WIP registration page web component ([bd772a9](https://github.com/unraid/api/commit/bd772a9c97d49b57a0b5a0e6a367c9a4e3732086))
|
||||
* **web:** WIP updateOs callback ([2ad55ed](https://github.com/unraid/api/commit/2ad55ed019155e46d8627ea5c1b82cd5e4351127))
|
||||
* WIP first pass at UpdateOs page replacement component ([3a5d871](https://github.com/unraid/api/commit/3a5d871f1fd054720c3693705484072ff567ff28))
|
||||
* WIP UpdateOs page component ([8e4c36d](https://github.com/unraid/api/commit/8e4c36d38ce4e70307f5d14c953d5103c8b7e8e4))
|
||||
|
||||
|
||||
### Bug Fixes
|
||||
|
||||
* 6.10 view release notes js ([254d894](https://github.com/unraid/api/commit/254d894f39e512d1b4a0472180cb27090de256a0))
|
||||
* add missing translation keys ([03b506c](https://github.com/unraid/api/commit/03b506cd4e68f23a85bbfd54205322a6a4f93e5b))
|
||||
* add serverName / description to dashboard payload ([9677aff](https://github.com/unraid/api/commit/9677aff1cd0942f36a2845f3f105601c494efd9e))
|
||||
* allow null for the local entry in the myservers cfg ([01157c8](https://github.com/unraid/api/commit/01157c86ea3838ca675d65528a882cf25d0019a6))
|
||||
* azure and gray theme custom colors ([92e552c](https://github.com/unraid/api/commit/92e552c9c7f7804902f18eb2d71f8483671fe048))
|
||||
* codegen on web run ([e2e67c2](https://github.com/unraid/api/commit/e2e67c21067a138d963f5f10760b84cf6a533542))
|
||||
* combinedKnownOrigins in state.php for UPC ([b550eea](https://github.com/unraid/api/commit/b550eeae7077cbdbd6d004506bdc96d04c04bc4c))
|
||||
* Connect settings myservers config parse ([1c1483a](https://github.com/unraid/api/commit/1c1483a5cc506deab9d858dabbb8388c8b1d1ec1))
|
||||
* dateTime system settings ([56ccbff](https://github.com/unraid/api/commit/56ccbff61fb61ab67277100c525b80adf95e9b72))
|
||||
* **deps:** update dependency graphql to v16.8.1 ([bff1b19](https://github.com/unraid/api/commit/bff1b19706bee1e3103e3a0a1d2fceb3154f9bba))
|
||||
* **deps:** update graphql-tools monorepo (major) ([#693](https://github.com/unraid/api/issues/693)) ([3447eb0](https://github.com/unraid/api/commit/3447eb047a1dcd575b88a96bbcef9946aca366a1))
|
||||
* **deps:** update nest monorepo ([#816](https://github.com/unraid/api/issues/816)) ([4af3699](https://github.com/unraid/api/commit/4af36991b8b376f816ed51fd503a66e99675a3e7))
|
||||
* downgrade remove erroneous file_get_contents ([df9c918](https://github.com/unraid/api/commit/df9c91867cf3f7cf6b424a386d7e68bd510ec20f))
|
||||
* exit with process.exit not process.exitcode ([dcb6def](https://github.com/unraid/api/commit/dcb6def1cf3365dca819feed101160c8ad0125dc))
|
||||
* graphQL CORS error detection ([e5ea67f](https://github.com/unraid/api/commit/e5ea67fe5224fd5aaf06e1e63e7efc01974a10ac))
|
||||
* header version thirdPartyDriversDownloading pill ([c2ff31c](https://github.com/unraid/api/commit/c2ff31c672bc30683062c6cefbd5e744a7a2a676))
|
||||
* lint unused param var prefixed ([8d103a9](https://github.com/unraid/api/commit/8d103a9ca89139d7b4f513318a67bcc64c0daa0c))
|
||||
* local container startup commands cleaned up ([6c0ccb2](https://github.com/unraid/api/commit/6c0ccb2b24f98282be4db2e0b2e6362f4a187def))
|
||||
* logrotate not working due to invalid ownership of unraid-api folder ([ec0581a](https://github.com/unraid/api/commit/ec0581abf58a217f698d52d5337f2b312e5a645b))
|
||||
* missing translation ([81a9380](https://github.com/unraid/api/commit/81a93802993e7d95fb587cbfe3b598136a89348b))
|
||||
* optional check on api.version to allow fallback to save value ([0ac4455](https://github.com/unraid/api/commit/0ac4455f78407eca7aa1d6ee360830067a1c5c3e))
|
||||
* patch ShowChanges.php in 6.10 ([92d09c2](https://github.com/unraid/api/commit/92d09c2846c1bf64276e140c4cf4635e8bbfa94b))
|
||||
* plg installer header version replacement ([7d0de2c](https://github.com/unraid/api/commit/7d0de2c8b3dc3c2d3c204e7846cf65d6df07545f))
|
||||
* plg remove reboot-details path ([d54d90e](https://github.com/unraid/api/commit/d54d90ec04c67ee532cbcb77c4c5890545899e5a))
|
||||
* **plg:** Downgrade & Update page file locations ([3fbb6b7](https://github.com/unraid/api/commit/3fbb6b70c1152d0691f3d74298908338e19cda53))
|
||||
* **plg:** third party reboot detection ([f0ee640](https://github.com/unraid/api/commit/f0ee640767e446a829fd2e60033560786e5f63b0))
|
||||
* plugin install should suppress output from `unraid-api stop` ([#757](https://github.com/unraid/api/issues/757)) ([3da5d95](https://github.com/unraid/api/commit/3da5d9573b499c84c25e33b26a2014e79bef40f7))
|
||||
* rearrange exit hook to try to fix closing ([843d3f4](https://github.com/unraid/api/commit/843d3f41162c5dbcfd7803912b1879d7a182231a))
|
||||
* refreshServerState check regExp ([7fca971](https://github.com/unraid/api/commit/7fca971cab40b6e5493e7e21baf85e3d6ba66b90))
|
||||
* remove var_dump Connect settings ([9425f8b](https://github.com/unraid/api/commit/9425f8b133d44ac759d09158eadd13c81e7796fb))
|
||||
* renew callback messaging in modal ([e98d065](https://github.com/unraid/api/commit/e98d0654237b111cf912eb5014dbcc5da0e92ca3))
|
||||
* replaceRenew response cache use & purge ([ca85199](https://github.com/unraid/api/commit/ca851991ecb09720d70135d302aa93ad10a96d3a))
|
||||
* set sha in test step as well. ([8af3367](https://github.com/unraid/api/commit/8af3367226f9a3bc51db65ffe5dd53d6c5aa0017))
|
||||
* state php version checking ([494f5e9](https://github.com/unraid/api/commit/494f5e9935bc207b81098e84a0fe3e259939cf39))
|
||||
* stop using username to determine reg status ([c5a6cd7](https://github.com/unraid/api/commit/c5a6cd7bf930d8bc94ccae45f5363c12fd1fccfc))
|
||||
* ThirdPartyDriver messaging on Update page ([f23ad76](https://github.com/unraid/api/commit/f23ad762c04c3da918429a376146fe096a5030d5))
|
||||
* try to set environment in docker build ([caece63](https://github.com/unraid/api/commit/caece63e7f180f94a7ee6b962c905296c6b987bb))
|
||||
* uninstall reboot-details include ([3849462](https://github.com/unraid/api/commit/3849462f572659a43157a49511075f2d8cd5dd4c))
|
||||
* unraid-api server state refresh after key extension use regExp ([490595f](https://github.com/unraid/api/commit/490595f9b420054e6c2fe40d868b902b262718af))
|
||||
* updateOs auth group usage ([52b1ad9](https://github.com/unraid/api/commit/52b1ad9a7d3c9cdc989dd729d7828b0678349c27))
|
||||
* updateOs type check ([ba230e2](https://github.com/unraid/api/commit/ba230e2643399fbfa1612059f235ccdf61f7f486))
|
||||
* web component translations class ([6c81f6f](https://github.com/unraid/api/commit/6c81f6f70dcbe4f055a0041863fe275d6e01d6b9))
|
||||
* **web:** azure & gray theme header font colors ([8a5c7c9](https://github.com/unraid/api/commit/8a5c7c9304a063b26d7ff2df5c174aa9f1c0f53c))
|
||||
* **web:** card wrapper error border styles ([c71f420](https://github.com/unraid/api/commit/c71f420a4c9f7325127e3f38157dbc6255b3e139))
|
||||
* **web:** connect graph error handling ([c239937](https://github.com/unraid/api/commit/c239937c407cfea0defde1994809a5c0a196cca2))
|
||||
* **web:** default time format include am/pm ([31694cd](https://github.com/unraid/api/commit/31694cd7141e2ec0b0c3b4e4480d34d19c80adae))
|
||||
* **web:** downgrade status pill for no downgrade available ([9d9ebb1](https://github.com/unraid/api/commit/9d9ebb1c6efd486a90dcd78ba63766e24be26d55))
|
||||
* **web:** downgrade-not-available when downgrade initiated ([d060359](https://github.com/unraid/api/commit/d0603592596a3173889e9d06d57cfaa602eb80bb))
|
||||
* **web:** installPlugin composable for os updates ([9fb024a](https://github.com/unraid/api/commit/9fb024a68d65905e5351cfa71ca64cdffa0fa74c))
|
||||
* **web:** lint fixes ([224d637](https://github.com/unraid/api/commit/224d63773d505b8d65c9455fb94260ae617d9fe5))
|
||||
* **web:** localStorage craftUrl for dev ([2e108da](https://github.com/unraid/api/commit/2e108da0db7de01d03ee3b0657a614355a61b208))
|
||||
* **web:** missing translation ([74a8f27](https://github.com/unraid/api/commit/74a8f27643d7ba9c9d5dcd6a43b189a936dae648))
|
||||
* **web:** missing translation for update ([cb46a94](https://github.com/unraid/api/commit/cb46a94c7238bf381fbfc48109b1dd648d2e4949))
|
||||
* **web:** missing translations ([8ea733b](https://github.com/unraid/api/commit/8ea733b295a5f3bd922e867f544e5538873a5088))
|
||||
* **web:** missing translations ([d2eed92](https://github.com/unraid/api/commit/d2eed9291de9297aa0d556f06b9b8f5f09734250))
|
||||
* **web:** no plugin, don't show restart api button ([e628a8b](https://github.com/unraid/api/commit/e628a8b64fab4d1a5ce84af62abde3cd4c53ba96))
|
||||
* **web:** preview and test releases usage ([4b8cfb4](https://github.com/unraid/api/commit/4b8cfb464e8296ce20d6ff3870949d739a86ca1b))
|
||||
* **web:** reboot required disable update check link ([f029652](https://github.com/unraid/api/commit/f0296528bae52227ecbe281786ddf4d3a0cc940f))
|
||||
* **web:** reg component conditional keyActions ([730dff2](https://github.com/unraid/api/commit/730dff2e6344f7ee076e1c67d82ef0783a5931b2))
|
||||
* **web:** Registration key actions ([f7b1016](https://github.com/unraid/api/commit/f7b1016980c3f576b007a1d01184bf35f0eef311))
|
||||
* **web:** regTy on account payload ([64b0b5e](https://github.com/unraid/api/commit/64b0b5eb5767d41012f6bcb9536030ec39e45af9))
|
||||
* **web:** regUpdatesExpired use .isAfter ([5d67adf](https://github.com/unraid/api/commit/5d67adf4625a108e3374eb72714cdc1747b2a9c5))
|
||||
* **web:** replace check request error handling ([c1491fe](https://github.com/unraid/api/commit/c1491fecdc327d78f8de7c0f04fda481fb47cb56))
|
||||
* **web:** replaceCheck type ([1bd9729](https://github.com/unraid/api/commit/1bd9729b0197b49ca460912bbc56cd3b206d00dc))
|
||||
* **web:** replaceCheck type ([8cc6020](https://github.com/unraid/api/commit/8cc602019a2c8a718b59590d166644a1cb4d16cc))
|
||||
* **web:** state $_SESSION usage ([412392d](https://github.com/unraid/api/commit/412392dc1c5e612199e76ee7e1cae03705957e3d))
|
||||
* **web:** state php warnings ([1460cab](https://github.com/unraid/api/commit/1460cabe6b041f9f9fb89ca474a7d7e872d31c39))
|
||||
* **web:** translation ([cc85a49](https://github.com/unraid/api/commit/cc85a4903178999dbb80da50aa3b02ff38012172))
|
||||
* **web:** type errors ([e6c57eb](https://github.com/unraid/api/commit/e6c57eb910a1c1f948a3104c4e7fc04ac8b2d327))
|
||||
* **web:** upc dropdown updates external icon ([13936bb](https://github.com/unraid/api/commit/13936bb157f9097a19c7498fce252f3f86526ccb))
|
||||
* **web:** update CallbackButton import ([eabfeca](https://github.com/unraid/api/commit/eabfeca618d3bf682a331c6d9e1f17b5facdcdca))
|
||||
* **web:** Update OS auto redirect loop with account ([9b56fc3](https://github.com/unraid/api/commit/9b56fc3883f51942de9b1c8d1d1f30595fee7fa5))
|
||||
* **web:** updateOs lint ([bd9e9d5](https://github.com/unraid/api/commit/bd9e9d55cc7bba432f65d78feee83526dbfff059))
|
||||
* **web:** use dateTime format from server ([7090f38](https://github.com/unraid/api/commit/7090f38a9ab8b2d1dfce4095f4e2669d4d78a3e1))
|
||||
|
||||
### [3.2.3](https://github.com/unraid/api/compare/v3.2.2...v3.2.3) (2023-09-08)
|
||||
|
||||
|
||||
@@ -3168,4 +3621,4 @@ All notable changes to this project will be documented in this file. See [standa
|
||||
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
###########################################################
|
||||
# Development/Build Image
|
||||
###########################################################
|
||||
FROM node:18.17.1-bookworm-slim As development
|
||||
FROM node:18.19.1-bookworm-slim As development
|
||||
|
||||
# Install build tools and dependencies
|
||||
RUN apt-get update -y && apt-get install -y \
|
||||
@@ -15,8 +15,6 @@ RUN apt-get update -y && apt-get install -y \
|
||||
git \
|
||||
build-essential
|
||||
|
||||
RUN mkdir /var/log/unraid-api/
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
# Set app env
|
||||
|
||||
58
api/README.md
Normal file
58
api/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# @unraid/api
|
||||
|
||||
## Installation
|
||||
|
||||
Install the production plugin via the apps tab (search for "my servers") on Unraid 6.9.2 or later.
|
||||
|
||||
## CLI
|
||||
|
||||
If you're on a unraid v6.9.2 or later machine this should be available by running `unraid-api` in any directory.
|
||||
|
||||
```bash
|
||||
root@Devon:~# unraid-api
|
||||
|
||||
Unraid API
|
||||
|
||||
Thanks for using the official Unraid API
|
||||
|
||||
Usage:
|
||||
|
||||
$ unraid-api command <options>
|
||||
|
||||
Commands:
|
||||
|
||||
start/stop/restart/version/status/report/switch-env
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help Prints this usage guide.
|
||||
-d, --debug Enabled debug mode.
|
||||
-p, --port string Set the graphql port.
|
||||
--environment production/staging/development Set the working environment.
|
||||
--log-level ALL/TRACE/DEBUG/INFO/WARN/ERROR/FATAL/MARK/OFF Set the log level.
|
||||
|
||||
Copyright © 2022 Lime Technology, Inc.
|
||||
|
||||
```
|
||||
|
||||
## Report
|
||||
To view the current status of the unraid-api and its connection to mothership, run:
|
||||
```
|
||||
unraid-api report
|
||||
```
|
||||
|
||||
To view verbose data (anonymized), run:
|
||||
```
|
||||
unraid-api report -v
|
||||
```
|
||||
|
||||
To view non-anonymized verbose data, run:
|
||||
```
|
||||
unraid-api report -vv
|
||||
```
|
||||
|
||||
## Secrets
|
||||
If you found this file you're likely a developer. If you'd like to know more about the API and when it's available please join [our discord](https://discord.unraid.net/).
|
||||
|
||||
## License
|
||||
Copyright 2019-2022 Lime Technology Inc. All rights reserved.
|
||||
@@ -1,6 +1,6 @@
|
||||
[api]
|
||||
version="3.2.3+075d7f25"
|
||||
extraOrigins=""
|
||||
version="3.8.1+d06e215a"
|
||||
extraOrigins="https://google.com,https://test.com"
|
||||
[local]
|
||||
[notifier]
|
||||
apikey="unnotify_30994bfaccf839c65bae75f7fa12dd5ee16e69389f754c3b98ed7d5"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
[api]
|
||||
version="3.2.3+075d7f25"
|
||||
extraOrigins=""
|
||||
version="3.8.1+d06e215a"
|
||||
extraOrigins="https://google.com,https://test.com"
|
||||
[local]
|
||||
[notifier]
|
||||
apikey="unnotify_30994bfaccf839c65bae75f7fa12dd5ee16e69389f754c3b98ed7d5"
|
||||
[remote]
|
||||
wanaccess="yes"
|
||||
wanaccess="no"
|
||||
wanport="8443"
|
||||
upnpEnabled="no"
|
||||
apikey="_______________________BIG_API_KEY_HERE_________________________"
|
||||
@@ -16,9 +16,9 @@ regWizTime="1611175408732_0951-1653-3509-FBA155FA23C0"
|
||||
idtoken=""
|
||||
accesstoken=""
|
||||
refreshtoken=""
|
||||
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://connect.myunraid.net, https://staging.connect.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
|
||||
dynamicRemoteAccessType="DISABLED"
|
||||
allowedOrigins="/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, http://localhost:8080, https://localhost:4443, https://tower.local:4443, https://192.168.1.150:4443, https://tower:4443, https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443, https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443, https://10-252-0-1.hash.myunraid.net:4443, https://10-252-1-1.hash.myunraid.net:4443, https://10-253-3-1.hash.myunraid.net:4443, https://10-253-4-1.hash.myunraid.net:4443, https://10-253-5-1.hash.myunraid.net:4443, https://10-100-0-1.hash.myunraid.net:4443, https://10-100-0-2.hash.myunraid.net:4443, https://10-123-1-2.hash.myunraid.net:4443, https://221-123-121-112.hash.myunraid.net:4443, https://google.com, https://test.com, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000, https://studio.apollographql.com"
|
||||
dynamicRemoteAccessType="STATIC"
|
||||
[upc]
|
||||
apikey="unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810"
|
||||
[connectionStatus]
|
||||
minigraph="PRE_INIT"
|
||||
minigraph="CONNECTED"
|
||||
|
||||
26
api/dev/states/nginx.ini
Normal file
26
api/dev/states/nginx.ini
Normal file
@@ -0,0 +1,26 @@
|
||||
NGINX_LANIP="192.168.1.150"
|
||||
NGINX_LANIP6=""
|
||||
NGINX_LANNAME="Tower"
|
||||
NGINX_LANMDNS="Tower.local"
|
||||
NGINX_CERTPATH="/boot/config/ssl/certs/certificate_bundle.pem"
|
||||
NGINX_USESSL="yes"
|
||||
NGINX_PORT="8080"
|
||||
NGINX_PORTSSL="4443"
|
||||
NGINX_DEFAULTURL="https://Tower.local:4443"
|
||||
NGINX_CERTNAME="*.thisisfourtyrandomcharacters012345678900.myunraid.net"
|
||||
NGINX_LANFQDN="192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net"
|
||||
NGINX_LANFQDN6=""
|
||||
NGINX_WANACCESS=""
|
||||
NGINX_WANIP=""
|
||||
NGINX_WANIP6=""
|
||||
NGINX_WANFQDN="85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net"
|
||||
NGINX_WANFQDN6=""
|
||||
NGINX_WG0FQDN="10-252-0-1.hash.myunraid.net"
|
||||
NGINX_WG1FQDN="10-252-1-1.hash.myunraid.net"
|
||||
NGINX_WG3FQDN="10-253-3-1.hash.myunraid.net"
|
||||
NGINX_WG4FQDN="10-253-4-1.hash.myunraid.net"
|
||||
NGINX_WG55FQDN="10-253-5-1.hash.myunraid.net"
|
||||
NGINX_TAILSCALEFQDN="10-100-0-1.hash.myunraid.net"
|
||||
NGINX_TAILSCALE0FQDN="10-100-0-2.hash.myunraid.net"
|
||||
NGINX_CUSTOMFQDN="10-123-1-2.hash.myunraid.net"
|
||||
NGINX_CUSTOMFQDN6="221-123-121-112.hash.myunraid.net"
|
||||
143
api/dev/states/var.ini
Normal file
143
api/dev/states/var.ini
Normal file
@@ -0,0 +1,143 @@
|
||||
version="6.11.2"
|
||||
MAX_ARRAYSZ="30"
|
||||
MAX_CACHESZ="30"
|
||||
NAME="Tower"
|
||||
timeZone="Australia/Adelaide"
|
||||
COMMENT="Dev Server"
|
||||
SECURITY="user"
|
||||
WORKGROUP="WORKGROUP"
|
||||
DOMAIN=""
|
||||
DOMAIN_SHORT=""
|
||||
hideDotFiles="no"
|
||||
localMaster="yes"
|
||||
enableFruit="no"
|
||||
USE_NETBIOS="yes"
|
||||
USE_WSD="no"
|
||||
WSD_OPT=""
|
||||
USE_NTP="yes"
|
||||
NTP_SERVER1="time1.google.com"
|
||||
NTP_SERVER2="time2.google.com"
|
||||
NTP_SERVER3="time3.google.com"
|
||||
NTP_SERVER4="time4.google.com"
|
||||
DOMAIN_LOGIN="Administrator"
|
||||
SYS_MODEL="Dell R710"
|
||||
SYS_ARRAY_SLOTS="24"
|
||||
SYS_FLASH_SLOTS="1"
|
||||
USE_SSL="auto"
|
||||
PORT="80"
|
||||
PORTSSL="443"
|
||||
LOCAL_TLD="local"
|
||||
BIND_MGT="no"
|
||||
USE_TELNET="yes"
|
||||
PORTTELNET="23"
|
||||
USE_SSH="yes"
|
||||
PORTSSH="22"
|
||||
USE_UPNP="yes"
|
||||
START_PAGE="Main"
|
||||
startArray="no"
|
||||
spindownDelay="0"
|
||||
spinupGroups="no"
|
||||
defaultFsType="xfs"
|
||||
shutdownTimeout="90"
|
||||
luksKeyfile="/tmp/unraid/keyfile"
|
||||
poll_attributes="1800"
|
||||
poll_attributes_default="1800"
|
||||
poll_attributes_status="default"
|
||||
queueDepth="auto"
|
||||
nr_requests="Auto"
|
||||
nr_requests_default="Auto"
|
||||
nr_requests_status="default"
|
||||
md_scheduler="auto"
|
||||
md_scheduler_default="auto"
|
||||
md_scheduler_status="default"
|
||||
md_num_stripes="1280"
|
||||
md_num_stripes_default="1280"
|
||||
md_num_stripes_status="default"
|
||||
md_queue_limit="80"
|
||||
md_queue_limit_default="80"
|
||||
md_queue_limit_status="default"
|
||||
md_sync_limit="5"
|
||||
md_sync_limit_default="5"
|
||||
md_sync_limit_status="default"
|
||||
md_write_method="auto"
|
||||
md_write_method_default="auto"
|
||||
md_write_method_status="default"
|
||||
shareDisk="yes"
|
||||
shareUser="e"
|
||||
shareUserInclude=""
|
||||
shareUserExclude=""
|
||||
shareSMBEnabled="yes"
|
||||
shareNFSEnabled="no"
|
||||
shareInitialOwner="Administrator"
|
||||
shareInitialGroup="Domain Users"
|
||||
shareCacheEnabled="yes"
|
||||
shareCacheFloor="2000000"
|
||||
shareMoverSchedule="40 3 * * *"
|
||||
shareMoverLogging="no"
|
||||
fuse_remember="330"
|
||||
fuse_remember_default="330"
|
||||
fuse_remember_status="default"
|
||||
fuse_directio="auto"
|
||||
fuse_directio_default="auto"
|
||||
fuse_directio_status="default"
|
||||
fuse_useino="yes"
|
||||
shareAvahiEnabled="yes"
|
||||
shareAvahiSMBName="%h"
|
||||
shareAvahiSMBModel="Xserve"
|
||||
shfs_logging="1"
|
||||
safeMode="no"
|
||||
startMode="Normal"
|
||||
configValid="yes"
|
||||
joinStatus="Not joined"
|
||||
deviceCount="4"
|
||||
flashGUID="0000-0000-0000-000000000000"
|
||||
flashProduct="DataTraveler_3.0"
|
||||
flashVendor="KINGSTON"
|
||||
regCheck=""
|
||||
regFILE="/app/dev/Unraid.net/Pro.key"
|
||||
regGUID="13FE-4200-C300-58C372A52B19"
|
||||
regTy="Pro"
|
||||
regTo="Eli Bosley"
|
||||
regTm="1833409182"
|
||||
regTm2="0"
|
||||
regExp=""
|
||||
regGen="0"
|
||||
sbName="/boot/config/super.dat"
|
||||
sbVersion="2.9.13"
|
||||
sbUpdated="1596079143"
|
||||
sbEvents="173"
|
||||
sbState="1"
|
||||
sbClean="yes"
|
||||
sbSynced="1586819259"
|
||||
sbSyncErrs="0"
|
||||
sbSynced2="1586822456"
|
||||
sbSyncExit="0"
|
||||
sbNumDisks="5"
|
||||
mdColor="green-blink"
|
||||
mdNumDisks="4"
|
||||
mdNumDisabled="1"
|
||||
mdNumInvalid="1"
|
||||
mdNumMissing="0"
|
||||
mdNumNew="0"
|
||||
mdNumErased="0"
|
||||
mdResync="0"
|
||||
mdResyncCorr="0"
|
||||
mdResyncPos="0"
|
||||
mdResyncDb="0"
|
||||
mdResyncDt="0"
|
||||
mdResyncAction="check P"
|
||||
mdResyncSize="438960096"
|
||||
mdState="STOPPED"
|
||||
mdVersion="2.9.14"
|
||||
fsState="Stopped"
|
||||
fsProgress="Autostart disabled"
|
||||
fsCopyPrcnt="0"
|
||||
fsNumMounted="0"
|
||||
fsNumUnmountable="0"
|
||||
fsUnmountableMask=""
|
||||
shareCount="0"
|
||||
shareSMBCount="1"
|
||||
shareNFSCount="0"
|
||||
shareMoverActive="no"
|
||||
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"
|
||||
csrf_token="0000000000000000"
|
||||
@@ -24,14 +24,9 @@ x-volumes: &volumes
|
||||
- ./fix-array-type.cjs:/app/fix-array-type.cjs
|
||||
- /var/run/docker.sock:/var/run/docker.sock
|
||||
|
||||
|
||||
networks:
|
||||
mothership_default:
|
||||
services:
|
||||
|
||||
dev:
|
||||
networks:
|
||||
- mothership_default
|
||||
image: unraid-api:development
|
||||
ports:
|
||||
- "3001:3001"
|
||||
@@ -50,6 +45,22 @@ services:
|
||||
profiles:
|
||||
- builder
|
||||
|
||||
local:
|
||||
image: unraid-api:development
|
||||
ports:
|
||||
- "3001:3001"
|
||||
build:
|
||||
context: .
|
||||
target: development
|
||||
dockerfile: Dockerfile
|
||||
<<: *volumes
|
||||
command: npm run start:dev
|
||||
environment:
|
||||
- IS_DOCKER=true
|
||||
- GIT_SHA=${GIT_SHA:?err}
|
||||
- IS_TAGGED=${IS_TAGGED}
|
||||
profiles:
|
||||
- builder
|
||||
|
||||
builder:
|
||||
image: unraid-api:builder
|
||||
|
||||
229
api/docs/development.md
Normal file
229
api/docs/development.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Development
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [production](https://unraid-dl.sfo2.digitaloceanspaces.com/unraid-api/dynamix.unraid.net.plg) or [staging](https://unraid-dl.sfo2.digitaloceanspaces.com/unraid-api/dynamix.unraid.net.staging.plg) plugin on Unraid 6.9.0-rc1 or later (6.9.2 or higher recommended).
|
||||
|
||||
## Connecting to the API
|
||||
|
||||
### HTTP
|
||||
|
||||
This can be accessed by default via `http://tower.local/graphql`.
|
||||
|
||||
See <https://graphql.org/learn/serving-over-http/#http-methods-headers-and-body>
|
||||
|
||||
### WS
|
||||
|
||||
If you're using the ApolloClient please see <https://github.com/apollographql/subscriptions-transport-ws#full-websocket-transport> otherwise see <https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Building in Docker
|
||||
|
||||
To get a development environment for testing start by running this docker command:
|
||||
|
||||
``docker compose run build-interactive``
|
||||
|
||||
which will give you an interactive shell inside of the newly build linux container.
|
||||
|
||||
To automatically build the plugin run the command below:
|
||||
|
||||
``docker compose run builder``
|
||||
|
||||
The builder command will build the plugin into deploy/release, and the interactive plugin lets you build the plugin or install node modules how you like.
|
||||
|
||||
## Logs
|
||||
|
||||
Logging can be configured via environment variables.
|
||||
|
||||
Log levels can be set when the api starts via `LOG_LEVEL=all/trace/debug/info/warn/error/fatal/mark/off`.
|
||||
|
||||
Additional detail for the log entry can be added with `LOG_CONTEXT=true` (warning, generates a lot of data).
|
||||
|
||||
By default, logs will be sent to syslog. Or you can set `LOG_TRANSPORT=file` to have logs saved in `/var/log/unraid-api/stdout.log`. Or enable debug mode to view logs inline.
|
||||
|
||||
Examples:
|
||||
|
||||
* `unraid-api start`
|
||||
* `LOG_LEVEL=debug unraid-api start --debug`
|
||||
* `LOG_LEVEL=trace LOG_CONTEXT=true LOG_TRANSPORT=file unraid-api start`
|
||||
|
||||
Log levels can be increased without restarting the api by issuing this command:
|
||||
|
||||
```
|
||||
kill -s SIGUSR2 `pidof unraid-api`
|
||||
```
|
||||
|
||||
and decreased via:
|
||||
|
||||
```
|
||||
kill -s SIGUSR1 `pidof unraid-api`
|
||||
```
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Viewing data sent to mothership
|
||||
|
||||
If the environment variable `LOG_MOTHERSHIP_MESSAGES=true` exists, any data the unraid-api sends to mothership will be saved in clear text here: `/var/log/unraid-api/relay-messages.log`
|
||||
|
||||
Examples:
|
||||
|
||||
* `LOG_MOTHERSHIP_MESSAGES=true unraid-api start`
|
||||
* `LOG_MOTHERSHIP_MESSAGES=true LOG_LEVEL=debug unraid-api start --debug`
|
||||
<br>
|
||||
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Debug mode
|
||||
|
||||
Debug mode can be enabled with the `-d` or `--debug` flag.
|
||||
This will enable the graphql playground and prevent the application starting as a daemon. Logs will be shown inline rather than saved to a file.
|
||||
|
||||
Examples:
|
||||
|
||||
* `unraid-api start --debug`
|
||||
* `LOG_LEVEL=debug unraid-api start --debug`
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Crash API On Demand
|
||||
|
||||
The `PLEASE_SEGFAULT_FOR_ME` env var can be to used to make the api crash after 30 seconds:
|
||||
|
||||
Examples:
|
||||
|
||||
* `PLEASE_SEGFAULT_FOR_ME=true LOG_LEVEL=debug unraid-api start --debug`
|
||||
* `PLEASE_SEGFAULT_FOR_ME=true unraid-api start`
|
||||
|
||||
The crash log will be stored here:
|
||||
|
||||
* `/var/log/unraid-api/crash.log`
|
||||
* `/var/log/unraid-api/crash.json`
|
||||
|
||||
`crash.json` just includes the most recent crash, while the reports get appended to `crash.log`.
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Switching between staging and production environments
|
||||
|
||||
1. Stop the api: `unraid-api stop`
|
||||
2. Switch environments: `unraid-api switch-env`
|
||||
3. Start the api: `unraid-api start`
|
||||
4. Confirm the environment: `unraid-api report`
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Playground
|
||||
|
||||
The playground can be access via `http://tower.local/graphql` while in debug mode.
|
||||
To get your API key open a terminal on your server and run `cat /boot/config/plugins/dynamix.my.servers/myservers.cfg | grep apikey=\"unraid | cut -d '"' -f2`. Add that API key in the "HTTP headers" panel of the playground.
|
||||
|
||||
```json
|
||||
{
|
||||
"x-api-key":"__REPLACE_ME_WITH_API_KEY__"
|
||||
}
|
||||
```
|
||||
|
||||
Next add the query you want to run and hit the play icon.
|
||||
|
||||
```gql
|
||||
query welcome {
|
||||
welcome {
|
||||
message
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should get something like this back.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"welcome": {
|
||||
"message": "Welcome root to this Unraid 6.10.0 server"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Click the "Schema" and "Docs" button on the right side of the playground to learn more.
|
||||
|
||||
For exploring the schema visually I'd suggest using [Voyager](https://apis.guru/graphql-voyager/) (click Change Schema -> Introspection, then copy/paste the introspection query into the local Graph Playground, and copy/paste the results back into Voyager).
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Running this locally
|
||||
|
||||
```bash
|
||||
MOTHERSHIP_RELAY_WS_LINK=ws://localhost:8000 \ # Switch to local copy of mothership
|
||||
PATHS_UNRAID_DATA=$(pwd)/dev/data \ # Where we store plugin data (e.g. permissions.json)
|
||||
PATHS_STATES=$(pwd)/dev/states \ # Where .ini files live (e.g. vars.ini)
|
||||
PATHS_DYNAMIX_BASE=$(pwd)/dev/dynamix \ # Dynamix's data directory
|
||||
PATHS_DYNAMIX_CONFIG=$(pwd)/dev/dynamix/dynamix.cfg \ # Dynamix's config file
|
||||
PATHS_MY_SERVERS_CONFIG=$(pwd)/dev/Unraid.net/myservers.cfg \ # My servers config file
|
||||
PORT=8500 \ # What port unraid-api should start on (e.g. /var/run/unraid-api.sock or 8000)
|
||||
node dist/cli.js --debug # Enable debug logging
|
||||
```
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Create a new release
|
||||
|
||||
To create a new version run `npm run release` and then run **ONLY** the `git push` section of the commands it returns.
|
||||
To create a new prerelease run `npm run release -- --prerelease alpha`.
|
||||
|
||||
Pushing to this repo will cause an automatic "rolling" release to be built which can be accessed via the page for the associated Github action run.
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Using a custom version (e.g. testing a new release)
|
||||
|
||||
1. Install the staging or production plugin (links in the Installation section at the top of this file)
|
||||
2. Download or build the api tgz file you want
|
||||
|
||||
* Download from [the releases page](https://github.com/unraid/api/releases)
|
||||
* Build it on your local machine (``docker compose run builder``) and copy from the `deploy/release` folder
|
||||
|
||||
3. Copy the file to `/boot/config/plugins/dynamix.my.servers/unraid-api.tgz`.
|
||||
4. Install the new api: `/etc/rc.d/rc.unraid-api (install / _install)`
|
||||
|
||||
* `_install` will no start the plugin for you after running, so you can make sure you launch in dev mode
|
||||
* `install` will start the plugin after install
|
||||
5. Start the api: `unraid-api start`
|
||||
6. Confirm the version: `unraid-api report`
|
||||
|
||||
## Cloning Secrets from AWS
|
||||
|
||||
1. Go here to create security credentials for your user [S3 Creds](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1&skipRegion=true#/security_credentials)
|
||||
2. Export your AWS secrets OR run `aws configure` to setup your environment
|
||||
|
||||
```sh
|
||||
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
||||
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
||||
export AWS_DEFAULT_REGION=us-east-1
|
||||
|
||||
```
|
||||
|
||||
3. Set variables for staging and production to the ARN of the secret you would like to clone:
|
||||
|
||||
* `STAGING_SECRET_ID`
|
||||
* `PRODUCTION_SECRET_ID`
|
||||
|
||||
4. Run `scripts/copy-env-from-aws.sh` to pull down the secrets into their respective files
|
||||
11490
api/package-lock.json
generated
11490
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
197
api/package.json
197
api/package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/api",
|
||||
"version": "3.2.3",
|
||||
"version": "3.11.0",
|
||||
"main": "dist/index.js",
|
||||
"bin": "dist/unraid-api.cjs",
|
||||
"type": "module",
|
||||
@@ -26,16 +26,16 @@
|
||||
"compile": "tsup --config ./tsup.config.ts",
|
||||
"bundle": "pkg . --public",
|
||||
"build": "npm run compile && npm run bundle",
|
||||
"build:docker": "GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker-compose run --rm builder",
|
||||
"build:docker": "./scripts/dc.sh run --rm builder",
|
||||
"build-pkg": "./scripts/build.mjs",
|
||||
"codegen": "MOTHERSHIP_GRAPHQL_LINK='https://staging.mothership.unraid.net/ws' graphql-codegen --config codegen.yml -r dotenv/config './.env.staging'",
|
||||
"codegen:watch": "DOTENV_CONFIG_PATH='./.env.staging' graphql-codegen-esm --config codegen.yml --watch -r dotenv/config",
|
||||
"codegen:watch": "DOTENV_CONFIG_PATH='./.env.staging' graphql-codegen --config codegen.yml --watch -r dotenv/config",
|
||||
"codegen:local": "NODE_TLS_REJECT_UNAUTHORIZED=0 MOTHERSHIP_GRAPHQL_LINK='https://mothership.localhost/ws' graphql-codegen-esm --config codegen.yml --watch",
|
||||
"tsc": "tsc --noEmit",
|
||||
"lint": "DEBUG=eslint:cli-engine eslint . --config .eslintrc.cjs",
|
||||
"lint:fix": "DEBUG=eslint:cli-engine eslint . --fix --config .eslintrc.cjs",
|
||||
"test:watch": "vitest --segfault-retry=3 --no-threads",
|
||||
"test": "vitest run --segfault-retry=3 --no-threads",
|
||||
"test:watch": "vitest --segfault-retry=3 --pool=forks",
|
||||
"test": "vitest run --segfault-retry=3 --pool=forks",
|
||||
"coverage": "vitest run --segfault-retry=3 --coverage",
|
||||
"patch:subscriptions-transport-ws": "node ./.scripts/patches/subscriptions-transport-ws.cjs",
|
||||
"release": "standard-version",
|
||||
@@ -45,12 +45,13 @@
|
||||
"start:plugin-verbose": "LOG_CONTEXT=true LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty LOG_LEVEL=trace unraid-api start --debug",
|
||||
"start:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development tsup --config ./tsup.config.ts --watch --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs start --debug'",
|
||||
"restart:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development tsup --config ./tsup.config.ts --watch --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs restart --debug'",
|
||||
"stop:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development tsup --config ./tsup.config.ts --watch --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs stop --debug'",
|
||||
"stop:dev": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development tsup --config ./tsup.config.ts --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs stop --debug'",
|
||||
"start:report": "LOG_MOTHERSHIP_MESSAGES=true LOG_TYPE=pretty NODE_ENV=development LOG_LEVEL=trace NODE_ENV=development LOG_CONTEXT=true tsup --config ./tsup.config.ts --watch --onSuccess 'DOTENV_CONFIG_PATH=./.env.development node -r dotenv/config dist/unraid-api.cjs report --debug'",
|
||||
"start:docker": "docker compose run --rm builder-interactive",
|
||||
"build:dev": "GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker-compose build dev",
|
||||
"docker:dev": "GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker-compose run --rm --service-ports dev",
|
||||
"docker:test": "GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker-compose run --rm builder npm run test"
|
||||
"build:dev": "./scripts/dc.sh build dev",
|
||||
"start:local": "./scripts/dc.sh run --rm --service-ports local",
|
||||
"start:ddev": "./scripts/dc.sh run --rm --service-ports dev",
|
||||
"start:dtest": "./scripts/dc.sh run --rm builder npm run test",
|
||||
"enter:ddev": "./scripts/dc.sh exec dev /bin/sh"
|
||||
},
|
||||
"files": [
|
||||
".env.staging",
|
||||
@@ -59,20 +60,21 @@
|
||||
"unraid-api"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.7.12",
|
||||
"@apollo/server": "^4.6.0",
|
||||
"@apollo/client": "^3.10.4",
|
||||
"@apollo/server": "^4.10.4",
|
||||
"@as-integrations/fastify": "^2.1.1",
|
||||
"@graphql-codegen/client-preset": "^4.0.0",
|
||||
"@graphql-tools/load-files": "^6.6.1",
|
||||
"@graphql-tools/merge": "^8.4.0",
|
||||
"@graphql-tools/schema": "^9.0.17",
|
||||
"@graphql-tools/utils": "^9.2.1",
|
||||
"@nestjs/apollo": "^12.0.11",
|
||||
"@nestjs/core": "^10.2.9",
|
||||
"@nestjs/graphql": "^12.0.11",
|
||||
"@nestjs/passport": "^10.0.2",
|
||||
"@nestjs/platform-fastify": "^10.2.9",
|
||||
"@reduxjs/toolkit": "^1.9.5",
|
||||
"@graphql-codegen/client-preset": "^4.2.5",
|
||||
"@graphql-tools/load-files": "^7.0.0",
|
||||
"@graphql-tools/merge": "^9.0.4",
|
||||
"@graphql-tools/schema": "^10.0.3",
|
||||
"@graphql-tools/utils": "^10.2.0",
|
||||
"@nestjs/apollo": "^12.1.0",
|
||||
"@nestjs/core": "^10.3.8",
|
||||
"@nestjs/graphql": "^12.1.1",
|
||||
"@nestjs/passport": "^10.0.3",
|
||||
"@nestjs/platform-fastify": "^10.3.8",
|
||||
"@nestjs/schedule": "^4.0.2",
|
||||
"@reduxjs/toolkit": "^2.2.4",
|
||||
"@reflet/cron": "^1.3.1",
|
||||
"@runonflux/nat-upnp": "^1.0.2",
|
||||
"accesscontrol": "^2.2.1",
|
||||
@@ -83,126 +85,127 @@
|
||||
"bytes": "^3.1.2",
|
||||
"cacheable-lookup": "^6.1.0",
|
||||
"catch-exit": "^1.2.2",
|
||||
"chokidar": "^3.5.3",
|
||||
"chokidar": "^3.6.0",
|
||||
"class-transformer": "^0.5.1",
|
||||
"class-validator": "^0.14.0",
|
||||
"class-validator": "^0.14.1",
|
||||
"cli-table": "^0.3.11",
|
||||
"command-exists": "^1.2.9",
|
||||
"convert": "^4.10.0",
|
||||
"convert": "^4.14.1",
|
||||
"cors": "^2.8.5",
|
||||
"cross-fetch": "^4.0.0",
|
||||
"docker-event-emitter": "^0.3.0",
|
||||
"dockerode": "^3.3.5",
|
||||
"dotenv": "^16.0.3",
|
||||
"express": "^4.18.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"find-process": "^1.4.7",
|
||||
"global-agent": "^3.0.0",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-fields": "^2.0.3",
|
||||
"graphql-scalars": "^1.21.3",
|
||||
"graphql-scalars": "^1.23.0",
|
||||
"graphql-subscriptions": "^2.0.0",
|
||||
"graphql-tag": "^2.12.6",
|
||||
"graphql-type-json": "^0.3.2",
|
||||
"graphql-type-uuid": "^0.2.0",
|
||||
"graphql-ws": "^5.14.2",
|
||||
"graphql-ws": "^5.16.0",
|
||||
"htpasswd-js": "^1.0.2",
|
||||
"ini": "^4.1.0",
|
||||
"ip": "^1.1.8",
|
||||
"jose": "^4.14.2",
|
||||
"ini": "^4.1.2",
|
||||
"ip": "^2.0.1",
|
||||
"jose": "^5.3.0",
|
||||
"lodash": "^4.17.21",
|
||||
"multi-ini": "^2.2.0",
|
||||
"mustache": "^4.2.0",
|
||||
"nanobus": "^4.5.0",
|
||||
"nest-access-control": "^3.1.0",
|
||||
"nestjs-pino": "^3.5.0",
|
||||
"nestjs-pino": "^4.0.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-window-polyfill": "^1.0.2",
|
||||
"openid-client": "^5.4.0",
|
||||
"openid-client": "^5.6.5",
|
||||
"p-iteration": "^1.1.8",
|
||||
"p-retry": "^4.6.2",
|
||||
"passport-http-header-strategy": "^1.1.0",
|
||||
"pidusage": "^3.0.2",
|
||||
"pino": "^8.16.2",
|
||||
"pino-http": "^8.5.1",
|
||||
"pino-pretty": "^10.2.3",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"pino": "^9.1.0",
|
||||
"pino-http": "^9.0.0",
|
||||
"pino-pretty": "^11.0.0",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"request": "^2.88.2",
|
||||
"semver": "^7.4.0",
|
||||
"semver": "^7.6.2",
|
||||
"stoppable": "^1.1.0",
|
||||
"systeminformation": "^5.21.2",
|
||||
"ts-command-line-args": "^2.5.0",
|
||||
"uuid": "^9.0.0",
|
||||
"ws": "^8.13.0",
|
||||
"wtfnode": "^0.9.1",
|
||||
"systeminformation": "^5.22.9",
|
||||
"ts-command-line-args": "^2.5.1",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.17.0",
|
||||
"wtfnode": "^0.9.2",
|
||||
"xhr2": "^0.2.1",
|
||||
"zod": "^3.22.2"
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "^7.21.0",
|
||||
"@graphql-codegen/add": "^5.0.0",
|
||||
"@graphql-codegen/cli": "^5.0.0",
|
||||
"@graphql-codegen/fragment-matcher": "^5.0.0",
|
||||
"@babel/runtime": "^7.24.5",
|
||||
"@graphql-codegen/add": "^5.0.2",
|
||||
"@graphql-codegen/cli": "^5.0.2",
|
||||
"@graphql-codegen/fragment-matcher": "^5.0.2",
|
||||
"@graphql-codegen/import-types-preset": "^3.0.0",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.0",
|
||||
"@graphql-codegen/typescript": "^4.0.0",
|
||||
"@graphql-codegen/typescript-operations": "^4.0.0",
|
||||
"@graphql-codegen/typescript-resolvers": "4.0.1",
|
||||
"@graphql-codegen/typed-document-node": "^5.0.6",
|
||||
"@graphql-codegen/typescript": "^4.0.6",
|
||||
"@graphql-codegen/typescript-operations": "^4.2.0",
|
||||
"@graphql-codegen/typescript-resolvers": "4.0.6",
|
||||
"@graphql-typed-document-node/core": "^3.2.0",
|
||||
"@nestjs/testing": "^10.2.10",
|
||||
"@swc/core": "^1.3.81",
|
||||
"@types/async-exit-hook": "^2.0.0",
|
||||
"@types/btoa": "^1.2.3",
|
||||
"@types/bytes": "^3.1.1",
|
||||
"@types/cli-table": "^0.3.1",
|
||||
"@types/command-exists": "^1.2.0",
|
||||
"@types/dockerode": "^3.3.16",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/graphql-fields": "^1.3.5",
|
||||
"@types/graphql-type-uuid": "^0.2.3",
|
||||
"@types/ini": "^1.3.31",
|
||||
"@types/lodash": "^4.14.192",
|
||||
"@types/mustache": "^4.2.2",
|
||||
"@types/node": "^18.17.12",
|
||||
"@types/pidusage": "^2.0.2",
|
||||
"@types/pify": "^5.0.1",
|
||||
"@types/semver": "^7.3.13",
|
||||
"@types/sendmail": "^1.4.4",
|
||||
"@types/stoppable": "^1.1.1",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@types/ws": "^8.5.4",
|
||||
"@types/wtfnode": "^0.7.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.58.0",
|
||||
"@typescript-eslint/parser": "^5.58.0",
|
||||
"@nestjs/testing": "^10.3.8",
|
||||
"@swc/core": "^1.5.7",
|
||||
"@types/async-exit-hook": "^2.0.2",
|
||||
"@types/btoa": "^1.2.5",
|
||||
"@types/bytes": "^3.1.4",
|
||||
"@types/cli-table": "^0.3.4",
|
||||
"@types/command-exists": "^1.2.3",
|
||||
"@types/dockerode": "^3.3.29",
|
||||
"@types/express": "^4.17.21",
|
||||
"@types/graphql-fields": "^1.3.9",
|
||||
"@types/graphql-type-uuid": "^0.2.6",
|
||||
"@types/ini": "^4.1.0",
|
||||
"@types/lodash": "^4.17.1",
|
||||
"@types/mustache": "^4.2.5",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/pidusage": "^2.0.5",
|
||||
"@types/pify": "^5.0.4",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/sendmail": "^1.4.7",
|
||||
"@types/stoppable": "^1.1.3",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@types/wtfnode": "^0.7.3",
|
||||
"@typescript-eslint/eslint-plugin": "^7.9.0",
|
||||
"@typescript-eslint/parser": "^7.9.0",
|
||||
"@unraid/eslint-config": "github:unraid/eslint-config",
|
||||
"@vitest/coverage-v8": "^0.34.1",
|
||||
"@vitest/ui": "^0.34.0",
|
||||
"@vitest/coverage-v8": "^1.6.0",
|
||||
"@vitest/ui": "^1.6.0",
|
||||
"camelcase-keys": "^8.0.2",
|
||||
"cz-conventional-changelog": "3.3.0",
|
||||
"eslint": "^8.38.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.0",
|
||||
"eslint-plugin-import": "^2.27.5",
|
||||
"eslint-plugin-prettier": "^5.0.0",
|
||||
"eslint-plugin-unicorn": "^48.0.1",
|
||||
"eslint-plugin-unused-imports": "^2.0.0",
|
||||
"eslint": "^8.56.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^53.0.0",
|
||||
"eslint-plugin-unused-imports": "^3.2.0",
|
||||
"execa": "^7.1.1",
|
||||
"filter-obj": "^5.1.0",
|
||||
"got": "^13.0.0",
|
||||
"graphql-codegen-typescript-validation-schema": "^0.11.0",
|
||||
"got": "^13",
|
||||
"graphql-codegen-typescript-validation-schema": "^0.14.1",
|
||||
"ip-regex": "^5.0.0",
|
||||
"json-difference": "^1.9.1",
|
||||
"json-difference": "^1.16.1",
|
||||
"map-obj": "^5.0.2",
|
||||
"p-props": "^5.0.0",
|
||||
"path-exists": "^5.0.0",
|
||||
"path-type": "^5.0.0",
|
||||
"pkg": "^5.8.1",
|
||||
"pretty-bytes": "^6.1.0",
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pretty-ms": "^8.0.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"tsup": "^7.0.0",
|
||||
"typescript": "^4.9.4",
|
||||
"typesync": "^0.11.0",
|
||||
"vite-tsconfig-paths": "^4.2.0",
|
||||
"vitest": "^0.34.0",
|
||||
"zx": "^7.2.1"
|
||||
"tsup": "^8.0.2",
|
||||
"typescript": "^5.4.5",
|
||||
"typesync": "^0.12.1",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
"zx": "^7.2.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@vmngr/libvirt": "github:unraid/libvirt"
|
||||
|
||||
4
api/scripts/dc.sh
Executable file
4
api/scripts/dc.sh
Executable file
@@ -0,0 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Pass all entered params after the docker compose call
|
||||
COMPOSE_PROJECT_NAME="connect" GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker compose -f docker-compose.yml "$@"
|
||||
48
api/src/__test__/common/allowed-origins.test.ts
Normal file
48
api/src/__test__/common/allowed-origins.test.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import 'reflect-metadata';
|
||||
import { expect, test } from 'vitest';
|
||||
|
||||
// Preloading imports for faster tests
|
||||
import '@app/common/allowed-origins';
|
||||
import '@app/store/modules/emhttp';
|
||||
import '@app/store';
|
||||
|
||||
test('Returns allowed origins', async () => {
|
||||
const { store } = await import('@app/store');
|
||||
const { loadStateFiles } = await import('@app/store/modules/emhttp');
|
||||
const { getAllowedOrigins } = await import('@app/common/allowed-origins');
|
||||
const { loadConfigFile } = await import('@app/store/modules/config');
|
||||
|
||||
// Load state files into store
|
||||
await store.dispatch(loadStateFiles());
|
||||
await store.dispatch(loadConfigFile());
|
||||
|
||||
// Get allowed origins
|
||||
expect(getAllowedOrigins()).toMatchInlineSnapshot(`
|
||||
[
|
||||
"/var/run/unraid-notifications.sock",
|
||||
"/var/run/unraid-php.sock",
|
||||
"/var/run/unraid-cli.sock",
|
||||
"http://localhost:8080",
|
||||
"https://localhost:4443",
|
||||
"https://tower.local:4443",
|
||||
"https://192.168.1.150:4443",
|
||||
"https://tower:4443",
|
||||
"https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443",
|
||||
"https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443",
|
||||
"https://10-252-0-1.hash.myunraid.net:4443",
|
||||
"https://10-252-1-1.hash.myunraid.net:4443",
|
||||
"https://10-253-3-1.hash.myunraid.net:4443",
|
||||
"https://10-253-4-1.hash.myunraid.net:4443",
|
||||
"https://10-253-5-1.hash.myunraid.net:4443",
|
||||
"https://10-100-0-1.hash.myunraid.net:4443",
|
||||
"https://10-100-0-2.hash.myunraid.net:4443",
|
||||
"https://10-123-1-2.hash.myunraid.net:4443",
|
||||
"https://221-123-121-112.hash.myunraid.net:4443",
|
||||
"https://google.com",
|
||||
"https://test.com",
|
||||
"https://connect.myunraid.net",
|
||||
"https://connect-staging.myunraid.net",
|
||||
"https://dev-my.myunraid.net:4000",
|
||||
]
|
||||
`);
|
||||
});
|
||||
@@ -1,109 +0,0 @@
|
||||
import { expect, test, vi } from 'vitest';
|
||||
import { store } from '@app/store';
|
||||
|
||||
import { loadStateFiles } from '@app/store/modules/emhttp';
|
||||
|
||||
vi.mock('@vmngr/libvirt', () => ({
|
||||
ConnectListAllDomainsFlags: {
|
||||
ACTIVE: 0,
|
||||
INACTIVE: 1,
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@app/core/log', () => ({
|
||||
logger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
trace: vi.fn(),
|
||||
},
|
||||
dashboardLogger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn((...input) => console.log(input)),
|
||||
debug: vi.fn(),
|
||||
trace: vi.fn(),
|
||||
},
|
||||
emhttpLogger: {
|
||||
info: vi.fn(),
|
||||
error: vi.fn(),
|
||||
debug: vi.fn(),
|
||||
trace: vi.fn(),
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@app/common/dashboard/boot-timestamp', () => ({
|
||||
bootTimestamp: new Date('2022-06-10T04:35:58.276Z'),
|
||||
}));
|
||||
|
||||
test('Returns generated data', async () => {
|
||||
await store.dispatch(loadStateFiles()).unwrap();
|
||||
|
||||
const { generateData } = await import('@app/common/dashboard/generate-data');
|
||||
const result = await generateData();
|
||||
|
||||
expect(result).toMatchInlineSnapshot(`
|
||||
{
|
||||
"apps": {
|
||||
"installed": 0,
|
||||
"started": 0,
|
||||
},
|
||||
"array": {
|
||||
"capacity": {
|
||||
"bytes": {
|
||||
"free": 19495825571000,
|
||||
"total": 41994745901000,
|
||||
"used": 22498920330000,
|
||||
},
|
||||
},
|
||||
"state": "STOPPED",
|
||||
},
|
||||
"config": {
|
||||
"valid": true,
|
||||
},
|
||||
"display": {
|
||||
"case": {
|
||||
"base64": "",
|
||||
"error": "",
|
||||
"icon": "",
|
||||
"url": "",
|
||||
},
|
||||
},
|
||||
"os": {
|
||||
"hostname": "Tower",
|
||||
"uptime": "2022-06-10T04:35:58.276Z",
|
||||
},
|
||||
"services": [
|
||||
{
|
||||
"name": "unraid-api",
|
||||
"online": true,
|
||||
"uptime": {
|
||||
"timestamp": "2022-06-10T04:35:58.276Z",
|
||||
},
|
||||
"version": "THIS_WILL_BE_REPLACED_WHEN_BUILT",
|
||||
},
|
||||
{
|
||||
"name": "dynamic-remote-access",
|
||||
"online": false,
|
||||
"uptime": {
|
||||
"timestamp": "2022-06-10T04:35:58.276Z",
|
||||
},
|
||||
"version": "DISABLED",
|
||||
},
|
||||
],
|
||||
"vars": {
|
||||
"flashGuid": "0000-0000-0000-000000000000",
|
||||
"regState": "PRO",
|
||||
"regTy": "PRO",
|
||||
"serverDescription": "Dev Server",
|
||||
"serverName": "Tower",
|
||||
},
|
||||
"versions": {
|
||||
"unraid": "6.11.2",
|
||||
},
|
||||
"vms": {
|
||||
"installed": 0,
|
||||
"started": 0,
|
||||
},
|
||||
}
|
||||
`);
|
||||
}, 10_000);
|
||||
@@ -245,6 +245,24 @@ RolesBuilder {
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"config": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"connect": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"connect/dynamic-remote-access": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
"update:own": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"customizations": {
|
||||
"read:any": [
|
||||
"*",
|
||||
@@ -255,12 +273,22 @@ RolesBuilder {
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"display": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"docker": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"docker/container": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"docker/network": {
|
||||
"info": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
@@ -270,16 +298,31 @@ RolesBuilder {
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"network": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"notifications": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"services": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"unraid-version": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"vars": {
|
||||
"read:any": [
|
||||
"*",
|
||||
],
|
||||
},
|
||||
"vms": {
|
||||
"read:any": [
|
||||
"*",
|
||||
|
||||
@@ -10,10 +10,12 @@ test('Creates an array event', async () => {
|
||||
);
|
||||
const { store } = await import('@app/store');
|
||||
const { loadStateFiles } = await import('@app/store/modules/emhttp');
|
||||
|
||||
const { loadConfigFile } = await import('@app/store/modules/config');
|
||||
// Load state files into store
|
||||
await store.dispatch(loadStateFiles());
|
||||
|
||||
await store.dispatch(loadConfigFile());
|
||||
|
||||
const arrayEvent = getArrayData(store.getState);
|
||||
expect(arrayEvent).toMatchInlineSnapshot(`
|
||||
{
|
||||
@@ -177,6 +179,7 @@ test('Creates an array event', async () => {
|
||||
"warning": null,
|
||||
},
|
||||
],
|
||||
"id": "97bbe87602982688216c367801f7aa24ea57350b44b7523160d01a9d48d6fcb9",
|
||||
"parities": [
|
||||
{
|
||||
"comment": null,
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { expect, test } from 'vitest';
|
||||
import { setupPermissions } from '@app/core/permissions';
|
||||
|
||||
test('Returns default permissions', () => {
|
||||
expect(setupPermissions()).toMatchSnapshot();
|
||||
expect(setupPermissions()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
import 'reflect-metadata';
|
||||
|
||||
import { test, expect } from 'vitest';
|
||||
import { getWriteableConfig } from '@app/core/utils/files/config-file-normalizer';
|
||||
import { initialState } from '@app/store/modules/config';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
test('it creates a FLASH config with NO OPTIONAL values', () => {
|
||||
const basicConfig = initialState;
|
||||
const config = getWriteableConfig(basicConfig, 'flash');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
const basicConfig = initialState;
|
||||
const config = getWriteableConfig(basicConfig, 'flash');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
{
|
||||
"api": {
|
||||
"extraOrigins": "",
|
||||
@@ -37,9 +39,9 @@ test('it creates a FLASH config with NO OPTIONAL values', () => {
|
||||
});
|
||||
|
||||
test('it creates a MEMORY config with NO OPTIONAL values', () => {
|
||||
const basicConfig = initialState;
|
||||
const config = getWriteableConfig(basicConfig, 'memory');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
const basicConfig = initialState;
|
||||
const config = getWriteableConfig(basicConfig, 'memory');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
{
|
||||
"api": {
|
||||
"extraOrigins": "",
|
||||
@@ -54,7 +56,7 @@ test('it creates a MEMORY config with NO OPTIONAL values', () => {
|
||||
},
|
||||
"remote": {
|
||||
"accesstoken": "",
|
||||
"allowedOrigins": "/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, https://connect.myunraid.net, https://staging.connect.myunraid.net, https://dev-my.myunraid.net:4000",
|
||||
"allowedOrigins": "/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000",
|
||||
"apikey": "",
|
||||
"avatar": "",
|
||||
"dynamicRemoteAccessType": "DISABLED",
|
||||
@@ -74,15 +76,15 @@ test('it creates a MEMORY config with NO OPTIONAL values', () => {
|
||||
});
|
||||
|
||||
test('it creates a FLASH config with OPTIONAL values', () => {
|
||||
const basicConfig = cloneDeep(initialState);
|
||||
basicConfig.remote['2Fa'] = 'yes';
|
||||
basicConfig.local['2Fa'] = 'yes';
|
||||
basicConfig.local.showT2Fa = 'yes';
|
||||
basicConfig.api.extraOrigins = 'myextra.origins';
|
||||
basicConfig.remote.upnpEnabled = 'yes';
|
||||
basicConfig.connectionStatus.upnpStatus = 'Turned On';
|
||||
const config = getWriteableConfig(basicConfig, 'flash');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
const basicConfig = cloneDeep(initialState);
|
||||
basicConfig.remote['2Fa'] = 'yes';
|
||||
basicConfig.local['2Fa'] = 'yes';
|
||||
basicConfig.local.showT2Fa = 'yes';
|
||||
basicConfig.api.extraOrigins = 'myextra.origins';
|
||||
basicConfig.remote.upnpEnabled = 'yes';
|
||||
basicConfig.connectionStatus.upnpStatus = 'Turned On';
|
||||
const config = getWriteableConfig(basicConfig, 'flash');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
{
|
||||
"api": {
|
||||
"extraOrigins": "myextra.origins",
|
||||
@@ -118,15 +120,15 @@ test('it creates a FLASH config with OPTIONAL values', () => {
|
||||
});
|
||||
|
||||
test('it creates a MEMORY config with OPTIONAL values', () => {
|
||||
const basicConfig = cloneDeep(initialState);
|
||||
basicConfig.remote['2Fa'] = 'yes';
|
||||
basicConfig.local['2Fa'] = 'yes';
|
||||
basicConfig.local.showT2Fa = 'yes';
|
||||
basicConfig.api.extraOrigins = 'myextra.origins';
|
||||
basicConfig.remote.upnpEnabled = 'yes';
|
||||
basicConfig.connectionStatus.upnpStatus = 'Turned On';
|
||||
const config = getWriteableConfig(basicConfig, 'memory');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
const basicConfig = cloneDeep(initialState);
|
||||
basicConfig.remote['2Fa'] = 'yes';
|
||||
basicConfig.local['2Fa'] = 'yes';
|
||||
basicConfig.local.showT2Fa = 'yes';
|
||||
basicConfig.api.extraOrigins = 'myextra.origins';
|
||||
basicConfig.remote.upnpEnabled = 'yes';
|
||||
basicConfig.connectionStatus.upnpStatus = 'Turned On';
|
||||
const config = getWriteableConfig(basicConfig, 'memory');
|
||||
expect(config).toMatchInlineSnapshot(`
|
||||
{
|
||||
"api": {
|
||||
"extraOrigins": "myextra.origins",
|
||||
@@ -146,7 +148,7 @@ test('it creates a MEMORY config with OPTIONAL values', () => {
|
||||
"remote": {
|
||||
"2Fa": "yes",
|
||||
"accesstoken": "",
|
||||
"allowedOrigins": "/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, https://connect.myunraid.net, https://staging.connect.myunraid.net, https://dev-my.myunraid.net:4000",
|
||||
"allowedOrigins": "/var/run/unraid-notifications.sock, /var/run/unraid-php.sock, /var/run/unraid-cli.sock, https://connect.myunraid.net, https://connect-staging.myunraid.net, https://dev-my.myunraid.net:4000",
|
||||
"apikey": "",
|
||||
"avatar": "",
|
||||
"dynamicRemoteAccessType": "DISABLED",
|
||||
|
||||
@@ -0,0 +1,39 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import { parse } from 'ini';
|
||||
import { safelySerializeObjectToIni } from '@app/core/utils/files/safe-ini-serializer';
|
||||
import { Serializer } from 'multi-ini';
|
||||
|
||||
test('MultiIni breaks when serializing an object with a boolean inside', async () => {
|
||||
const objectToSerialize = {
|
||||
root: {
|
||||
anonMode: false,
|
||||
},
|
||||
};
|
||||
const serializer = new Serializer({ keep_quotes: false });
|
||||
expect(serializer.serialize(objectToSerialize)).toMatchInlineSnapshot(`
|
||||
"[root]
|
||||
anonMode=false
|
||||
"
|
||||
`)
|
||||
});
|
||||
|
||||
test('MultiIni can safely serialize an object with a boolean inside', async () => {
|
||||
const objectToSerialize = {
|
||||
root: {
|
||||
anonMode: false,
|
||||
},
|
||||
};
|
||||
expect(safelySerializeObjectToIni(objectToSerialize)).toMatchInlineSnapshot(`
|
||||
"[root]
|
||||
anonMode="false"
|
||||
"
|
||||
`);
|
||||
const result = safelySerializeObjectToIni(objectToSerialize);
|
||||
expect(parse(result)).toMatchInlineSnapshot(`
|
||||
{
|
||||
"root": {
|
||||
"anonMode": false,
|
||||
},
|
||||
}
|
||||
`);
|
||||
});
|
||||
114
api/src/__test__/core/utils/misc/parse-config.test.ts
Normal file
114
api/src/__test__/core/utils/misc/parse-config.test.ts
Normal file
@@ -0,0 +1,114 @@
|
||||
import { test, expect } from 'vitest';
|
||||
import { parseConfig } from '@app/core/utils/misc/parse-config';
|
||||
import { Parser as MultiIniParser } from 'multi-ini';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { parse } from 'ini';
|
||||
import { safelySerializeObjectToIni } from '@app/core/utils/files/safe-ini-serializer';
|
||||
|
||||
const iniTestData = `["root"]
|
||||
idx="0"
|
||||
name="root"
|
||||
desc="Console and webGui login account"
|
||||
passwd="yes"
|
||||
["xo"]
|
||||
idx="1"
|
||||
name="xo"
|
||||
desc=""
|
||||
passwd="yes"
|
||||
["test_user"]
|
||||
idx="2"
|
||||
name="test_user"
|
||||
desc=""
|
||||
passwd="no"`;
|
||||
|
||||
test('it loads a config from a passed in ini file successfully', () => {
|
||||
const res = parseConfig<any>({
|
||||
file: iniTestData,
|
||||
type: 'ini',
|
||||
});
|
||||
expect(res).toMatchInlineSnapshot(`
|
||||
{
|
||||
"root": {
|
||||
"desc": "Console and webGui login account",
|
||||
"idx": "0",
|
||||
"name": "root",
|
||||
"passwd": "yes",
|
||||
},
|
||||
"testUser": {
|
||||
"desc": "",
|
||||
"idx": "2",
|
||||
"name": "test_user",
|
||||
"passwd": "no",
|
||||
},
|
||||
"xo": {
|
||||
"desc": "",
|
||||
"idx": "1",
|
||||
"name": "xo",
|
||||
"passwd": "yes",
|
||||
},
|
||||
}
|
||||
`);
|
||||
expect(res?.root.desc).toEqual('Console and webGui login account');
|
||||
});
|
||||
|
||||
test('it loads a config from disk properly', () => {
|
||||
const path = './dev/states/var.ini';
|
||||
const res = parseConfig<any>({ filePath: path, type: 'ini' });
|
||||
expect(res.DOMAIN_SHORT).toEqual(undefined);
|
||||
expect(res.domainShort).toEqual('');
|
||||
expect(res.shareCount).toEqual('0');
|
||||
});
|
||||
|
||||
test('Confirm Multi-Ini Parser Still Broken', () => {
|
||||
const parser = new MultiIniParser();
|
||||
const res = parser.parse(iniTestData);
|
||||
expect(res).toMatchInlineSnapshot('{}');
|
||||
});
|
||||
|
||||
test('Combine Ini and Multi-Ini to read and then write a file with quotes', async () => {
|
||||
const parsedFile = parse(iniTestData);
|
||||
expect(parsedFile).toMatchInlineSnapshot(`
|
||||
{
|
||||
"root": {
|
||||
"desc": "Console and webGui login account",
|
||||
"idx": "0",
|
||||
"name": "root",
|
||||
"passwd": "yes",
|
||||
},
|
||||
"test_user": {
|
||||
"desc": "",
|
||||
"idx": "2",
|
||||
"name": "test_user",
|
||||
"passwd": "no",
|
||||
},
|
||||
"xo": {
|
||||
"desc": "",
|
||||
"idx": "1",
|
||||
"name": "xo",
|
||||
"passwd": "yes",
|
||||
},
|
||||
}
|
||||
`);
|
||||
|
||||
const ini = safelySerializeObjectToIni(parsedFile);
|
||||
await writeFile('/tmp/test.ini', ini);
|
||||
const file = await readFile('/tmp/test.ini', 'utf-8');
|
||||
expect(file).toMatchInlineSnapshot(`
|
||||
"[root]
|
||||
idx="0"
|
||||
name="root"
|
||||
desc="Console and webGui login account"
|
||||
passwd="yes"
|
||||
[xo]
|
||||
idx="1"
|
||||
name="xo"
|
||||
desc=""
|
||||
passwd="yes"
|
||||
[test_user]
|
||||
idx="2"
|
||||
name="test_user"
|
||||
desc=""
|
||||
passwd="no"
|
||||
"
|
||||
`);
|
||||
});
|
||||
@@ -0,0 +1,10 @@
|
||||
import 'reflect-metadata';
|
||||
import { checkMothershipAuthentication } from "@app/graphql/resolvers/query/cloud/check-mothership-authentication";
|
||||
import { expect, test } from "vitest";
|
||||
import packageJson from '@app/../package.json'
|
||||
|
||||
test('It fails to authenticate with mothership with no credentials', async () => {
|
||||
await expect(checkMothershipAuthentication('BAD', 'BAD')).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Failed to connect to https://mothership.unraid.net/ws with a "426" HTTP error.]`);
|
||||
expect(packageJson.version).not.toBeNull();
|
||||
await expect(checkMothershipAuthentication(packageJson.version, 'BAD_API_KEY')).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Invalid credentials]`);
|
||||
}, 15_000)
|
||||
206
api/src/__test__/graphql/resolvers/subscription/network.test.ts
Normal file
206
api/src/__test__/graphql/resolvers/subscription/network.test.ts
Normal file
@@ -0,0 +1,206 @@
|
||||
import { expect, test } from 'vitest';
|
||||
import { type Nginx } from '../../../../core/types/states/nginx';
|
||||
import { getUrlForField, getUrlForServer, getServerIps, type NginxUrlFields } from '@app/graphql/resolvers/subscription/network';
|
||||
import { store } from '@app/store';
|
||||
import { loadStateFiles } from '@app/store/modules/emhttp';
|
||||
import { loadConfigFile } from '@app/store/modules/config';
|
||||
|
||||
test.each([
|
||||
[{ httpPort: 80, httpsPort: 443, url: 'my-default-url.com' }],
|
||||
[{ httpPort: 123, httpsPort: 443, url: 'my-default-url.com' }],
|
||||
[{ httpPort: 80, httpsPort: 12_345, url: 'my-default-url.com' }],
|
||||
[{ httpPort: 212, httpsPort: 3_233, url: 'my-default-url.com' }],
|
||||
[{ httpPort: 80, httpsPort: 443, url: 'https://BROKEN_URL' }],
|
||||
|
||||
])('getUrlForField', ({ httpPort, httpsPort, url }) => {
|
||||
const responseInsecure = getUrlForField({
|
||||
port: httpPort,
|
||||
url,
|
||||
});
|
||||
|
||||
const responseSecure = getUrlForField({
|
||||
portSsl: httpsPort,
|
||||
url,
|
||||
});
|
||||
if (httpPort === 80) {
|
||||
expect(responseInsecure.port).toBe('');
|
||||
} else {
|
||||
expect(responseInsecure.port).toBe(httpPort.toString());
|
||||
}
|
||||
|
||||
if (httpsPort === 443) {
|
||||
expect(responseSecure.port).toBe('');
|
||||
} else {
|
||||
expect(responseSecure.port).toBe(httpsPort.toString());
|
||||
}
|
||||
});
|
||||
|
||||
test('getUrlForServer - field exists, ssl disabled', () => {
|
||||
const result = getUrlForServer({ nginx: { lanIp: '192.168.1.1', sslEnabled: false, httpPort: 123, httpsPort: 445 } as const as Nginx,
|
||||
field: 'lanIp',
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot('"http://192.168.1.1:123/"');
|
||||
});
|
||||
|
||||
test('getUrlForServer - field exists, ssl yes', () => {
|
||||
const result = getUrlForServer({
|
||||
nginx: { lanIp: '192.168.1.1', sslEnabled: true, sslMode: 'yes', httpPort: 123, httpsPort: 445 } as const as Nginx,
|
||||
field: 'lanIp',
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot('"https://192.168.1.1:445/"');
|
||||
});
|
||||
|
||||
test('getUrlForServer - field exists, ssl yes, port empty', () => {
|
||||
const result = getUrlForServer(
|
||||
{ nginx: { lanIp: '192.168.1.1', sslEnabled: true, sslMode: 'yes', httpPort: 80, httpsPort: 443 } as const as Nginx,
|
||||
field: 'lanIp',
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot('"https://192.168.1.1/"');
|
||||
});
|
||||
|
||||
test('getUrlForServer - field exists, ssl auto', () => {
|
||||
const getResult = async () => getUrlForServer({
|
||||
nginx: { lanIp: '192.168.1.1', sslEnabled: true, sslMode: 'auto', httpPort: 123, httpsPort: 445 } as const as Nginx,
|
||||
field: 'lanIp',
|
||||
});
|
||||
void expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Cannot get IP Based URL for field: "lanIp" SSL mode auto]`);
|
||||
});
|
||||
|
||||
test('getUrlForServer - field does not exist, ssl disabled', () => {
|
||||
const getResult = async () => getUrlForServer(
|
||||
{
|
||||
nginx: { lanIp: '192.168.1.1', sslEnabled: false, sslMode: 'no' } as const as Nginx,
|
||||
ports: {
|
||||
port: ':123', portSsl: ':445', defaultUrl: new URL('https://my-default-url.unraid.net'),
|
||||
},
|
||||
// @ts-expect-error Field doesn't exist
|
||||
field: 'idontexist',
|
||||
});
|
||||
void expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: IP URL Resolver: Could not resolve any access URL for field: "idontexist", is FQDN?: false]`);
|
||||
});
|
||||
|
||||
test('getUrlForServer - FQDN - field exists, port non-empty', () => {
|
||||
const result = getUrlForServer({
|
||||
nginx: { lanFqdn: 'my-fqdn.unraid.net', httpsPort: 445 } as const as Nginx,
|
||||
field: 'lanFqdn',
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot('"https://my-fqdn.unraid.net:445/"');
|
||||
});
|
||||
|
||||
test('getUrlForServer - FQDN - field exists, port empty', () => {
|
||||
const result = getUrlForServer({ nginx: { lanFqdn: 'my-fqdn.unraid.net', httpPort: 80, httpsPort: 443 } as const as Nginx,
|
||||
field: 'lanFqdn',
|
||||
});
|
||||
expect(result).toMatchInlineSnapshot('"https://my-fqdn.unraid.net/"');
|
||||
});
|
||||
|
||||
test.each([
|
||||
[{ nginx: { lanFqdn: 'my-fqdn.unraid.net', sslEnabled: false, sslMode: 'no', httpPort: 80, httpsPort: 443 } as const as Nginx, field: 'lanFqdn' as NginxUrlFields }],
|
||||
[{ nginx: { wanFqdn: 'my-fqdn.unraid.net', sslEnabled: true, sslMode: 'yes', httpPort: 80, httpsPort: 443 } as const as Nginx, field: 'wanFqdn' as NginxUrlFields }],
|
||||
[{ nginx: { wanFqdn6: 'my-fqdn.unraid.net', sslEnabled: true, sslMode: 'auto', httpPort: 80, httpsPort: 443 } as const as Nginx, field: 'wanFqdn6' as NginxUrlFields }],
|
||||
|
||||
])('getUrlForServer - FQDN', ({ nginx, field }) => {
|
||||
const result = getUrlForServer({ nginx, field });
|
||||
expect(result.toString()).toBe('https://my-fqdn.unraid.net/');
|
||||
});
|
||||
|
||||
test('getUrlForServer - field does not exist, ssl disabled', () => {
|
||||
const getResult = async () => getUrlForServer({ nginx:
|
||||
{ lanFqdn: 'my-fqdn.unraid.net' } as const as Nginx,
|
||||
ports: { portSsl: '', port: '', defaultUrl: new URL('https://my-default-url.unraid.net') },
|
||||
// @ts-expect-error Field doesn't exist
|
||||
field: 'idontexist' });
|
||||
void expect(getResult).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: IP URL Resolver: Could not resolve any access URL for field: "idontexist", is FQDN?: false]`);
|
||||
});
|
||||
|
||||
test('integration test, loading nginx ini and generating all URLs', async () => {
|
||||
await store.dispatch(loadStateFiles());
|
||||
await store.dispatch(loadConfigFile());
|
||||
|
||||
const urls = getServerIps();
|
||||
expect(urls.urls).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"ipv4": "https://tower.local:4443/",
|
||||
"ipv6": "https://tower.local:4443/",
|
||||
"name": "Default",
|
||||
"type": "DEFAULT",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://192.168.1.150:4443/",
|
||||
"name": "LAN IPv4",
|
||||
"type": "LAN",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://tower:4443/",
|
||||
"name": "LAN Name",
|
||||
"type": "MDNS",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://tower.local:4443/",
|
||||
"name": "LAN MDNS",
|
||||
"type": "MDNS",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net:4443/",
|
||||
"name": "FQDN LAN",
|
||||
"type": "LAN",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net:8443/",
|
||||
"name": "FQDN WAN",
|
||||
"type": "WAN",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-252-0-1.hash.myunraid.net:4443/",
|
||||
"name": "FQDN WG 0",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-252-1-1.hash.myunraid.net:4443/",
|
||||
"name": "FQDN WG 1",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-253-3-1.hash.myunraid.net:4443/",
|
||||
"name": "FQDN WG 2",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-253-4-1.hash.myunraid.net:4443/",
|
||||
"name": "FQDN WG 3",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-253-5-1.hash.myunraid.net:4443/",
|
||||
"name": "FQDN WG 4",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-100-0-1.hash.myunraid.net:4443/",
|
||||
"name": "FQDN TAILSCALE 0",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-100-0-2.hash.myunraid.net:4443/",
|
||||
"name": "FQDN TAILSCALE 1",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://10-123-1-2.hash.myunraid.net:4443/",
|
||||
"name": "FQDN CUSTOM 0",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
{
|
||||
"ipv4": "https://221-123-121-112.hash.myunraid.net:4443/",
|
||||
"name": "FQDN CUSTOM 1",
|
||||
"type": "WIREGUARD",
|
||||
},
|
||||
]
|
||||
`);
|
||||
expect(urls.errors).toMatchInlineSnapshot(`
|
||||
[
|
||||
[Error: IP URL Resolver: Could not resolve any access URL for field: "lanIp6", is FQDN?: false],
|
||||
]
|
||||
`);
|
||||
});
|
||||
111
api/src/__test__/mothership/api-key/api-key-check-jobs.test.ts
Normal file
111
api/src/__test__/mothership/api-key/api-key-check-jobs.test.ts
Normal file
@@ -0,0 +1,111 @@
|
||||
import { API_KEY_STATUS } from '@app/mothership/api-key/api-key-types';
|
||||
import * as apiKeyCheckJobs from '@app/mothership/jobs/api-key-check-jobs';
|
||||
import * as apiKeyValidator from '@app/mothership/api-key/validate-api-key-with-keyserver';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import { type RecursivePartial } from '@app/types/index';
|
||||
import { type RootState } from '@app/store/index';
|
||||
import { logoutUser } from '@app/store/modules/config';
|
||||
|
||||
describe('apiKeyCheckJob Tests', () => {
|
||||
it('API Check Job (with success)', async () => {
|
||||
const getState = vi.fn<[], RecursivePartial<RootState>>().mockReturnValue({
|
||||
apiKey: { status: API_KEY_STATUS.PENDING_VALIDATION },
|
||||
config: { remote: { apikey: '_______________________BIG_API_KEY_HERE_________________________' } },
|
||||
emhttp: { var: { flashGuid: 'my-flash-guid', version: '6.11.5' } },
|
||||
});
|
||||
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const validationSpy = vi.spyOn(apiKeyValidator, 'validateApiKeyWithKeyServer').mockResolvedValue(API_KEY_STATUS.API_KEY_VALID);
|
||||
|
||||
await expect(apiKeyCheckJobs.apiKeyCheckJob(getState, dispatch)).resolves.toBe(true);
|
||||
|
||||
expect(validationSpy).toHaveBeenCalledOnce();
|
||||
|
||||
expect(dispatch).toHaveBeenLastCalledWith({
|
||||
payload: API_KEY_STATUS.API_KEY_VALID,
|
||||
type: 'apiKey/setApiKeyState',
|
||||
});
|
||||
});
|
||||
|
||||
it('API Check Job (with invalid length key)', async () => {
|
||||
// Setup state
|
||||
const getState = vi.fn<[], RecursivePartial<RootState>>().mockReturnValue({
|
||||
apiKey: { status: API_KEY_STATUS.PENDING_VALIDATION },
|
||||
config: { remote: { apikey: 'too-short-key' } },
|
||||
emhttp: { var: { flashGuid: 'my-flash-guid', version: '6.11.5' } },
|
||||
});
|
||||
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const validationSpy = vi.spyOn(apiKeyValidator, 'validateApiKeyWithKeyServer').mockResolvedValue(API_KEY_STATUS.API_KEY_VALID);
|
||||
|
||||
await expect(apiKeyCheckJobs.apiKeyCheckJob(getState, dispatch)).resolves.toBe(false);
|
||||
expect(dispatch).toHaveBeenCalledWith(expect.any(Function));
|
||||
|
||||
expect(validationSpy).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('API Check Job (with a failure that throws an error - NETWORK_ERROR)', async () => {
|
||||
const getState = vi.fn<[], RecursivePartial<RootState>>().mockReturnValue({
|
||||
apiKey: { status: API_KEY_STATUS.PENDING_VALIDATION },
|
||||
config: { remote: { apikey: '_______________________BIG_API_KEY_HERE_________________________' } },
|
||||
emhttp: { var: { flashGuid: 'my-flash-guid', version: '6.11.5' } },
|
||||
});
|
||||
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const validationSpy = vi.spyOn(apiKeyValidator, 'validateApiKeyWithKeyServer')
|
||||
.mockResolvedValueOnce(API_KEY_STATUS.NETWORK_ERROR);
|
||||
|
||||
await expect(apiKeyCheckJobs.apiKeyCheckJob(getState, dispatch)).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Keyserver Failure, must retry]`);
|
||||
|
||||
expect(validationSpy).toHaveBeenCalledOnce();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
payload: API_KEY_STATUS.NETWORK_ERROR,
|
||||
type: 'apiKey/setApiKeyState',
|
||||
});
|
||||
});
|
||||
|
||||
it('API Check Job (with a failure that throws an error - INVALID_RESPONSE)', async () => {
|
||||
const getState = vi.fn<[], RecursivePartial<RootState>>().mockReturnValue({
|
||||
apiKey: { status: API_KEY_STATUS.PENDING_VALIDATION },
|
||||
config: { remote: { apikey: '_______________________BIG_API_KEY_HERE_________________________' } },
|
||||
emhttp: { var: { flashGuid: 'my-flash-guid', version: '6.11.5' } },
|
||||
});
|
||||
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const validationSpy = vi.spyOn(apiKeyValidator, 'validateApiKeyWithKeyServer')
|
||||
.mockResolvedValueOnce(API_KEY_STATUS.INVALID_KEYSERVER_RESPONSE);
|
||||
|
||||
await expect(apiKeyCheckJobs.apiKeyCheckJob(getState, dispatch)).rejects.toThrowErrorMatchingInlineSnapshot(`[Error: Keyserver Failure, must retry]`);
|
||||
|
||||
expect(validationSpy).toHaveBeenCalledOnce();
|
||||
|
||||
expect(dispatch).toHaveBeenCalledWith({
|
||||
payload: API_KEY_STATUS.INVALID_KEYSERVER_RESPONSE,
|
||||
type: 'apiKey/setApiKeyState',
|
||||
});
|
||||
}, 10_000);
|
||||
|
||||
it('API Check Job (with failure that results in a log out)', async () => {
|
||||
const getState = vi.fn<[], RecursivePartial<RootState>>().mockReturnValue({
|
||||
apiKey: { status: API_KEY_STATUS.PENDING_VALIDATION },
|
||||
config: { remote: { apikey: '_______________________BIG_API_KEY_HERE_________________________' } },
|
||||
emhttp: { var: { flashGuid: 'my-flash-guid', version: '6.11.5' } },
|
||||
});
|
||||
|
||||
const dispatch = vi.fn();
|
||||
|
||||
const validationSpy = vi.spyOn(apiKeyValidator, 'validateApiKeyWithKeyServer')
|
||||
.mockResolvedValue(API_KEY_STATUS.API_KEY_INVALID);
|
||||
|
||||
await expect(apiKeyCheckJobs.apiKeyCheckJob(getState, dispatch)).resolves.toBe(false);
|
||||
|
||||
expect(validationSpy).toHaveBeenCalledOnce();
|
||||
expect(dispatch).toHaveBeenCalledTimes(1);
|
||||
expect(dispatch).toHaveBeenCalledWith(expect.any(Function));
|
||||
}, 10_000);
|
||||
});
|
||||
@@ -56,7 +56,7 @@ test('After init returns values from cfg file for all fields', async () => {
|
||||
expect(state).toMatchObject(
|
||||
expect.objectContaining({
|
||||
api: {
|
||||
extraOrigins: '',
|
||||
extraOrigins: expect.stringMatching('https://google.com,https://test.com'),
|
||||
version: expect.any(String),
|
||||
},
|
||||
connectionStatus: {
|
||||
@@ -114,7 +114,7 @@ test('updateUserConfig merges in changes to current state', async () => {
|
||||
expect(state).toMatchObject(
|
||||
expect.objectContaining({
|
||||
api: {
|
||||
extraOrigins: '',
|
||||
extraOrigins: expect.stringMatching('https://google.com,https://test.com'),
|
||||
version: expect.any(String),
|
||||
},
|
||||
connectionStatus: {
|
||||
|
||||
@@ -104,10 +104,76 @@ test('After init returns values from cfg file for all fields', async () => {
|
||||
"certificateName": "*.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"certificatePath": "/boot/config/ssl/certs/certificate_bundle.pem",
|
||||
"defaultUrl": "https://Tower.local:4443",
|
||||
"fqdnUrls": [
|
||||
{
|
||||
"fqdn": "192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"id": null,
|
||||
"interface": "LAN",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"id": null,
|
||||
"interface": "WAN",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-252-0-1.hash.myunraid.net",
|
||||
"id": 0,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-252-1-1.hash.myunraid.net",
|
||||
"id": 1,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-3-1.hash.myunraid.net",
|
||||
"id": 2,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-4-1.hash.myunraid.net",
|
||||
"id": 3,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-5-1.hash.myunraid.net",
|
||||
"id": 4,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-100-0-1.hash.myunraid.net",
|
||||
"id": 0,
|
||||
"interface": "TAILSCALE",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-100-0-2.hash.myunraid.net",
|
||||
"id": 1,
|
||||
"interface": "TAILSCALE",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-123-1-2.hash.myunraid.net",
|
||||
"id": 0,
|
||||
"interface": "CUSTOM",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "221-123-121-112.hash.myunraid.net",
|
||||
"id": 1,
|
||||
"interface": "CUSTOM",
|
||||
"isIpv6": true,
|
||||
},
|
||||
],
|
||||
"httpPort": 8080,
|
||||
"httpsPort": 4443,
|
||||
"lanFqdn": "192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"lanFqdn6": "",
|
||||
"lanIp": "192.168.1.150",
|
||||
"lanIp6": "",
|
||||
"lanMdns": "Tower.local",
|
||||
@@ -115,31 +181,7 @@ test('After init returns values from cfg file for all fields', async () => {
|
||||
"sslEnabled": true,
|
||||
"sslMode": "yes",
|
||||
"wanAccessEnabled": false,
|
||||
"wanFqdn": "85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"wanFqdn6": "",
|
||||
"wanIp": "",
|
||||
"wgFqdns": [
|
||||
{
|
||||
"fqdn": "10-252-0-1.hash.myunraid.net",
|
||||
"id": 0,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-252-1-1.hash.myunraid.net",
|
||||
"id": 1,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-3-1.hash.myunraid.net",
|
||||
"id": 3,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-4-1.hash.myunraid.net",
|
||||
"id": 4,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-5-1.hash.myunraid.net",
|
||||
"id": 55,
|
||||
},
|
||||
],
|
||||
}
|
||||
`);
|
||||
expect(disks).toMatchInlineSnapshot(`
|
||||
@@ -984,6 +1026,7 @@ test('After init returns values from cfg file for all fields', async () => {
|
||||
"porttelnet": 23,
|
||||
"queueDepth": "auto",
|
||||
"regCheck": "Valid",
|
||||
"regExp": "",
|
||||
"regFile": "/app/dev/Unraid.net/Pro.key",
|
||||
"regGen": "0",
|
||||
"regGuid": "13FE-4200-C300-58C372A52B19",
|
||||
|
||||
@@ -20,6 +20,7 @@ test('Returns paths', async () => {
|
||||
"myservers-config",
|
||||
"myservers-config-states",
|
||||
"myservers-env",
|
||||
"myservers-keepalive",
|
||||
"keyfile-base",
|
||||
"machine-id",
|
||||
"log-base",
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
|
||||
|
||||
exports[`Returns parsed state file 1`] = `
|
||||
{
|
||||
"certificateName": "*.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"certificatePath": "/boot/config/ssl/certs/certificate_bundle.pem",
|
||||
"defaultUrl": "https://Tower.local:4443",
|
||||
"fqdnUrls": [
|
||||
{
|
||||
"fqdn": "192-168-1-150.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"id": null,
|
||||
"interface": "LAN",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "85-121-123-122.thisisfourtyrandomcharacters012345678900.myunraid.net",
|
||||
"id": null,
|
||||
"interface": "WAN",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-252-0-1.hash.myunraid.net",
|
||||
"id": 0,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-252-1-1.hash.myunraid.net",
|
||||
"id": 1,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-3-1.hash.myunraid.net",
|
||||
"id": 2,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-4-1.hash.myunraid.net",
|
||||
"id": 3,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-253-5-1.hash.myunraid.net",
|
||||
"id": 4,
|
||||
"interface": "WG",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-100-0-1.hash.myunraid.net",
|
||||
"id": 0,
|
||||
"interface": "TAILSCALE",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-100-0-2.hash.myunraid.net",
|
||||
"id": 1,
|
||||
"interface": "TAILSCALE",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "10-123-1-2.hash.myunraid.net",
|
||||
"id": 0,
|
||||
"interface": "CUSTOM",
|
||||
"isIpv6": false,
|
||||
},
|
||||
{
|
||||
"fqdn": "221-123-121-112.hash.myunraid.net",
|
||||
"id": 1,
|
||||
"interface": "CUSTOM",
|
||||
"isIpv6": true,
|
||||
},
|
||||
],
|
||||
"httpPort": 8080,
|
||||
"httpsPort": 4443,
|
||||
"lanIp": "192.168.1.150",
|
||||
"lanIp6": "",
|
||||
"lanMdns": "Tower.local",
|
||||
"lanName": "Tower",
|
||||
"sslEnabled": true,
|
||||
"sslMode": "yes",
|
||||
"wanAccessEnabled": false,
|
||||
"wanIp": "",
|
||||
}
|
||||
`;
|
||||
16
api/src/__test__/store/state-parsers/nginx.test.ts
Normal file
16
api/src/__test__/store/state-parsers/nginx.test.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { join } from 'path';
|
||||
import { expect, test } from 'vitest';
|
||||
import { store } from '@app/store';
|
||||
import type { NginxIni } from '@app/store/state-parsers/nginx';
|
||||
|
||||
test('Returns parsed state file', async () => {
|
||||
const { parse } = await import('@app/store/state-parsers/nginx');
|
||||
const { parseConfig } = await import('@app/core/utils/misc/parse-config');
|
||||
const { paths } = store.getState();
|
||||
const filePath = join(paths.states, 'nginx.ini');
|
||||
const stateFile = parseConfig<NginxIni>({
|
||||
filePath,
|
||||
type: 'ini',
|
||||
});
|
||||
expect(parse(stateFile)).toMatchSnapshot();
|
||||
});
|
||||
@@ -103,6 +103,7 @@ test('Returns parsed state file', async () => {
|
||||
"porttelnet": 23,
|
||||
"queueDepth": "auto",
|
||||
"regCheck": "Valid",
|
||||
"regExp": "",
|
||||
"regFile": "/app/dev/Unraid.net/Pro.key",
|
||||
"regGen": "0",
|
||||
"regGuid": "13FE-4200-C300-58C372A52B19",
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { setEnv } from '@app/cli/set-env';
|
||||
import { start } from '@app/cli/commands/start';
|
||||
import { stop } from '@app/cli/commands/stop';
|
||||
|
||||
@@ -6,7 +5,6 @@ import { stop } from '@app/cli/commands/stop';
|
||||
* Stop a running API process and then start it again.
|
||||
*/
|
||||
export const restart = async () => {
|
||||
setEnv('LOG_TRANSPORT', 'stdout');
|
||||
await stop();
|
||||
await start();
|
||||
};
|
||||
|
||||
@@ -6,14 +6,12 @@ import { logToSyslog } from '@app/cli/log-to-syslog';
|
||||
import { getters } from '@app/store';
|
||||
import { getAllUnraidApiPids } from '@app/cli/get-unraid-api-pid';
|
||||
import { API_VERSION } from '@app/environment';
|
||||
import { setEnv } from '@app/cli/set-env';
|
||||
|
||||
/**
|
||||
* Start a new API process.
|
||||
*/
|
||||
export const start = async () => {
|
||||
// Set process title
|
||||
setEnv('LOG_TRANSPORT', 'stdout');
|
||||
|
||||
process.title = 'unraid-api';
|
||||
const runningProcesses = await getAllUnraidApiPids();
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { cliLogger } from '@app/core/log';
|
||||
import { getAllUnraidApiPids } from '@app/cli/get-unraid-api-pid';
|
||||
import { setEnv } from '@app/cli/set-env';
|
||||
import { sleep } from '@app/core/utils/misc/sleep';
|
||||
import pRetry from 'p-retry';
|
||||
|
||||
@@ -9,8 +8,6 @@ import pRetry from 'p-retry';
|
||||
*/
|
||||
|
||||
export const stop = async () => {
|
||||
setEnv('LOG_TRANSPORT', 'stdout');
|
||||
|
||||
try {
|
||||
await pRetry(async (attempts) => {
|
||||
const runningApis = await getAllUnraidApiPids();
|
||||
@@ -18,7 +15,7 @@ export const stop = async () => {
|
||||
if (runningApis.length > 0) {
|
||||
cliLogger.info('Stopping %s unraid-api process(es)...', runningApis.length);
|
||||
runningApis.forEach(pid => process.kill(pid, 'SIGTERM'));
|
||||
|
||||
await sleep(50);
|
||||
const newPids = await getAllUnraidApiPids();
|
||||
|
||||
if (newPids.length > 0) {
|
||||
@@ -31,7 +28,7 @@ export const stop = async () => {
|
||||
return true;
|
||||
}, {
|
||||
retries: 2,
|
||||
minTimeout: 2_000,
|
||||
minTimeout: 1_000,
|
||||
factor: 1,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
|
||||
@@ -11,11 +11,7 @@ export const main = async (...argv: string[]) => {
|
||||
cliLogger.debug(env, 'Loading env file');
|
||||
|
||||
// Set envs
|
||||
setEnv(
|
||||
'LOG_TYPE',
|
||||
process.env.LOG_TYPE ??
|
||||
(command === 'start' || mainOptions.debug ? 'pretty' : 'raw')
|
||||
);
|
||||
setEnv('LOG_TYPE', 'pretty');
|
||||
cliLogger.debug({ paths: getters.paths() }, 'Starting CLI');
|
||||
|
||||
setEnv('DEBUG', mainOptions.debug ?? false);
|
||||
@@ -28,7 +24,7 @@ export const main = async (...argv: string[]) => {
|
||||
if (!process.env.LOG_TRANSPORT) {
|
||||
if (process.env.ENVIRONMENT === 'production' && !mainOptions.debug) {
|
||||
setEnv('LOG_TRANSPORT', 'file');
|
||||
setEnv('LOG_LEVEL', 'DEBUG');
|
||||
setEnv('LOG_LEVEL', 'INFO');
|
||||
} else if (!mainOptions.debug) {
|
||||
// Staging Environment, backgrounded plugin
|
||||
setEnv('LOG_TRANSPORT', 'file');
|
||||
|
||||
@@ -1,93 +1,112 @@
|
||||
import { getters, type RootState, store } from '@app/store';
|
||||
import { uniq } from 'lodash';
|
||||
import { getServerIps, getUrlForField } from '@app/graphql/resolvers/subscription/network';
|
||||
import {
|
||||
getServerIps,
|
||||
getUrlForField,
|
||||
} from '@app/graphql/resolvers/subscription/network';
|
||||
import { FileLoadStatus } from '@app/store/types';
|
||||
import { logger } from '../core';
|
||||
import { GRAPHQL_INTROSPECTION } from '@app/environment';
|
||||
|
||||
const getAllowedSocks = (): string[] => [
|
||||
// Notifier bridge
|
||||
'/var/run/unraid-notifications.sock',
|
||||
// Notifier bridge
|
||||
'/var/run/unraid-notifications.sock',
|
||||
|
||||
// Unraid PHP scripts
|
||||
'/var/run/unraid-php.sock',
|
||||
// Unraid PHP scripts
|
||||
'/var/run/unraid-php.sock',
|
||||
|
||||
// CLI
|
||||
'/var/run/unraid-cli.sock',
|
||||
// CLI
|
||||
'/var/run/unraid-cli.sock',
|
||||
];
|
||||
|
||||
const getLocalAccessUrlsForServer = (state: RootState = store.getState()): string[] => {
|
||||
const { emhttp } = state;
|
||||
if (emhttp.status !== FileLoadStatus.LOADED) {
|
||||
return [];
|
||||
}
|
||||
const getLocalAccessUrlsForServer = (
|
||||
state: RootState = store.getState()
|
||||
): string[] => {
|
||||
const { emhttp } = state;
|
||||
if (emhttp.status !== FileLoadStatus.LOADED) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const { nginx } = emhttp;
|
||||
try {
|
||||
return [
|
||||
getUrlForField({ url: 'localhost', port: nginx.httpPort }).toString(),
|
||||
getUrlForField({ url: 'localhost', portSsl: nginx.httpsPort }).toString(),
|
||||
];
|
||||
} catch (error: unknown) {
|
||||
logger.debug('Caught error in getLocalAccessUrlsForServer: \n%o', error);
|
||||
return [];
|
||||
}
|
||||
const { nginx } = emhttp;
|
||||
try {
|
||||
return [
|
||||
getUrlForField({
|
||||
url: 'localhost',
|
||||
port: nginx.httpPort,
|
||||
}).toString(),
|
||||
getUrlForField({
|
||||
url: 'localhost',
|
||||
portSsl: nginx.httpsPort,
|
||||
}).toString(),
|
||||
];
|
||||
} catch (error: unknown) {
|
||||
logger.debug(
|
||||
'Caught error in getLocalAccessUrlsForServer: \n%o',
|
||||
error
|
||||
);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
const getRemoteAccessUrlsForAllowedOrigins = (state: RootState = store.getState()): string[] => {
|
||||
const { urls } = getServerIps(state);
|
||||
const getRemoteAccessUrlsForAllowedOrigins = (
|
||||
state: RootState = store.getState()
|
||||
): string[] => {
|
||||
const { urls } = getServerIps(state);
|
||||
|
||||
if (urls) {
|
||||
return urls.reduce<string[]>((acc, curr) => {
|
||||
if (curr.ipv4 && curr.ipv6) {
|
||||
acc.push(curr.ipv4.toString());
|
||||
} else if (curr.ipv4) {
|
||||
acc.push(curr.ipv4.toString());
|
||||
} else if (curr.ipv6) {
|
||||
acc.push(curr.ipv6.toString());
|
||||
}
|
||||
if (urls) {
|
||||
return urls.reduce<string[]>((acc, curr) => {
|
||||
if (curr.ipv4 && curr.ipv6 || curr.ipv4) {
|
||||
acc.push(curr.ipv4.toString());
|
||||
} else if (curr.ipv6) {
|
||||
acc.push(curr.ipv6.toString());
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
return [];
|
||||
return [];
|
||||
};
|
||||
|
||||
const getExtraOrigins = (): string[] => {
|
||||
const { extraOrigins } = getters.config().api;
|
||||
if (extraOrigins) {
|
||||
return extraOrigins.split(', ').filter(origin => origin.startsWith('http://') || origin.startsWith('https://'));
|
||||
}
|
||||
export const getExtraOrigins = (): string[] => {
|
||||
const { extraOrigins } = getters.config().api;
|
||||
if (extraOrigins) {
|
||||
return extraOrigins
|
||||
.replaceAll(' ', '')
|
||||
.split(',')
|
||||
.filter(
|
||||
(origin) =>
|
||||
origin.startsWith('http://') ||
|
||||
origin.startsWith('https://')
|
||||
);
|
||||
}
|
||||
|
||||
return [];
|
||||
return [];
|
||||
};
|
||||
|
||||
const getConnectOrigins = () : string[] => {
|
||||
const connectMain = 'https://connect.myunraid.net';
|
||||
const connectStaging = 'https://staging.connect.myunraid.net';
|
||||
const connectDev = 'https://dev-my.myunraid.net:4000';
|
||||
const getConnectOrigins = (): string[] => {
|
||||
const connectMain = 'https://connect.myunraid.net';
|
||||
const connectStaging = 'https://connect-staging.myunraid.net';
|
||||
const connectDev = 'https://dev-my.myunraid.net:4000';
|
||||
|
||||
return [
|
||||
connectMain,
|
||||
connectStaging,
|
||||
connectDev
|
||||
]
|
||||
}
|
||||
return [connectMain, connectStaging, connectDev];
|
||||
};
|
||||
|
||||
const getApolloSandbox = (): string[] => {
|
||||
if (GRAPHQL_INTROSPECTION) {
|
||||
return ['https://studio.apollographql.com'];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
if (GRAPHQL_INTROSPECTION) {
|
||||
return ['https://studio.apollographql.com'];
|
||||
}
|
||||
return [];
|
||||
};
|
||||
|
||||
export const getAllowedOrigins = (state: RootState = store.getState()): string[] => uniq([
|
||||
...getAllowedSocks(),
|
||||
...getLocalAccessUrlsForServer(),
|
||||
...getRemoteAccessUrlsForAllowedOrigins(state),
|
||||
...getExtraOrigins(),
|
||||
...getConnectOrigins(),
|
||||
...getApolloSandbox()
|
||||
|
||||
]).map(url => url.endsWith('/') ? url.slice(0, -1) : url);
|
||||
export const getAllowedOrigins = (
|
||||
state: RootState = store.getState()
|
||||
): string[] =>
|
||||
uniq([
|
||||
...getAllowedSocks(),
|
||||
...getLocalAccessUrlsForServer(),
|
||||
...getRemoteAccessUrlsForAllowedOrigins(state),
|
||||
...getExtraOrigins(),
|
||||
...getConnectOrigins(),
|
||||
...getApolloSandbox(),
|
||||
]).map((url) => (url.endsWith('/') ? url.slice(0, -1) : url));
|
||||
|
||||
@@ -1,152 +0,0 @@
|
||||
import { ConnectListAllDomainsFlags } from '@vmngr/libvirt';
|
||||
import { getHypervisor } from '@app/core/utils/vms/get-hypervisor';
|
||||
import { getUnraidVersion } from '@app/common/dashboard/get-unraid-version';
|
||||
import { getArray } from '@app/common/dashboard/get-array';
|
||||
import { bootTimestamp } from '@app/common/dashboard/boot-timestamp';
|
||||
import { dashboardLogger } from '@app/core/log';
|
||||
import { getters, store } from '@app/store';
|
||||
import {
|
||||
type DashboardServiceInput,
|
||||
type DashboardInput,
|
||||
} from '@app/graphql/generated/client/graphql';
|
||||
import { API_VERSION } from '@app/environment';
|
||||
import { DynamicRemoteAccessType } from '@app/remoteAccess/types';
|
||||
import { DashboardInputSchema } from '@app/graphql/generated/client/validators';
|
||||
import { ZodError } from 'zod';
|
||||
|
||||
const getVmSummary = async (): Promise<DashboardInput['vms']> => {
|
||||
try {
|
||||
const hypervisor = await getHypervisor();
|
||||
if (!hypervisor) {
|
||||
return {
|
||||
installed: 0,
|
||||
started: 0,
|
||||
};
|
||||
}
|
||||
|
||||
const activeDomains = (await hypervisor.connectListAllDomains(
|
||||
ConnectListAllDomainsFlags.ACTIVE
|
||||
)) as unknown[];
|
||||
const inactiveDomains = (await hypervisor.connectListAllDomains(
|
||||
ConnectListAllDomainsFlags.INACTIVE
|
||||
)) as unknown[];
|
||||
return {
|
||||
installed: activeDomains.length + inactiveDomains.length,
|
||||
started: activeDomains.length,
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
installed: 0,
|
||||
started: 0,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
const getDynamicRemoteAccessService = (): DashboardServiceInput | null => {
|
||||
const { config, dynamicRemoteAccess } = store.getState();
|
||||
const enabledStatus = config.remote.dynamicRemoteAccessType;
|
||||
|
||||
return {
|
||||
name: 'dynamic-remote-access',
|
||||
online: enabledStatus !== DynamicRemoteAccessType.DISABLED,
|
||||
version: dynamicRemoteAccess.runningType,
|
||||
uptime: {
|
||||
timestamp: bootTimestamp.toISOString(),
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
const services = (): DashboardInput['services'] => {
|
||||
const dynamicRemoteAccess = getDynamicRemoteAccessService();
|
||||
return [
|
||||
{
|
||||
name: 'unraid-api',
|
||||
online: true,
|
||||
uptime: {
|
||||
timestamp: bootTimestamp.toISOString(),
|
||||
},
|
||||
version: API_VERSION,
|
||||
},
|
||||
...(dynamicRemoteAccess ? [dynamicRemoteAccess] : []),
|
||||
];
|
||||
};
|
||||
|
||||
const getData = async (): Promise<DashboardInput> => {
|
||||
const emhttp = getters.emhttp();
|
||||
const docker = getters.docker();
|
||||
|
||||
return {
|
||||
vars: {
|
||||
regState: emhttp.var.regState,
|
||||
regTy: emhttp.var.regTy,
|
||||
flashGuid: emhttp.var.flashGuid,
|
||||
serverName: emhttp.var.name,
|
||||
serverDescription: emhttp.var.comment,
|
||||
},
|
||||
apps: {
|
||||
installed: docker.installed ?? 0,
|
||||
started: docker.running ?? 0,
|
||||
},
|
||||
versions: {
|
||||
unraid: await getUnraidVersion(),
|
||||
},
|
||||
os: {
|
||||
hostname: emhttp.var.name,
|
||||
uptime: bootTimestamp.toISOString(),
|
||||
},
|
||||
vms: await getVmSummary(),
|
||||
array: getArray(),
|
||||
services: services(),
|
||||
display: {
|
||||
case: {
|
||||
url: '',
|
||||
icon: '',
|
||||
error: '',
|
||||
base64: '',
|
||||
},
|
||||
},
|
||||
config: emhttp.var.configValid
|
||||
? { valid: true }
|
||||
: {
|
||||
valid: false,
|
||||
error:
|
||||
{
|
||||
error: 'UNKNOWN_ERROR',
|
||||
invalid: 'INVALID',
|
||||
nokeyserver: 'NO_KEY_SERVER',
|
||||
withdrawn: 'WITHDRAWN',
|
||||
}[emhttp.var.configState] ?? 'UNKNOWN_ERROR',
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export const generateData = async (): Promise<DashboardInput | null> => {
|
||||
const data = await getData();
|
||||
|
||||
try {
|
||||
// Validate generated data
|
||||
// @TODO: Fix this runtype to use generated types from the Zod validators (as seen in mothership Codegen)
|
||||
const result = DashboardInputSchema().parse(data);
|
||||
|
||||
return result;
|
||||
} catch (error: unknown) {
|
||||
// Log error for user
|
||||
if (error instanceof ZodError) {
|
||||
dashboardLogger.error(
|
||||
'Failed validation with issues: ',
|
||||
error.issues.map((issue) => ({
|
||||
message: issue.message,
|
||||
path: issue.path.join(','),
|
||||
}))
|
||||
);
|
||||
} else {
|
||||
dashboardLogger.error(
|
||||
'Failed validating dashboard object: ',
|
||||
error,
|
||||
data
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
@@ -34,6 +34,7 @@ export const FIVE_MINUTES_MS = 5 * ONE_MINUTE;
|
||||
export const TEN_MINUTES_MS = 10 * ONE_MINUTE;
|
||||
export const THIRTY_MINUTES_MS = 30 * ONE_MINUTE;
|
||||
export const ONE_HOUR_MS = 60 * ONE_MINUTE;
|
||||
export const ONE_DAY_MS = ONE_HOUR_MS * 24;
|
||||
|
||||
// Seconds
|
||||
export const ONE_HOUR_SECS = 60 * 60;
|
||||
|
||||
@@ -2,6 +2,33 @@ import { pino } from 'pino';
|
||||
import { LOG_TRANSPORT, LOG_TYPE } from '@app/environment';
|
||||
|
||||
import pretty from 'pino-pretty';
|
||||
import { chmodSync, existsSync, mkdirSync, rmSync, statSync } from 'node:fs';
|
||||
import { getters } from '@app/store/index';
|
||||
import { join } from 'node:path';
|
||||
|
||||
const makeLoggingDirectoryIfNotExists = () => {
|
||||
if (!existsSync(getters.paths()['log-base'])) {
|
||||
console.log('Creating logging directory');
|
||||
mkdirSync(getters.paths()['log-base']);
|
||||
}
|
||||
|
||||
chmodSync(getters.paths()['log-base'], 0o644);
|
||||
if (
|
||||
existsSync(`${getters.paths()['log-base']}/stdout.log`) &&
|
||||
statSync(`${getters.paths()['log-base']}/stdout.log`).size > 5_000_000
|
||||
) {
|
||||
rmSync(`${getters.paths()['log-base']}/stdout.log`);
|
||||
}
|
||||
try {
|
||||
rmSync(`${getters.paths()['log-base']}/stdout.log.*`);
|
||||
} catch (e) {
|
||||
// Ignore Error
|
||||
}
|
||||
};
|
||||
|
||||
if (LOG_TRANSPORT === 'file') {
|
||||
makeLoggingDirectoryIfNotExists();
|
||||
}
|
||||
|
||||
export const levels = [
|
||||
'trace',
|
||||
@@ -20,9 +47,12 @@ const level =
|
||||
] ?? 'info';
|
||||
|
||||
export const logDestination = pino.destination({
|
||||
dest: LOG_TRANSPORT === 'file' ? '/var/log/unraid-api/stdout.log' : 1,
|
||||
dest:
|
||||
LOG_TRANSPORT === 'file'
|
||||
? join(getters.paths()['log-base'], 'stdout.log')
|
||||
: 1,
|
||||
minLength: 1_024,
|
||||
sync: false
|
||||
sync: false,
|
||||
});
|
||||
|
||||
const stream =
|
||||
|
||||
@@ -9,7 +9,7 @@ export const setupLogRotation = async () => {
|
||||
'/etc/logrotate.d/unraid-api',
|
||||
`
|
||||
/var/log/unraid-api/*.log {
|
||||
rotate 2
|
||||
rotate 1
|
||||
missingok
|
||||
size 5M
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { getServerIdentifier } from '@app/core/utils/server-identifier';
|
||||
import {
|
||||
ArrayDiskType,
|
||||
type ArrayCapacity,
|
||||
@@ -56,6 +57,7 @@ export const getArrayData = (getState = store.getState): ArrayType => {
|
||||
};
|
||||
|
||||
return {
|
||||
id: getServerIdentifier('array'),
|
||||
state: emhttp.var.mdState,
|
||||
capacity,
|
||||
boot,
|
||||
|
||||
@@ -1,30 +0,0 @@
|
||||
import type { CoreResult, CoreContext } from '@app/core/types';
|
||||
import { ensurePermission } from '@app/core/utils/permissions/ensure-permission';
|
||||
import { getShares } from '@app/core/utils/shares/get-shares';
|
||||
|
||||
/**
|
||||
* Get all shares.
|
||||
*/
|
||||
export const getAllShares = async (context: CoreContext): Promise<CoreResult> => {
|
||||
const { user } = context;
|
||||
|
||||
// Check permissions
|
||||
ensurePermission(user, {
|
||||
resource: 'share',
|
||||
action: 'read',
|
||||
possession: 'any',
|
||||
});
|
||||
|
||||
const userShares = getShares('users');
|
||||
const diskShares = getShares('disks');
|
||||
|
||||
const shares = [
|
||||
...userShares,
|
||||
...diskShares,
|
||||
];
|
||||
|
||||
return {
|
||||
text: `Shares: ${JSON.stringify(shares, null, 2)}`,
|
||||
json: shares,
|
||||
};
|
||||
};
|
||||
@@ -5,14 +5,12 @@ import {
|
||||
diskLayout,
|
||||
} from 'systeminformation';
|
||||
import { map as asyncMap } from 'p-iteration';
|
||||
import { ensurePermission } from '@app/core/utils/permissions/ensure-permission';
|
||||
import { type Context } from '@app/graphql/schema/utils';
|
||||
import {
|
||||
type Disk,
|
||||
DiskInterfaceType,
|
||||
DiskSmartStatus,
|
||||
DiskFsType,
|
||||
} from '@app/graphql/generated/api/types';
|
||||
import { DiskFsType } from '@app/graphql/generated/api/types';
|
||||
import { graphqlLogger } from '@app/core/log';
|
||||
|
||||
const getTemperature = async (
|
||||
|
||||
@@ -11,7 +11,6 @@ export * from './users';
|
||||
export * from './vms';
|
||||
export * from './add-share';
|
||||
export * from './add-user';
|
||||
export * from './get-all-shares';
|
||||
export * from './get-apps';
|
||||
export * from './get-devices';
|
||||
export * from './get-disks';
|
||||
|
||||
@@ -123,16 +123,38 @@ const roles: Record<string, Role> = {
|
||||
extends: 'guest',
|
||||
permissions: [
|
||||
{ resource: 'array', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'config', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'connect', action: 'read:any', attributes: '*' },
|
||||
{
|
||||
resource: 'connect/dynamic-remote-access',
|
||||
action: 'read:any',
|
||||
attributes: '*',
|
||||
},
|
||||
{
|
||||
resource: 'connect/dynamic-remote-access',
|
||||
action: 'update:own',
|
||||
attributes: '*',
|
||||
},
|
||||
{ resource: 'customizations', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'dashboard', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'display', action: 'read:any', attributes: '*' },
|
||||
{
|
||||
resource: 'docker/container',
|
||||
action: 'read:any',
|
||||
attributes: '*',
|
||||
},
|
||||
{ resource: 'docker', action: 'read:any', attributes: '*' },
|
||||
{
|
||||
resource: 'docker/container',
|
||||
action: 'read:any',
|
||||
attributes: '*',
|
||||
},
|
||||
{ resource: 'info', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'logs', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'docker/network', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'network', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'notifications', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'services', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vars', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vms', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'vms/domain', action: 'read:any', attributes: '*' },
|
||||
{ resource: 'unraid-version', action: 'read:any', attributes: '*' },
|
||||
|
||||
23
api/src/core/types/states/nginx.ts
Normal file
23
api/src/core/types/states/nginx.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
export interface FqdnEntry {
|
||||
interface: string;
|
||||
id: number | null;
|
||||
fqdn: string;
|
||||
isIpv6: boolean;
|
||||
}
|
||||
|
||||
export interface Nginx {
|
||||
certificateName: string;
|
||||
certificatePath: string;
|
||||
defaultUrl: string;
|
||||
httpPort: number;
|
||||
httpsPort: number;
|
||||
lanIp: string;
|
||||
lanIp6: string;
|
||||
lanMdns: string;
|
||||
lanName: string;
|
||||
sslEnabled: boolean;
|
||||
sslMode: 'yes' | 'no' | 'auto';
|
||||
wanAccessEnabled: boolean;
|
||||
wanIp: string;
|
||||
fqdnUrls: FqdnEntry[];
|
||||
}
|
||||
@@ -113,8 +113,12 @@ export type Var = {
|
||||
regFile: string;
|
||||
regGen: string;
|
||||
regGuid: string;
|
||||
/** Registration time for key */
|
||||
regTm: string;
|
||||
/** Expiration of license for Trial Keys */
|
||||
regTm2: string;
|
||||
/** Expiration of Updates for non-legacy keys */
|
||||
regExp: string | null;
|
||||
/** Who the current Unraid key is registered to. */
|
||||
regTo: string;
|
||||
/** Which type of key this is. */
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { getAllowedOrigins } from '@app/common/allowed-origins';
|
||||
import { DynamicRemoteAccessType } from '@app/remoteAccess/types';
|
||||
import { DynamicRemoteAccessType } from '@app/graphql/generated/api/types';
|
||||
import {
|
||||
type SliceState as ConfigSliceState,
|
||||
initialState,
|
||||
|
||||
6
api/src/core/utils/server-identifier.ts
Normal file
6
api/src/core/utils/server-identifier.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
import { getters } from '@app/store/index';
|
||||
import crypto from 'crypto';
|
||||
export const getServerIdentifier = (domain: string | null = null): string => {
|
||||
const config = getters.config();
|
||||
return crypto.createHash('sha256').update(`${domain ? domain : ''}-${config.api.version}-${config.remote.apikey ?? config.upc.apikey}`).digest('hex');
|
||||
};
|
||||
@@ -15,6 +15,6 @@ export const PORT = process.env.PORT ?? '/var/run/unraid-api.sock';
|
||||
export const DRY_RUN = process.env.DRY_RUN === 'true';
|
||||
export const BYPASS_PERMISSION_CHECKS = process.env.BYPASS_PERMISSION_CHECKS === 'true';
|
||||
export const LOG_CORS = process.env.LOG_CORS === 'true';
|
||||
export const LOG_TYPE = process.env.LOG_TYPE as 'pretty' | 'raw';
|
||||
export const LOG_TYPE = process.env.LOG_TYPE as 'pretty' | 'raw' ?? 'pretty';
|
||||
export const LOG_LEVEL = process.env.LOG_LEVEL as 'TRACE' | 'DEBUG' | 'INFO' | 'WARN' | 'ERROR' | 'FATAL';
|
||||
export const LOG_TRANSPORT = process.env.LOG_TRANSPORT as 'file' | 'stdout';
|
||||
@@ -2,7 +2,7 @@
|
||||
import * as Types from '@app/graphql/generated/api/types';
|
||||
|
||||
import { z } from 'zod'
|
||||
import { AllowedOriginInput, ApiKey, ApiKeyResponse, ArrayType, ArrayCapacity, ArrayDisk, ArrayDiskFsColor, ArrayDiskStatus, ArrayDiskType, ArrayPendingState, ArrayState, Baseboard, Capacity, Case, Cloud, CloudResponse, Config, ConfigErrorState, ConnectSignInInput, ConnectUserInfoInput, ContainerHostConfig, ContainerMount, ContainerPort, ContainerPortType, ContainerState, Devices, Disk, DiskFsType, DiskInterfaceType, DiskPartition, DiskSmartStatus, Display, DockerContainer, DockerNetwork, Flash, Gpu, Importance, Info, InfoApps, InfoCpu, InfoMemory, KeyFile, Me, MemoryFormFactor, MemoryLayout, MemoryType, MinigraphStatus, MinigraphqlResponse, Mount, Network, Notification, NotificationFilter, NotificationInput, NotificationType, Os, Owner, ParityCheck, Partition, Pci, ProfileModel, Registration, RegistrationState, RelayResponse, Server, ServerStatus, Service, SetupRemoteAccessInput, Share, System, Temperature, Theme, UnassignedDevice, Uptime, Usb, User, Vars, Versions, VmDomain, VmState, Vms, WAN_ACCESS_TYPE, WAN_FORWARD_TYPE, Welcome, addApiKeyInput, addUserInput, arrayDiskInput, authenticateInput, deleteUserInput, mdState, registrationType, updateApikeyInput, usersInput } from '@app/graphql/generated/api/types'
|
||||
import { AccessUrl, AccessUrlInput, AllowedOriginInput, ApiKey, ApiKeyResponse, ArrayType, ArrayCapacity, ArrayDisk, ArrayDiskFsColor, ArrayDiskStatus, ArrayDiskType, ArrayPendingState, ArrayState, Baseboard, Capacity, Case, Cloud, CloudResponse, Config, ConfigErrorState, Connect, ConnectSignInInput, ConnectUserInfoInput, ContainerHostConfig, ContainerMount, ContainerPort, ContainerPortType, ContainerState, Devices, Disk, DiskFsType, DiskInterfaceType, DiskPartition, DiskSmartStatus, Display, Docker, DockerContainer, DockerNetwork, DynamicRemoteAccessStatus, DynamicRemoteAccessType, EnableDynamicRemoteAccessInput, Flash, Gpu, Importance, Info, InfoApps, InfoCpu, InfoMemory, KeyFile, Me, MemoryFormFactor, MemoryLayout, MemoryType, MinigraphStatus, MinigraphqlResponse, Mount, Network, Node, Notification, NotificationFilter, NotificationInput, NotificationType, Os, Owner, ParityCheck, Partition, Pci, ProfileModel, Registration, RegistrationState, RelayResponse, RemoteAccess, Server, ServerStatus, Service, SetupRemoteAccessInput, Share, System, Temperature, Theme, URL_TYPE, UnassignedDevice, Uptime, Usb, User, UserAccount, Vars, Versions, VmDomain, VmState, Vms, WAN_ACCESS_TYPE, WAN_FORWARD_TYPE, Welcome, addApiKeyInput, addUserInput, arrayDiskInput, authenticateInput, deleteUserInput, mdState, registrationType, updateApikeyInput, usersInput } from '@app/graphql/generated/api/types'
|
||||
import { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
|
||||
type Properties<T> = Required<{
|
||||
@@ -37,6 +37,8 @@ export const DiskInterfaceTypeSchema = z.nativeEnum(DiskInterfaceType);
|
||||
|
||||
export const DiskSmartStatusSchema = z.nativeEnum(DiskSmartStatus);
|
||||
|
||||
export const DynamicRemoteAccessTypeSchema = z.nativeEnum(DynamicRemoteAccessType);
|
||||
|
||||
export const ImportanceSchema = z.nativeEnum(Importance);
|
||||
|
||||
export const MemoryFormFactorSchema = z.nativeEnum(MemoryFormFactor);
|
||||
@@ -55,6 +57,8 @@ export const TemperatureSchema = z.nativeEnum(Temperature);
|
||||
|
||||
export const ThemeSchema = z.nativeEnum(Theme);
|
||||
|
||||
export const URL_TYPESchema = z.nativeEnum(URL_TYPE);
|
||||
|
||||
export const VmStateSchema = z.nativeEnum(VmState);
|
||||
|
||||
export const WAN_ACCESS_TYPESchema = z.nativeEnum(WAN_ACCESS_TYPE);
|
||||
@@ -65,6 +69,25 @@ export const mdStateSchema = z.nativeEnum(mdState);
|
||||
|
||||
export const registrationTypeSchema = z.nativeEnum(registrationType);
|
||||
|
||||
export function AccessUrlSchema(): z.ZodObject<Properties<AccessUrl>> {
|
||||
return z.object({
|
||||
__typename: z.literal('AccessUrl').optional(),
|
||||
ipv4: definedNonNullAnySchema.nullish(),
|
||||
ipv6: definedNonNullAnySchema.nullish(),
|
||||
name: z.string().nullish(),
|
||||
type: URL_TYPESchema
|
||||
})
|
||||
}
|
||||
|
||||
export function AccessUrlInputSchema(): z.ZodObject<Properties<AccessUrlInput>> {
|
||||
return z.object({
|
||||
ipv4: definedNonNullAnySchema.nullish(),
|
||||
ipv6: definedNonNullAnySchema.nullish(),
|
||||
name: z.string().nullish(),
|
||||
type: URL_TYPESchema
|
||||
})
|
||||
}
|
||||
|
||||
export function AllowedOriginInputSchema(): z.ZodObject<Properties<AllowedOriginInput>> {
|
||||
return z.object({
|
||||
origins: z.array(z.string())
|
||||
@@ -97,6 +120,7 @@ export function ArrayTypeSchema(): z.ZodObject<Properties<ArrayType>> {
|
||||
caches: z.array(ArrayDiskSchema()),
|
||||
capacity: ArrayCapacitySchema(),
|
||||
disks: z.array(ArrayDiskSchema()),
|
||||
id: z.string(),
|
||||
parities: z.array(ArrayDiskSchema()),
|
||||
pendingState: ArrayPendingStateSchema.nullish(),
|
||||
previousState: ArrayStateSchema.nullish(),
|
||||
@@ -195,10 +219,19 @@ export function ConfigSchema(): z.ZodObject<Properties<Config>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Config').optional(),
|
||||
error: ConfigErrorStateSchema.nullish(),
|
||||
id: z.string(),
|
||||
valid: z.boolean().nullish()
|
||||
})
|
||||
}
|
||||
|
||||
export function ConnectSchema(): z.ZodObject<Properties<Connect>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Connect').optional(),
|
||||
dynamicRemoteAccess: DynamicRemoteAccessStatusSchema(),
|
||||
id: z.string()
|
||||
})
|
||||
}
|
||||
|
||||
export function ConnectSignInInputSchema(): z.ZodObject<Properties<ConnectSignInInput>> {
|
||||
return z.object({
|
||||
accessToken: z.string().nullish(),
|
||||
@@ -300,6 +333,7 @@ export function DisplaySchema(): z.ZodObject<Properties<Display>> {
|
||||
dashapps: z.string().nullish(),
|
||||
date: z.string().nullish(),
|
||||
hot: z.number().nullish(),
|
||||
id: z.string(),
|
||||
locale: z.string().nullish(),
|
||||
max: z.number().nullish(),
|
||||
number: z.string().nullish(),
|
||||
@@ -317,6 +351,15 @@ export function DisplaySchema(): z.ZodObject<Properties<Display>> {
|
||||
})
|
||||
}
|
||||
|
||||
export function DockerSchema(): z.ZodObject<Properties<Docker>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Docker').optional(),
|
||||
containers: z.array(DockerContainerSchema()).nullish(),
|
||||
id: z.string(),
|
||||
networks: z.array(DockerNetworkSchema()).nullish()
|
||||
})
|
||||
}
|
||||
|
||||
export function DockerContainerSchema(): z.ZodObject<Properties<DockerContainer>> {
|
||||
return z.object({
|
||||
__typename: z.literal('DockerContainer').optional(),
|
||||
@@ -359,6 +402,22 @@ export function DockerNetworkSchema(): z.ZodObject<Properties<DockerNetwork>> {
|
||||
})
|
||||
}
|
||||
|
||||
export function DynamicRemoteAccessStatusSchema(): z.ZodObject<Properties<DynamicRemoteAccessStatus>> {
|
||||
return z.object({
|
||||
__typename: z.literal('DynamicRemoteAccessStatus').optional(),
|
||||
enabledType: DynamicRemoteAccessTypeSchema,
|
||||
error: z.string().nullish(),
|
||||
runningType: DynamicRemoteAccessTypeSchema
|
||||
})
|
||||
}
|
||||
|
||||
export function EnableDynamicRemoteAccessInputSchema(): z.ZodObject<Properties<EnableDynamicRemoteAccessInput>> {
|
||||
return z.object({
|
||||
enabled: z.boolean(),
|
||||
url: z.lazy(() => AccessUrlInputSchema())
|
||||
})
|
||||
}
|
||||
|
||||
export function FlashSchema(): z.ZodObject<Properties<Flash>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Flash').optional(),
|
||||
@@ -389,10 +448,12 @@ export function InfoSchema(): z.ZodObject<Properties<Info>> {
|
||||
cpu: InfoCpuSchema().nullish(),
|
||||
devices: DevicesSchema().nullish(),
|
||||
display: DisplaySchema().nullish(),
|
||||
id: z.string(),
|
||||
machineId: z.string().nullish(),
|
||||
memory: InfoMemorySchema().nullish(),
|
||||
os: OsSchema().nullish(),
|
||||
system: SystemSchema().nullish(),
|
||||
time: z.string(),
|
||||
versions: VersionsSchema().nullish()
|
||||
})
|
||||
}
|
||||
@@ -503,8 +564,10 @@ export function MountSchema(): z.ZodObject<Properties<Mount>> {
|
||||
export function NetworkSchema(): z.ZodObject<Properties<Network>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Network').optional(),
|
||||
accessUrls: z.array(AccessUrlSchema()).nullish(),
|
||||
carrierChanges: z.string().nullish(),
|
||||
duplex: z.string().nullish(),
|
||||
id: z.string(),
|
||||
iface: z.string().nullish(),
|
||||
ifaceName: z.string().nullish(),
|
||||
internal: z.string().nullish(),
|
||||
@@ -518,6 +581,12 @@ export function NetworkSchema(): z.ZodObject<Properties<Network>> {
|
||||
})
|
||||
}
|
||||
|
||||
export function NodeSchema(): z.ZodObject<Properties<Node>> {
|
||||
return z.object({
|
||||
id: z.string()
|
||||
})
|
||||
}
|
||||
|
||||
export function NotificationSchema(): z.ZodObject<Properties<Notification>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Notification').optional(),
|
||||
@@ -688,7 +757,8 @@ export function RegistrationSchema(): z.ZodObject<Properties<Registration>> {
|
||||
guid: z.string().nullish(),
|
||||
keyFile: KeyFileSchema().nullish(),
|
||||
state: RegistrationStateSchema.nullish(),
|
||||
type: registrationTypeSchema.nullish()
|
||||
type: registrationTypeSchema.nullish(),
|
||||
updateExpiration: z.string().nullish()
|
||||
})
|
||||
}
|
||||
|
||||
@@ -701,6 +771,15 @@ export function RelayResponseSchema(): z.ZodObject<Properties<RelayResponse>> {
|
||||
})
|
||||
}
|
||||
|
||||
export function RemoteAccessSchema(): z.ZodObject<Properties<RemoteAccess>> {
|
||||
return z.object({
|
||||
__typename: z.literal('RemoteAccess').optional(),
|
||||
accessType: WAN_ACCESS_TYPESchema,
|
||||
forwardType: WAN_FORWARD_TYPESchema.nullish(),
|
||||
port: z.number().nullish()
|
||||
})
|
||||
}
|
||||
|
||||
export function ServerSchema(): z.ZodObject<Properties<Server>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Server').optional(),
|
||||
@@ -719,6 +798,7 @@ export function ServerSchema(): z.ZodObject<Properties<Server>> {
|
||||
export function ServiceSchema(): z.ZodObject<Properties<Service>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Service').optional(),
|
||||
id: z.string(),
|
||||
name: z.string().nullish(),
|
||||
online: z.boolean().nullish(),
|
||||
uptime: UptimeSchema().nullish(),
|
||||
@@ -851,6 +931,15 @@ export function UserSchema(): z.ZodObject<Properties<User>> {
|
||||
})
|
||||
}
|
||||
|
||||
export function UserAccountSchema(): z.ZodObject<Properties<UserAccount>> {
|
||||
return z.object({
|
||||
description: z.string(),
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
roles: z.string()
|
||||
})
|
||||
}
|
||||
|
||||
export function VarsSchema(): z.ZodObject<Properties<Vars>> {
|
||||
return z.object({
|
||||
__typename: z.literal('Vars').optional(),
|
||||
@@ -884,6 +973,7 @@ export function VarsSchema(): z.ZodObject<Properties<Vars>> {
|
||||
fuseRememberDefault: z.string().nullish(),
|
||||
fuseRememberStatus: z.string().nullish(),
|
||||
hideDotFiles: z.boolean().nullish(),
|
||||
id: z.string(),
|
||||
joinStatus: z.string().nullish(),
|
||||
localMaster: z.boolean().nullish(),
|
||||
localTld: z.string().nullish(),
|
||||
|
||||
@@ -20,9 +20,25 @@ export type Scalars = {
|
||||
JSON: { input: { [key: string]: any }; output: { [key: string]: any }; }
|
||||
Long: { input: number; output: number; }
|
||||
Port: { input: number; output: number; }
|
||||
URL: { input: URL; output: URL; }
|
||||
UUID: { input: string; output: string; }
|
||||
};
|
||||
|
||||
export type AccessUrl = {
|
||||
__typename?: 'AccessUrl';
|
||||
ipv4?: Maybe<Scalars['URL']['output']>;
|
||||
ipv6?: Maybe<Scalars['URL']['output']>;
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
type: URL_TYPE;
|
||||
};
|
||||
|
||||
export type AccessUrlInput = {
|
||||
ipv4?: InputMaybe<Scalars['URL']['input']>;
|
||||
ipv6?: InputMaybe<Scalars['URL']['input']>;
|
||||
name?: InputMaybe<Scalars['String']['input']>;
|
||||
type: URL_TYPE;
|
||||
};
|
||||
|
||||
export type AllowedOriginInput = {
|
||||
origins: Array<Scalars['String']['input']>;
|
||||
};
|
||||
@@ -42,7 +58,7 @@ export type ApiKeyResponse = {
|
||||
valid: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type ArrayType = {
|
||||
export type ArrayType = Node & {
|
||||
__typename?: 'Array';
|
||||
/** Current boot disk */
|
||||
boot?: Maybe<ArrayDisk>;
|
||||
@@ -52,6 +68,7 @@ export type ArrayType = {
|
||||
capacity: ArrayCapacity;
|
||||
/** Data disks in the current array */
|
||||
disks: Array<ArrayDisk>;
|
||||
id: Scalars['ID']['output'];
|
||||
/** Parity disks in the current array */
|
||||
parities: Array<ArrayDisk>;
|
||||
/** Array state after this query/mutation */
|
||||
@@ -232,9 +249,10 @@ export type CloudResponse = {
|
||||
status: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Config = {
|
||||
export type Config = Node & {
|
||||
__typename?: 'Config';
|
||||
error?: Maybe<ConfigErrorState>;
|
||||
id: Scalars['ID']['output'];
|
||||
valid?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
@@ -245,6 +263,12 @@ export enum ConfigErrorState {
|
||||
WITHDRAWN = 'WITHDRAWN'
|
||||
}
|
||||
|
||||
export type Connect = Node & {
|
||||
__typename?: 'Connect';
|
||||
dynamicRemoteAccess: DynamicRemoteAccessStatus;
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type ConnectSignInInput = {
|
||||
accessToken?: InputMaybe<Scalars['String']['input']>;
|
||||
apiKey: Scalars['String']['input'];
|
||||
@@ -327,7 +351,8 @@ export type Disk = {
|
||||
export enum DiskFsType {
|
||||
BTRFS = 'btrfs',
|
||||
VFAT = 'vfat',
|
||||
XFS = 'xfs'
|
||||
XFS = 'xfs',
|
||||
ZFS = 'zfs'
|
||||
}
|
||||
|
||||
export enum DiskInterfaceType {
|
||||
@@ -358,6 +383,7 @@ export type Display = {
|
||||
dashapps?: Maybe<Scalars['String']['output']>;
|
||||
date?: Maybe<Scalars['String']['output']>;
|
||||
hot?: Maybe<Scalars['Int']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
locale?: Maybe<Scalars['String']['output']>;
|
||||
max?: Maybe<Scalars['Int']['output']>;
|
||||
number?: Maybe<Scalars['String']['output']>;
|
||||
@@ -374,6 +400,13 @@ export type Display = {
|
||||
wwn?: Maybe<Scalars['Boolean']['output']>;
|
||||
};
|
||||
|
||||
export type Docker = Node & {
|
||||
__typename?: 'Docker';
|
||||
containers?: Maybe<Array<DockerContainer>>;
|
||||
id: Scalars['ID']['output'];
|
||||
networks?: Maybe<Array<DockerNetwork>>;
|
||||
};
|
||||
|
||||
export type DockerContainer = {
|
||||
__typename?: 'DockerContainer';
|
||||
autoStart: Scalars['Boolean']['output'];
|
||||
@@ -413,6 +446,24 @@ export type DockerNetwork = {
|
||||
scope?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type DynamicRemoteAccessStatus = {
|
||||
__typename?: 'DynamicRemoteAccessStatus';
|
||||
enabledType: DynamicRemoteAccessType;
|
||||
error?: Maybe<Scalars['String']['output']>;
|
||||
runningType: DynamicRemoteAccessType;
|
||||
};
|
||||
|
||||
export enum DynamicRemoteAccessType {
|
||||
DISABLED = 'DISABLED',
|
||||
STATIC = 'STATIC',
|
||||
UPNP = 'UPNP'
|
||||
}
|
||||
|
||||
export type EnableDynamicRemoteAccessInput = {
|
||||
enabled: Scalars['Boolean']['input'];
|
||||
url: AccessUrlInput;
|
||||
};
|
||||
|
||||
export type Flash = {
|
||||
__typename?: 'Flash';
|
||||
guid?: Maybe<Scalars['String']['output']>;
|
||||
@@ -437,7 +488,7 @@ export enum Importance {
|
||||
WARNING = 'WARNING'
|
||||
}
|
||||
|
||||
export type Info = {
|
||||
export type Info = Node & {
|
||||
__typename?: 'Info';
|
||||
/** Count of docker containers */
|
||||
apps?: Maybe<InfoApps>;
|
||||
@@ -445,11 +496,13 @@ export type Info = {
|
||||
cpu?: Maybe<InfoCpu>;
|
||||
devices?: Maybe<Devices>;
|
||||
display?: Maybe<Display>;
|
||||
id: Scalars['ID']['output'];
|
||||
/** Machine ID */
|
||||
machineId?: Maybe<Scalars['ID']['output']>;
|
||||
memory?: Maybe<InfoMemory>;
|
||||
os?: Maybe<Os>;
|
||||
system?: Maybe<System>;
|
||||
time: Scalars['DateTime']['output'];
|
||||
versions?: Maybe<Versions>;
|
||||
};
|
||||
|
||||
@@ -576,6 +629,7 @@ export type Mutation = {
|
||||
connectSignOut: Scalars['Boolean']['output'];
|
||||
/** Delete a user */
|
||||
deleteUser?: Maybe<User>;
|
||||
enableDynamicRemoteAccess: Scalars['Boolean']['output'];
|
||||
/** Get an existing API key */
|
||||
getApiKey?: Maybe<ApiKey>;
|
||||
login?: Maybe<Scalars['String']['output']>;
|
||||
@@ -587,7 +641,6 @@ export type Mutation = {
|
||||
removeDiskFromArray?: Maybe<ArrayType>;
|
||||
/** Resume parity check */
|
||||
resumeParityCheck?: Maybe<Scalars['JSON']['output']>;
|
||||
sendNotification?: Maybe<Notification>;
|
||||
setAdditionalAllowedOrigins: Array<Scalars['String']['output']>;
|
||||
setupRemoteAccess: Scalars['Boolean']['output'];
|
||||
shutdown?: Maybe<Scalars['String']['output']>;
|
||||
@@ -634,6 +687,11 @@ export type MutationdeleteUserArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationenableDynamicRemoteAccessArgs = {
|
||||
input: EnableDynamicRemoteAccessInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationgetApiKeyArgs = {
|
||||
input?: InputMaybe<authenticateInput>;
|
||||
name: Scalars['String']['input'];
|
||||
@@ -656,11 +714,6 @@ export type MutationremoveDiskFromArrayArgs = {
|
||||
};
|
||||
|
||||
|
||||
export type MutationsendNotificationArgs = {
|
||||
notification: NotificationInput;
|
||||
};
|
||||
|
||||
|
||||
export type MutationsetAdditionalAllowedOriginsArgs = {
|
||||
input: AllowedOriginInput;
|
||||
};
|
||||
@@ -686,10 +739,12 @@ export type MutationupdateApikeyArgs = {
|
||||
name: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export type Network = {
|
||||
export type Network = Node & {
|
||||
__typename?: 'Network';
|
||||
accessUrls?: Maybe<Array<AccessUrl>>;
|
||||
carrierChanges?: Maybe<Scalars['String']['output']>;
|
||||
duplex?: Maybe<Scalars['String']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
iface?: Maybe<Scalars['String']['output']>;
|
||||
ifaceName?: Maybe<Scalars['String']['output']>;
|
||||
internal?: Maybe<Scalars['String']['output']>;
|
||||
@@ -702,6 +757,10 @@ export type Network = {
|
||||
type?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type Node = {
|
||||
id: Scalars['ID']['output'];
|
||||
};
|
||||
|
||||
export type Notification = {
|
||||
__typename?: 'Notification';
|
||||
description: Scalars['String']['output'];
|
||||
@@ -862,28 +921,34 @@ export type Query = {
|
||||
array: ArrayType;
|
||||
cloud?: Maybe<Cloud>;
|
||||
config: Config;
|
||||
connect: Connect;
|
||||
/** Single disk */
|
||||
disk?: Maybe<Disk>;
|
||||
/** Mulitiple disks */
|
||||
disks: Array<Maybe<Disk>>;
|
||||
display?: Maybe<Display>;
|
||||
docker: Docker;
|
||||
/** All Docker containers */
|
||||
dockerContainers: Array<DockerContainer>;
|
||||
/** Docker network */
|
||||
dockerNetwork: DockerNetwork;
|
||||
/** All Docker networks */
|
||||
dockerNetworks: Array<Maybe<DockerNetwork>>;
|
||||
extraAllowedOrigins: Array<Scalars['String']['output']>;
|
||||
flash?: Maybe<Flash>;
|
||||
info?: Maybe<Info>;
|
||||
/** Current user account */
|
||||
me?: Maybe<Me>;
|
||||
network?: Maybe<Network>;
|
||||
notifications: Array<Notification>;
|
||||
online?: Maybe<Scalars['Boolean']['output']>;
|
||||
owner?: Maybe<Owner>;
|
||||
parityHistory?: Maybe<Array<Maybe<ParityCheck>>>;
|
||||
registration?: Maybe<Registration>;
|
||||
remoteAccess: RemoteAccess;
|
||||
server?: Maybe<Server>;
|
||||
servers: Array<Server>;
|
||||
services: Array<Service>;
|
||||
/** Network Shares */
|
||||
shares?: Maybe<Array<Maybe<Share>>>;
|
||||
unassignedDevices?: Maybe<Array<Maybe<UnassignedDevice>>>;
|
||||
@@ -938,6 +1003,7 @@ export type Registration = {
|
||||
keyFile?: Maybe<KeyFile>;
|
||||
state?: Maybe<RegistrationState>;
|
||||
type?: Maybe<registrationType>;
|
||||
updateExpiration?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export enum RegistrationState {
|
||||
@@ -988,6 +1054,13 @@ export type RelayResponse = {
|
||||
timeout?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type RemoteAccess = {
|
||||
__typename?: 'RemoteAccess';
|
||||
accessType: WAN_ACCESS_TYPE;
|
||||
forwardType?: Maybe<WAN_FORWARD_TYPE>;
|
||||
port?: Maybe<Scalars['Port']['output']>;
|
||||
};
|
||||
|
||||
export type Server = {
|
||||
__typename?: 'Server';
|
||||
apikey: Scalars['String']['output'];
|
||||
@@ -1007,8 +1080,9 @@ export enum ServerStatus {
|
||||
ONLINE = 'online'
|
||||
}
|
||||
|
||||
export type Service = {
|
||||
export type Service = Node & {
|
||||
__typename?: 'Service';
|
||||
id: Scalars['ID']['output'];
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
online?: Maybe<Scalars['Boolean']['output']>;
|
||||
uptime?: Maybe<Uptime>;
|
||||
@@ -1122,6 +1196,15 @@ export enum Theme {
|
||||
WHITE = 'white'
|
||||
}
|
||||
|
||||
export enum URL_TYPE {
|
||||
DEFAULT = 'DEFAULT',
|
||||
LAN = 'LAN',
|
||||
MDNS = 'MDNS',
|
||||
OTHER = 'OTHER',
|
||||
WAN = 'WAN',
|
||||
WIREGUARD = 'WIREGUARD'
|
||||
}
|
||||
|
||||
export type UnassignedDevice = {
|
||||
__typename?: 'UnassignedDevice';
|
||||
devlinks?: Maybe<Scalars['String']['output']>;
|
||||
@@ -1208,7 +1291,7 @@ export type UserAccount = {
|
||||
roles: Scalars['String']['output'];
|
||||
};
|
||||
|
||||
export type Vars = {
|
||||
export type Vars = Node & {
|
||||
__typename?: 'Vars';
|
||||
bindMgt?: Maybe<Scalars['Boolean']['output']>;
|
||||
cacheNumDevices?: Maybe<Scalars['Int']['output']>;
|
||||
@@ -1242,6 +1325,7 @@ export type Vars = {
|
||||
fuseRememberDefault?: Maybe<Scalars['String']['output']>;
|
||||
fuseRememberStatus?: Maybe<Scalars['String']['output']>;
|
||||
hideDotFiles?: Maybe<Scalars['Boolean']['output']>;
|
||||
id: Scalars['ID']['output'];
|
||||
joinStatus?: Maybe<Scalars['String']['output']>;
|
||||
localMaster?: Maybe<Scalars['Boolean']['output']>;
|
||||
localTld?: Maybe<Scalars['String']['output']>;
|
||||
@@ -1566,11 +1650,14 @@ export type DirectiveResolverFn<TResult = {}, TParent = {}, TContext = {}, TArgs
|
||||
|
||||
/** Mapping of interface types */
|
||||
export type ResolversInterfaceTypes<RefType extends Record<string, unknown>> = ResolversObject<{
|
||||
Node: ( ArrayType ) | ( Config ) | ( Connect ) | ( Docker ) | ( Info ) | ( Network ) | ( Service ) | ( Vars );
|
||||
UserAccount: ( Me ) | ( User );
|
||||
}>;
|
||||
|
||||
/** Mapping between all available schema types and the resolvers types */
|
||||
export type ResolversTypes = ResolversObject<{
|
||||
AccessUrl: ResolverTypeWrapper<AccessUrl>;
|
||||
AccessUrlInput: AccessUrlInput;
|
||||
AllowedOriginInput: AllowedOriginInput;
|
||||
ApiKey: ResolverTypeWrapper<ApiKey>;
|
||||
ApiKeyResponse: ResolverTypeWrapper<ApiKeyResponse>;
|
||||
@@ -1590,6 +1677,7 @@ export type ResolversTypes = ResolversObject<{
|
||||
CloudResponse: ResolverTypeWrapper<CloudResponse>;
|
||||
Config: ResolverTypeWrapper<Config>;
|
||||
ConfigErrorState: ConfigErrorState;
|
||||
Connect: ResolverTypeWrapper<Connect>;
|
||||
ConnectSignInInput: ConnectSignInInput;
|
||||
ConnectUserInfoInput: ConnectUserInfoInput;
|
||||
ContainerHostConfig: ResolverTypeWrapper<ContainerHostConfig>;
|
||||
@@ -1605,8 +1693,12 @@ export type ResolversTypes = ResolversObject<{
|
||||
DiskPartition: ResolverTypeWrapper<DiskPartition>;
|
||||
DiskSmartStatus: DiskSmartStatus;
|
||||
Display: ResolverTypeWrapper<Display>;
|
||||
Docker: ResolverTypeWrapper<Docker>;
|
||||
DockerContainer: ResolverTypeWrapper<DockerContainer>;
|
||||
DockerNetwork: ResolverTypeWrapper<DockerNetwork>;
|
||||
DynamicRemoteAccessStatus: ResolverTypeWrapper<DynamicRemoteAccessStatus>;
|
||||
DynamicRemoteAccessType: DynamicRemoteAccessType;
|
||||
EnableDynamicRemoteAccessInput: EnableDynamicRemoteAccessInput;
|
||||
Flash: ResolverTypeWrapper<Flash>;
|
||||
Float: ResolverTypeWrapper<Scalars['Float']['output']>;
|
||||
Gpu: ResolverTypeWrapper<Gpu>;
|
||||
@@ -1629,6 +1721,7 @@ export type ResolversTypes = ResolversObject<{
|
||||
Mount: ResolverTypeWrapper<Mount>;
|
||||
Mutation: ResolverTypeWrapper<{}>;
|
||||
Network: ResolverTypeWrapper<Network>;
|
||||
Node: ResolverTypeWrapper<ResolversInterfaceTypes<ResolversTypes>['Node']>;
|
||||
Notification: ResolverTypeWrapper<Notification>;
|
||||
NotificationFilter: NotificationFilter;
|
||||
NotificationInput: NotificationInput;
|
||||
@@ -1644,6 +1737,7 @@ export type ResolversTypes = ResolversObject<{
|
||||
Registration: ResolverTypeWrapper<Registration>;
|
||||
RegistrationState: RegistrationState;
|
||||
RelayResponse: ResolverTypeWrapper<RelayResponse>;
|
||||
RemoteAccess: ResolverTypeWrapper<RemoteAccess>;
|
||||
Server: ResolverTypeWrapper<Server>;
|
||||
ServerStatus: ServerStatus;
|
||||
Service: ResolverTypeWrapper<Service>;
|
||||
@@ -1654,6 +1748,8 @@ export type ResolversTypes = ResolversObject<{
|
||||
System: ResolverTypeWrapper<System>;
|
||||
Temperature: Temperature;
|
||||
Theme: Theme;
|
||||
URL: ResolverTypeWrapper<Scalars['URL']['output']>;
|
||||
URL_TYPE: URL_TYPE;
|
||||
UUID: ResolverTypeWrapper<Scalars['UUID']['output']>;
|
||||
UnassignedDevice: ResolverTypeWrapper<UnassignedDevice>;
|
||||
Uptime: ResolverTypeWrapper<Uptime>;
|
||||
@@ -1681,6 +1777,8 @@ export type ResolversTypes = ResolversObject<{
|
||||
|
||||
/** Mapping between all available schema types and the resolvers parents */
|
||||
export type ResolversParentTypes = ResolversObject<{
|
||||
AccessUrl: AccessUrl;
|
||||
AccessUrlInput: AccessUrlInput;
|
||||
AllowedOriginInput: AllowedOriginInput;
|
||||
ApiKey: ApiKey;
|
||||
ApiKeyResponse: ApiKeyResponse;
|
||||
@@ -1694,6 +1792,7 @@ export type ResolversParentTypes = ResolversObject<{
|
||||
Cloud: Cloud;
|
||||
CloudResponse: CloudResponse;
|
||||
Config: Config;
|
||||
Connect: Connect;
|
||||
ConnectSignInInput: ConnectSignInInput;
|
||||
ConnectUserInfoInput: ConnectUserInfoInput;
|
||||
ContainerHostConfig: ContainerHostConfig;
|
||||
@@ -1704,8 +1803,11 @@ export type ResolversParentTypes = ResolversObject<{
|
||||
Disk: Disk;
|
||||
DiskPartition: DiskPartition;
|
||||
Display: Display;
|
||||
Docker: Docker;
|
||||
DockerContainer: DockerContainer;
|
||||
DockerNetwork: DockerNetwork;
|
||||
DynamicRemoteAccessStatus: DynamicRemoteAccessStatus;
|
||||
EnableDynamicRemoteAccessInput: EnableDynamicRemoteAccessInput;
|
||||
Flash: Flash;
|
||||
Float: Scalars['Float']['output'];
|
||||
Gpu: Gpu;
|
||||
@@ -1724,6 +1826,7 @@ export type ResolversParentTypes = ResolversObject<{
|
||||
Mount: Mount;
|
||||
Mutation: {};
|
||||
Network: Network;
|
||||
Node: ResolversInterfaceTypes<ResolversParentTypes>['Node'];
|
||||
Notification: Notification;
|
||||
NotificationFilter: NotificationFilter;
|
||||
NotificationInput: NotificationInput;
|
||||
@@ -1737,6 +1840,7 @@ export type ResolversParentTypes = ResolversObject<{
|
||||
Query: {};
|
||||
Registration: Registration;
|
||||
RelayResponse: RelayResponse;
|
||||
RemoteAccess: RemoteAccess;
|
||||
Server: Server;
|
||||
Service: Service;
|
||||
SetupRemoteAccessInput: SetupRemoteAccessInput;
|
||||
@@ -1744,6 +1848,7 @@ export type ResolversParentTypes = ResolversObject<{
|
||||
String: Scalars['String']['output'];
|
||||
Subscription: {};
|
||||
System: System;
|
||||
URL: Scalars['URL']['output'];
|
||||
UUID: Scalars['UUID']['output'];
|
||||
UnassignedDevice: UnassignedDevice;
|
||||
Uptime: Uptime;
|
||||
@@ -1764,6 +1869,14 @@ export type ResolversParentTypes = ResolversObject<{
|
||||
usersInput: usersInput;
|
||||
}>;
|
||||
|
||||
export type AccessUrlResolvers<ContextType = Context, ParentType extends ResolversParentTypes['AccessUrl'] = ResolversParentTypes['AccessUrl']> = ResolversObject<{
|
||||
ipv4?: Resolver<Maybe<ResolversTypes['URL']>, ParentType, ContextType>;
|
||||
ipv6?: Resolver<Maybe<ResolversTypes['URL']>, ParentType, ContextType>;
|
||||
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
type?: Resolver<ResolversTypes['URL_TYPE'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type ApiKeyResolvers<ContextType = Context, ParentType extends ResolversParentTypes['ApiKey'] = ResolversParentTypes['ApiKey']> = ResolversObject<{
|
||||
description?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
expiresAt?: Resolver<ResolversTypes['Long'], ParentType, ContextType>;
|
||||
@@ -1784,6 +1897,7 @@ export type ArrayResolvers<ContextType = Context, ParentType extends ResolversPa
|
||||
caches?: Resolver<Array<ResolversTypes['ArrayDisk']>, ParentType, ContextType>;
|
||||
capacity?: Resolver<ResolversTypes['ArrayCapacity'], ParentType, ContextType>;
|
||||
disks?: Resolver<Array<ResolversTypes['ArrayDisk']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
parities?: Resolver<Array<ResolversTypes['ArrayDisk']>, ParentType, ContextType>;
|
||||
pendingState?: Resolver<Maybe<ResolversTypes['ArrayPendingState']>, ParentType, ContextType>;
|
||||
previousState?: Resolver<Maybe<ResolversTypes['ArrayState']>, ParentType, ContextType>;
|
||||
@@ -1866,10 +1980,17 @@ export type CloudResponseResolvers<ContextType = Context, ParentType extends Res
|
||||
|
||||
export type ConfigResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Config'] = ResolversParentTypes['Config']> = ResolversObject<{
|
||||
error?: Resolver<Maybe<ResolversTypes['ConfigErrorState']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
valid?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type ConnectResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Connect'] = ResolversParentTypes['Connect']> = ResolversObject<{
|
||||
dynamicRemoteAccess?: Resolver<ResolversTypes['DynamicRemoteAccessStatus'], ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type ContainerHostConfigResolvers<ContextType = Context, ParentType extends ResolversParentTypes['ContainerHostConfig'] = ResolversParentTypes['ContainerHostConfig']> = ResolversObject<{
|
||||
networkMode?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
@@ -1943,6 +2064,7 @@ export type DisplayResolvers<ContextType = Context, ParentType extends Resolvers
|
||||
dashapps?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
date?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
hot?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
locale?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
max?: Resolver<Maybe<ResolversTypes['Int']>, ParentType, ContextType>;
|
||||
number?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -1960,6 +2082,13 @@ export type DisplayResolvers<ContextType = Context, ParentType extends Resolvers
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type DockerResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Docker'] = ResolversParentTypes['Docker']> = ResolversObject<{
|
||||
containers?: Resolver<Maybe<Array<ResolversTypes['DockerContainer']>>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
networks?: Resolver<Maybe<Array<ResolversTypes['DockerNetwork']>>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type DockerContainerResolvers<ContextType = Context, ParentType extends ResolversParentTypes['DockerContainer'] = ResolversParentTypes['DockerContainer']> = ResolversObject<{
|
||||
autoStart?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
command?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
@@ -1998,6 +2127,13 @@ export type DockerNetworkResolvers<ContextType = Context, ParentType extends Res
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type DynamicRemoteAccessStatusResolvers<ContextType = Context, ParentType extends ResolversParentTypes['DynamicRemoteAccessStatus'] = ResolversParentTypes['DynamicRemoteAccessStatus']> = ResolversObject<{
|
||||
enabledType?: Resolver<ResolversTypes['DynamicRemoteAccessType'], ParentType, ContextType>;
|
||||
error?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
runningType?: Resolver<ResolversTypes['DynamicRemoteAccessType'], ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type FlashResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Flash'] = ResolversParentTypes['Flash']> = ResolversObject<{
|
||||
guid?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
product?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -2022,10 +2158,12 @@ export type InfoResolvers<ContextType = Context, ParentType extends ResolversPar
|
||||
cpu?: Resolver<Maybe<ResolversTypes['InfoCpu']>, ParentType, ContextType>;
|
||||
devices?: Resolver<Maybe<ResolversTypes['Devices']>, ParentType, ContextType>;
|
||||
display?: Resolver<Maybe<ResolversTypes['Display']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
machineId?: Resolver<Maybe<ResolversTypes['ID']>, ParentType, ContextType>;
|
||||
memory?: Resolver<Maybe<ResolversTypes['InfoMemory']>, ParentType, ContextType>;
|
||||
os?: Resolver<Maybe<ResolversTypes['Os']>, ParentType, ContextType>;
|
||||
system?: Resolver<Maybe<ResolversTypes['System']>, ParentType, ContextType>;
|
||||
time?: Resolver<ResolversTypes['DateTime'], ParentType, ContextType>;
|
||||
versions?: Resolver<Maybe<ResolversTypes['Versions']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
@@ -2134,6 +2272,7 @@ export type MutationResolvers<ContextType = Context, ParentType extends Resolver
|
||||
connectSignIn?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationconnectSignInArgs, 'input'>>;
|
||||
connectSignOut?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType>;
|
||||
deleteUser?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<MutationdeleteUserArgs, 'input'>>;
|
||||
enableDynamicRemoteAccess?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationenableDynamicRemoteAccessArgs, 'input'>>;
|
||||
getApiKey?: Resolver<Maybe<ResolversTypes['ApiKey']>, ParentType, ContextType, RequireFields<MutationgetApiKeyArgs, 'name'>>;
|
||||
login?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType, RequireFields<MutationloginArgs, 'password' | 'username'>>;
|
||||
mountArrayDisk?: Resolver<Maybe<ResolversTypes['Disk']>, ParentType, ContextType, RequireFields<MutationmountArrayDiskArgs, 'id'>>;
|
||||
@@ -2141,7 +2280,6 @@ export type MutationResolvers<ContextType = Context, ParentType extends Resolver
|
||||
reboot?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
removeDiskFromArray?: Resolver<Maybe<ResolversTypes['Array']>, ParentType, ContextType, Partial<MutationremoveDiskFromArrayArgs>>;
|
||||
resumeParityCheck?: Resolver<Maybe<ResolversTypes['JSON']>, ParentType, ContextType>;
|
||||
sendNotification?: Resolver<Maybe<ResolversTypes['Notification']>, ParentType, ContextType, RequireFields<MutationsendNotificationArgs, 'notification'>>;
|
||||
setAdditionalAllowedOrigins?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType, RequireFields<MutationsetAdditionalAllowedOriginsArgs, 'input'>>;
|
||||
setupRemoteAccess?: Resolver<ResolversTypes['Boolean'], ParentType, ContextType, RequireFields<MutationsetupRemoteAccessArgs, 'input'>>;
|
||||
shutdown?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -2153,8 +2291,10 @@ export type MutationResolvers<ContextType = Context, ParentType extends Resolver
|
||||
}>;
|
||||
|
||||
export type NetworkResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Network'] = ResolversParentTypes['Network']> = ResolversObject<{
|
||||
accessUrls?: Resolver<Maybe<Array<ResolversTypes['AccessUrl']>>, ParentType, ContextType>;
|
||||
carrierChanges?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
duplex?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
iface?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
ifaceName?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
internal?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -2168,6 +2308,11 @@ export type NetworkResolvers<ContextType = Context, ParentType extends Resolvers
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type NodeResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Node'] = ResolversParentTypes['Node']> = ResolversObject<{
|
||||
__resolveType: TypeResolveFn<'Array' | 'Config' | 'Connect' | 'Docker' | 'Info' | 'Network' | 'Service' | 'Vars', ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type NotificationResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Notification'] = ResolversParentTypes['Notification']> = ResolversObject<{
|
||||
description?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
@@ -2304,22 +2449,28 @@ export type QueryResolvers<ContextType = Context, ParentType extends ResolversPa
|
||||
array?: Resolver<ResolversTypes['Array'], ParentType, ContextType>;
|
||||
cloud?: Resolver<Maybe<ResolversTypes['Cloud']>, ParentType, ContextType>;
|
||||
config?: Resolver<ResolversTypes['Config'], ParentType, ContextType>;
|
||||
connect?: Resolver<ResolversTypes['Connect'], ParentType, ContextType>;
|
||||
disk?: Resolver<Maybe<ResolversTypes['Disk']>, ParentType, ContextType, RequireFields<QuerydiskArgs, 'id'>>;
|
||||
disks?: Resolver<Array<Maybe<ResolversTypes['Disk']>>, ParentType, ContextType>;
|
||||
display?: Resolver<Maybe<ResolversTypes['Display']>, ParentType, ContextType>;
|
||||
docker?: Resolver<ResolversTypes['Docker'], ParentType, ContextType>;
|
||||
dockerContainers?: Resolver<Array<ResolversTypes['DockerContainer']>, ParentType, ContextType, Partial<QuerydockerContainersArgs>>;
|
||||
dockerNetwork?: Resolver<ResolversTypes['DockerNetwork'], ParentType, ContextType, RequireFields<QuerydockerNetworkArgs, 'id'>>;
|
||||
dockerNetworks?: Resolver<Array<Maybe<ResolversTypes['DockerNetwork']>>, ParentType, ContextType, Partial<QuerydockerNetworksArgs>>;
|
||||
extraAllowedOrigins?: Resolver<Array<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
flash?: Resolver<Maybe<ResolversTypes['Flash']>, ParentType, ContextType>;
|
||||
info?: Resolver<Maybe<ResolversTypes['Info']>, ParentType, ContextType>;
|
||||
me?: Resolver<Maybe<ResolversTypes['Me']>, ParentType, ContextType>;
|
||||
network?: Resolver<Maybe<ResolversTypes['Network']>, ParentType, ContextType>;
|
||||
notifications?: Resolver<Array<ResolversTypes['Notification']>, ParentType, ContextType, RequireFields<QuerynotificationsArgs, 'filter'>>;
|
||||
online?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||
owner?: Resolver<Maybe<ResolversTypes['Owner']>, ParentType, ContextType>;
|
||||
parityHistory?: Resolver<Maybe<Array<Maybe<ResolversTypes['ParityCheck']>>>, ParentType, ContextType>;
|
||||
registration?: Resolver<Maybe<ResolversTypes['Registration']>, ParentType, ContextType>;
|
||||
remoteAccess?: Resolver<ResolversTypes['RemoteAccess'], ParentType, ContextType>;
|
||||
server?: Resolver<Maybe<ResolversTypes['Server']>, ParentType, ContextType>;
|
||||
servers?: Resolver<Array<ResolversTypes['Server']>, ParentType, ContextType>;
|
||||
services?: Resolver<Array<ResolversTypes['Service']>, ParentType, ContextType>;
|
||||
shares?: Resolver<Maybe<Array<Maybe<ResolversTypes['Share']>>>, ParentType, ContextType>;
|
||||
unassignedDevices?: Resolver<Maybe<Array<Maybe<ResolversTypes['UnassignedDevice']>>>, ParentType, ContextType>;
|
||||
user?: Resolver<Maybe<ResolversTypes['User']>, ParentType, ContextType, RequireFields<QueryuserArgs, 'id'>>;
|
||||
@@ -2334,6 +2485,7 @@ export type RegistrationResolvers<ContextType = Context, ParentType extends Reso
|
||||
keyFile?: Resolver<Maybe<ResolversTypes['KeyFile']>, ParentType, ContextType>;
|
||||
state?: Resolver<Maybe<ResolversTypes['RegistrationState']>, ParentType, ContextType>;
|
||||
type?: Resolver<Maybe<ResolversTypes['registrationType']>, ParentType, ContextType>;
|
||||
updateExpiration?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
@@ -2344,6 +2496,13 @@ export type RelayResponseResolvers<ContextType = Context, ParentType extends Res
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type RemoteAccessResolvers<ContextType = Context, ParentType extends ResolversParentTypes['RemoteAccess'] = ResolversParentTypes['RemoteAccess']> = ResolversObject<{
|
||||
accessType?: Resolver<ResolversTypes['WAN_ACCESS_TYPE'], ParentType, ContextType>;
|
||||
forwardType?: Resolver<Maybe<ResolversTypes['WAN_FORWARD_TYPE']>, ParentType, ContextType>;
|
||||
port?: Resolver<Maybe<ResolversTypes['Port']>, ParentType, ContextType>;
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export type ServerResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Server'] = ResolversParentTypes['Server']> = ResolversObject<{
|
||||
apikey?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
guid?: Resolver<ResolversTypes['String'], ParentType, ContextType>;
|
||||
@@ -2358,6 +2517,7 @@ export type ServerResolvers<ContextType = Context, ParentType extends ResolversP
|
||||
}>;
|
||||
|
||||
export type ServiceResolvers<ContextType = Context, ParentType extends ResolversParentTypes['Service'] = ResolversParentTypes['Service']> = ResolversObject<{
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
name?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
online?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||
uptime?: Resolver<Maybe<ResolversTypes['Uptime']>, ParentType, ContextType>;
|
||||
@@ -2423,6 +2583,10 @@ export type SystemResolvers<ContextType = Context, ParentType extends ResolversP
|
||||
__isTypeOf?: IsTypeOfResolverFn<ParentType, ContextType>;
|
||||
}>;
|
||||
|
||||
export interface URLScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['URL'], any> {
|
||||
name: 'URL';
|
||||
}
|
||||
|
||||
export interface UUIDScalarConfig extends GraphQLScalarTypeConfig<ResolversTypes['UUID'], any> {
|
||||
name: 'UUID';
|
||||
}
|
||||
@@ -2542,6 +2706,7 @@ export type VarsResolvers<ContextType = Context, ParentType extends ResolversPar
|
||||
fuseRememberDefault?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
fuseRememberStatus?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
hideDotFiles?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||
id?: Resolver<ResolversTypes['ID'], ParentType, ContextType>;
|
||||
joinStatus?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
localMaster?: Resolver<Maybe<ResolversTypes['Boolean']>, ParentType, ContextType>;
|
||||
localTld?: Resolver<Maybe<ResolversTypes['String']>, ParentType, ContextType>;
|
||||
@@ -2705,6 +2870,7 @@ export type WelcomeResolvers<ContextType = Context, ParentType extends Resolvers
|
||||
}>;
|
||||
|
||||
export type Resolvers<ContextType = Context> = ResolversObject<{
|
||||
AccessUrl?: AccessUrlResolvers<ContextType>;
|
||||
ApiKey?: ApiKeyResolvers<ContextType>;
|
||||
ApiKeyResponse?: ApiKeyResponseResolvers<ContextType>;
|
||||
Array?: ArrayResolvers<ContextType>;
|
||||
@@ -2716,6 +2882,7 @@ export type Resolvers<ContextType = Context> = ResolversObject<{
|
||||
Cloud?: CloudResolvers<ContextType>;
|
||||
CloudResponse?: CloudResponseResolvers<ContextType>;
|
||||
Config?: ConfigResolvers<ContextType>;
|
||||
Connect?: ConnectResolvers<ContextType>;
|
||||
ContainerHostConfig?: ContainerHostConfigResolvers<ContextType>;
|
||||
ContainerMount?: ContainerMountResolvers<ContextType>;
|
||||
ContainerPort?: ContainerPortResolvers<ContextType>;
|
||||
@@ -2724,8 +2891,10 @@ export type Resolvers<ContextType = Context> = ResolversObject<{
|
||||
Disk?: DiskResolvers<ContextType>;
|
||||
DiskPartition?: DiskPartitionResolvers<ContextType>;
|
||||
Display?: DisplayResolvers<ContextType>;
|
||||
Docker?: DockerResolvers<ContextType>;
|
||||
DockerContainer?: DockerContainerResolvers<ContextType>;
|
||||
DockerNetwork?: DockerNetworkResolvers<ContextType>;
|
||||
DynamicRemoteAccessStatus?: DynamicRemoteAccessStatusResolvers<ContextType>;
|
||||
Flash?: FlashResolvers<ContextType>;
|
||||
Gpu?: GpuResolvers<ContextType>;
|
||||
Info?: InfoResolvers<ContextType>;
|
||||
@@ -2741,6 +2910,7 @@ export type Resolvers<ContextType = Context> = ResolversObject<{
|
||||
Mount?: MountResolvers<ContextType>;
|
||||
Mutation?: MutationResolvers<ContextType>;
|
||||
Network?: NetworkResolvers<ContextType>;
|
||||
Node?: NodeResolvers<ContextType>;
|
||||
Notification?: NotificationResolvers<ContextType>;
|
||||
Os?: OsResolvers<ContextType>;
|
||||
Owner?: OwnerResolvers<ContextType>;
|
||||
@@ -2752,11 +2922,13 @@ export type Resolvers<ContextType = Context> = ResolversObject<{
|
||||
Query?: QueryResolvers<ContextType>;
|
||||
Registration?: RegistrationResolvers<ContextType>;
|
||||
RelayResponse?: RelayResponseResolvers<ContextType>;
|
||||
RemoteAccess?: RemoteAccessResolvers<ContextType>;
|
||||
Server?: ServerResolvers<ContextType>;
|
||||
Service?: ServiceResolvers<ContextType>;
|
||||
Share?: ShareResolvers<ContextType>;
|
||||
Subscription?: SubscriptionResolvers<ContextType>;
|
||||
System?: SystemResolvers<ContextType>;
|
||||
URL?: GraphQLScalarType;
|
||||
UUID?: GraphQLScalarType;
|
||||
UnassignedDevice?: UnassignedDeviceResolvers<ContextType>;
|
||||
Uptime?: UptimeResolvers<ContextType>;
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable */
|
||||
import type { ResultOf, DocumentTypeDecoration, TypedDocumentNode } from '@graphql-typed-document-node/core';
|
||||
import type { FragmentDefinitionNode } from 'graphql';
|
||||
import type { Incremental } from './graphql.js';
|
||||
|
||||
52
api/src/graphql/generated/client/gql.ts
Normal file
52
api/src/graphql/generated/client/gql.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
/* eslint-disable */
|
||||
import * as types from './graphql.js';
|
||||
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
|
||||
/**
|
||||
* Map of all GraphQL operations in the project.
|
||||
*
|
||||
* This map has several performance disadvantages:
|
||||
* 1. It is not tree-shakeable, so it will include all operations in the project.
|
||||
* 2. It is not minifiable, so the string of a GraphQL query will be multiple times inside the bundle.
|
||||
* 3. It does not support dead code elimination, so it will add unused operations.
|
||||
*
|
||||
* Therefore it is highly recommended to use the babel or swc plugin for production.
|
||||
*/
|
||||
const documents = {
|
||||
"\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n": types.sendRemoteGraphQLResponseDocument,
|
||||
"\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n": types.RemoteGraphQLEventFragmentFragmentDoc,
|
||||
"\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteGraphQLEventFragment\n }\n }\n": types.eventsDocument,
|
||||
};
|
||||
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*
|
||||
*
|
||||
* @example
|
||||
* ```ts
|
||||
* const query = graphql(`query GetUser($id: ID!) { user(id: $id) { name } }`);
|
||||
* ```
|
||||
*
|
||||
* The query argument is unknown!
|
||||
* Please regenerate the types.
|
||||
*/
|
||||
export function graphql(source: string): unknown;
|
||||
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n"): (typeof documents)["\n mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {\n remoteGraphQLResponse(input: $input)\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n"): (typeof documents)["\n fragment RemoteGraphQLEventFragment on RemoteGraphQLEvent {\n remoteGraphQLEventData: data {\n type\n body\n sha256\n }\n }\n"];
|
||||
/**
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteGraphQLEventFragment\n }\n }\n"): (typeof documents)["\n subscription events {\n events {\n __typename\n ... on ClientConnectedEvent {\n connectedData: data {\n type\n version\n apiKey\n }\n connectedEvent: type\n }\n ... on ClientDisconnectedEvent {\n disconnectedData: data {\n type\n version\n apiKey\n }\n disconnectedEvent: type\n }\n ...RemoteGraphQLEventFragment\n }\n }\n"];
|
||||
|
||||
export function graphql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
}
|
||||
|
||||
export type DocumentType<TDocumentNode extends DocumentNode<any, any>> = TDocumentNode extends DocumentNode< infer TType, any> ? TType : never;
|
||||
@@ -468,6 +468,7 @@ export type Query = {
|
||||
ksServers: Array<KsServerDetails>;
|
||||
online?: Maybe<Scalars['Boolean']['output']>;
|
||||
remoteQuery: Scalars['String']['output'];
|
||||
serverStatus: ServerStatusResponse;
|
||||
servers: Array<Maybe<Server>>;
|
||||
status?: Maybe<ServerStatus>;
|
||||
};
|
||||
@@ -482,6 +483,11 @@ export type QueryremoteQueryArgs = {
|
||||
input: RemoteGraphQLClientInput;
|
||||
};
|
||||
|
||||
|
||||
export type QueryserverStatusArgs = {
|
||||
apiKey: Scalars['String']['input'];
|
||||
};
|
||||
|
||||
export enum RegistrationState {
|
||||
/** Basic */
|
||||
BASIC = 'BASIC',
|
||||
@@ -554,6 +560,10 @@ export type RemoteAccessInput = {
|
||||
export type RemoteGraphQLClientInput = {
|
||||
apiKey: Scalars['String']['input'];
|
||||
body: Scalars['String']['input'];
|
||||
/** Time in milliseconds to wait for a response from the remote server (defaults to 15000) */
|
||||
timeout?: InputMaybe<Scalars['Int']['input']>;
|
||||
/** How long mothership should cache the result of this query in seconds, only valid on queries */
|
||||
ttl?: InputMaybe<Scalars['Int']['input']>;
|
||||
};
|
||||
|
||||
export type RemoteGraphQLEvent = {
|
||||
@@ -621,6 +631,13 @@ export enum ServerStatus {
|
||||
ONLINE = 'online'
|
||||
}
|
||||
|
||||
export type ServerStatusResponse = {
|
||||
__typename?: 'ServerStatusResponse';
|
||||
id: Scalars['ID']['output'];
|
||||
lastPublish?: Maybe<Scalars['String']['output']>;
|
||||
online: Scalars['Boolean']['output'];
|
||||
};
|
||||
|
||||
export type Service = {
|
||||
__typename?: 'Service';
|
||||
name?: Maybe<Scalars['String']['output']>;
|
||||
@@ -709,37 +726,6 @@ export type Vars = {
|
||||
regTy?: Maybe<Scalars['String']['output']>;
|
||||
};
|
||||
|
||||
export type updateDashboardMutationVariables = Exact<{
|
||||
data: DashboardInput;
|
||||
apiKey: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type updateDashboardMutation = { __typename?: 'Mutation', updateDashboard: { __typename?: 'Dashboard', apps?: { __typename?: 'DashboardApps', installed?: number | null } | null } };
|
||||
|
||||
export type sendNotificationMutationVariables = Exact<{
|
||||
notification: NotificationInput;
|
||||
apiKey: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type sendNotificationMutation = { __typename?: 'Mutation', sendNotification?: { __typename?: 'Notification', title?: string | null, subject?: string | null, description?: string | null, importance?: Importance | null, link?: string | null, status: NotificationStatus } | null };
|
||||
|
||||
export type updateNetworkMutationVariables = Exact<{
|
||||
data: NetworkInput;
|
||||
apiKey: Scalars['String']['input'];
|
||||
}>;
|
||||
|
||||
|
||||
export type updateNetworkMutation = { __typename?: 'Mutation', updateNetwork: { __typename?: 'Network', accessUrls?: Array<{ __typename?: 'AccessUrl', name?: string | null, type: URL_TYPE, ipv4?: URL | null, ipv6?: URL | null }> | null } };
|
||||
|
||||
export type sendRemoteAccessMutationMutationVariables = Exact<{
|
||||
remoteAccess: RemoteAccessInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type sendRemoteAccessMutationMutation = { __typename?: 'Mutation', remoteSession?: boolean | null };
|
||||
|
||||
export type sendRemoteGraphQLResponseMutationVariables = Exact<{
|
||||
input: RemoteGraphQLServerInput;
|
||||
}>;
|
||||
@@ -749,24 +735,14 @@ export type sendRemoteGraphQLResponseMutation = { __typename?: 'Mutation', remot
|
||||
|
||||
export type RemoteGraphQLEventFragmentFragment = { __typename?: 'RemoteGraphQLEvent', remoteGraphQLEventData: { __typename?: 'RemoteGraphQLEventData', type: RemoteGraphQLEventType, body: string, sha256: string } } & { ' $fragmentName'?: 'RemoteGraphQLEventFragmentFragment' };
|
||||
|
||||
export type RemoteAccessEventFragmentFragment = { __typename?: 'RemoteAccessEvent', type: EventType, data: { __typename?: 'RemoteAccessEventData', type: RemoteAccessEventActionType, apiKey: string, url?: { __typename?: 'AccessUrl', type: URL_TYPE, name?: string | null, ipv4?: URL | null, ipv6?: URL | null } | null } } & { ' $fragmentName'?: 'RemoteAccessEventFragmentFragment' };
|
||||
|
||||
export type eventsSubscriptionVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type eventsSubscription = { __typename?: 'Subscription', events?: Array<{ __typename: 'ClientConnectedEvent', connectedEvent: EventType, connectedData: { __typename?: 'ClientConnectionEventData', type: ClientType, version: string, apiKey: string } } | { __typename: 'ClientDisconnectedEvent', disconnectedEvent: EventType, disconnectedData: { __typename?: 'ClientConnectionEventData', type: ClientType, version: string, apiKey: string } } | { __typename: 'ClientPingEvent' } | (
|
||||
{ __typename: 'RemoteAccessEvent' }
|
||||
& { ' $fragmentRefs'?: { 'RemoteAccessEventFragmentFragment': RemoteAccessEventFragmentFragment } }
|
||||
) | (
|
||||
export type eventsSubscription = { __typename?: 'Subscription', events?: Array<{ __typename: 'ClientConnectedEvent', connectedEvent: EventType, connectedData: { __typename?: 'ClientConnectionEventData', type: ClientType, version: string, apiKey: string } } | { __typename: 'ClientDisconnectedEvent', disconnectedEvent: EventType, disconnectedData: { __typename?: 'ClientConnectionEventData', type: ClientType, version: string, apiKey: string } } | { __typename: 'ClientPingEvent' } | { __typename: 'RemoteAccessEvent' } | (
|
||||
{ __typename: 'RemoteGraphQLEvent' }
|
||||
& { ' $fragmentRefs'?: { 'RemoteGraphQLEventFragmentFragment': RemoteGraphQLEventFragmentFragment } }
|
||||
) | { __typename: 'UpdateEvent' }> | null };
|
||||
|
||||
export const RemoteGraphQLEventFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"remoteGraphQLEventData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"sha256"}}]}}]}}]} as unknown as DocumentNode<RemoteGraphQLEventFragmentFragment, unknown>;
|
||||
export const RemoteAccessEventFragmentFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteAccessEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteAccessEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"url"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"ipv4"}},{"kind":"Field","name":{"kind":"Name","value":"ipv6"}}]}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}}]}}]} as unknown as DocumentNode<RemoteAccessEventFragmentFragment, unknown>;
|
||||
export const updateDashboardDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateDashboard"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"DashboardInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateDashboard"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"directives":[{"kind":"Directive","name":{"kind":"Name","value":"auth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"apiKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}}}]}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"apps"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"installed"}}]}}]}}]}}]} as unknown as DocumentNode<updateDashboardMutation, updateDashboardMutationVariables>;
|
||||
export const sendNotificationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"sendNotification"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"notification"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NotificationInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"sendNotification"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"notification"},"value":{"kind":"Variable","name":{"kind":"Name","value":"notification"}}}],"directives":[{"kind":"Directive","name":{"kind":"Name","value":"auth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"apiKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}}}]}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"title"}},{"kind":"Field","name":{"kind":"Name","value":"subject"}},{"kind":"Field","name":{"kind":"Name","value":"description"}},{"kind":"Field","name":{"kind":"Name","value":"importance"}},{"kind":"Field","name":{"kind":"Name","value":"link"}},{"kind":"Field","name":{"kind":"Name","value":"status"}}]}}]}}]} as unknown as DocumentNode<sendNotificationMutation, sendNotificationMutationVariables>;
|
||||
export const updateNetworkDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"updateNetwork"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"data"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"NetworkInput"}}}},{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"String"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"updateNetwork"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"data"},"value":{"kind":"Variable","name":{"kind":"Name","value":"data"}}}],"directives":[{"kind":"Directive","name":{"kind":"Name","value":"auth"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"apiKey"},"value":{"kind":"Variable","name":{"kind":"Name","value":"apiKey"}}}]}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessUrls"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"ipv4"}},{"kind":"Field","name":{"kind":"Name","value":"ipv6"}}]}}]}}]}}]} as unknown as DocumentNode<updateNetworkMutation, updateNetworkMutationVariables>;
|
||||
export const sendRemoteAccessMutationDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"sendRemoteAccessMutation"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"remoteAccess"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteAccessInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"remoteSession"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"remoteAccess"},"value":{"kind":"Variable","name":{"kind":"Name","value":"remoteAccess"}}}]}]}}]} as unknown as DocumentNode<sendRemoteAccessMutationMutation, sendRemoteAccessMutationMutationVariables>;
|
||||
export const sendRemoteGraphQLResponseDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"sendRemoteGraphQLResponse"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLServerInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"remoteGraphQLResponse"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<sendRemoteGraphQLResponseMutation, sendRemoteGraphQLResponseMutationVariables>;
|
||||
export const eventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientConnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"connectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"connectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientDisconnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"disconnectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"disconnectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteAccessEventFragment"}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteAccessEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteAccessEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"url"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"name"}},{"kind":"Field","name":{"kind":"Name","value":"ipv4"}},{"kind":"Field","name":{"kind":"Name","value":"ipv6"}}]}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"remoteGraphQLEventData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"sha256"}}]}}]}}]} as unknown as DocumentNode<eventsSubscription, eventsSubscriptionVariables>;
|
||||
export const eventsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"subscription","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"events"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"__typename"}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientConnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"connectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"connectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"InlineFragment","typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"ClientDisconnectedEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"disconnectedData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"version"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"}}]}},{"kind":"Field","alias":{"kind":"Name","value":"disconnectedEvent"},"name":{"kind":"Name","value":"type"}}]}},{"kind":"FragmentSpread","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"RemoteGraphQLEventFragment"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"RemoteGraphQLEvent"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","alias":{"kind":"Name","value":"remoteGraphQLEventData"},"name":{"kind":"Name","value":"data"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"type"}},{"kind":"Field","name":{"kind":"Name","value":"body"}},{"kind":"Field","name":{"kind":"Name","value":"sha256"}}]}}]}}]} as unknown as DocumentNode<eventsSubscription, eventsSubscriptionVariables>;
|
||||
7
api/src/graphql/mothership/mutations.ts
Normal file
7
api/src/graphql/mothership/mutations.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { graphql } from '@app/graphql/generated/client/gql';
|
||||
|
||||
export const SEND_REMOTE_QUERY_RESPONSE = graphql(/* GraphQL */ `
|
||||
mutation sendRemoteGraphQLResponse($input: RemoteGraphQLServerInput!) {
|
||||
remoteGraphQLResponse(input: $input)
|
||||
}
|
||||
`);
|
||||
@@ -10,22 +10,6 @@ export const RemoteGraphQL_Fragment = graphql(/* GraphQL */ `
|
||||
}
|
||||
`);
|
||||
|
||||
export const RemoteAccess_Fragment = graphql(/* GraphQL */ `
|
||||
fragment RemoteAccessEventFragment on RemoteAccessEvent {
|
||||
type
|
||||
data {
|
||||
type
|
||||
url {
|
||||
type
|
||||
name
|
||||
ipv4
|
||||
ipv6
|
||||
}
|
||||
apiKey
|
||||
}
|
||||
}
|
||||
`);
|
||||
|
||||
export const EVENTS_SUBSCRIPTION = graphql(/* GraphQL */ `
|
||||
subscription events {
|
||||
events {
|
||||
@@ -46,7 +30,6 @@ export const EVENTS_SUBSCRIPTION = graphql(/* GraphQL */ `
|
||||
}
|
||||
disconnectedEvent: type
|
||||
}
|
||||
...RemoteAccessEventFragment
|
||||
...RemoteGraphQLEventFragment
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,91 +0,0 @@
|
||||
import { dashboardLogger } from '@app/core/log';
|
||||
import { generateData } from '@app/common/dashboard/generate-data';
|
||||
import { PUBSUB_CHANNEL, pubsub } from '@app/core/pubsub';
|
||||
import { getters, store } from '@app/store';
|
||||
import { saveDataPacket } from '@app/store/modules/dashboard';
|
||||
import { isEqual } from 'lodash';
|
||||
import { GraphQLClient } from '@app/mothership/graphql-client';
|
||||
import { SEND_DASHBOARD_PAYLOAD_MUTATION } from '../../mothership/mutations';
|
||||
import { type DashboardInput } from '../../generated/client/graphql';
|
||||
import { getDiff } from 'json-difference';
|
||||
import { DEBUG } from '@app/environment';
|
||||
import { isApolloError } from '@apollo/client/core';
|
||||
|
||||
const isNumberBetween = (min: number, max: number) => (num: number) => num > min && num < max;
|
||||
|
||||
const logAndReturn = <T>(returnValue: T, logLevel: 'info' | 'debug' | 'trace' | 'error', logLine: string, ...logParams: unknown[]): T => {
|
||||
dashboardLogger[logLevel](logLine, ...logParams);
|
||||
return returnValue;
|
||||
};
|
||||
|
||||
const ONE_MB = 1_024 * 1_024;
|
||||
const ONE_HUNDRED_MB = 100 * ONE_MB;
|
||||
|
||||
const canSendDataPacket = (dataPacket: DashboardInput | null) => {
|
||||
const { lastDataPacketTimestamp, lastDataPacket } = getters.dashboard();
|
||||
// Const { lastDataPacketTimestamp, lastDataPacketString, lastDataPacket } = dashboardStore;
|
||||
if (!dataPacket) return logAndReturn(false, 'error', 'Not sending update to dashboard becuase the data packet is empty');
|
||||
|
||||
// UPDATE - No data packet has been sent since boot
|
||||
if (!lastDataPacketTimestamp) return logAndReturn(true, 'debug', 'Sending update as none have been sent since the API started');
|
||||
|
||||
// NO_UPDATE - This is an exact copy of the last data packet
|
||||
if (isEqual(dataPacket, lastDataPacket)) return logAndReturn(false, 'trace', '[NETWORK] Skipping Update');
|
||||
|
||||
if (!lastDataPacket) return logAndReturn(true, 'debug', 'Sending update as no data packets have been stored in state yet');
|
||||
|
||||
const difference = getDiff(lastDataPacket, dataPacket);
|
||||
|
||||
const oldBytesFree = lastDataPacket.array?.capacity.bytes?.free;
|
||||
const newBytesFree = dataPacket.array?.capacity.bytes?.free;
|
||||
|
||||
if (oldBytesFree && newBytesFree && difference.added.length === 0 && difference.removed.length === 0 && difference.edited.length === 2) {
|
||||
// If size has changed less than 100 MB (and nothing else has changed), don't send an update
|
||||
|
||||
const numberBetweenCheck = isNumberBetween((Number(oldBytesFree) * ONE_MB) - ONE_HUNDRED_MB, (Number(oldBytesFree) * ONE_MB) + ONE_HUNDRED_MB);
|
||||
if (numberBetweenCheck(Number(newBytesFree) * ONE_MB)) {
|
||||
logAndReturn(false, 'info', 'Size has not changed enough to send a new dashboard payload');
|
||||
}
|
||||
}
|
||||
|
||||
return logAndReturn(true, 'trace', 'Sending update because the packets are not equal');
|
||||
};
|
||||
|
||||
export const publishToDashboard = async () => {
|
||||
try {
|
||||
const dataPacket = await generateData();
|
||||
// Only update data on change
|
||||
if (!canSendDataPacket(dataPacket)) return;
|
||||
|
||||
dashboardLogger.debug('New Data Packet Is: %o', dataPacket);
|
||||
|
||||
// Save new data packet
|
||||
store.dispatch(saveDataPacket({ lastDataPacket: dataPacket }));
|
||||
|
||||
// Publish the updated data
|
||||
dashboardLogger.trace({ dataPacket } , 'Publishing update');
|
||||
|
||||
// Update local clients
|
||||
await pubsub.publish(PUBSUB_CHANNEL.DASHBOARD, {
|
||||
dashboard: dataPacket,
|
||||
});
|
||||
if (dataPacket) {
|
||||
const client = GraphQLClient.getInstance();
|
||||
if (!client) {
|
||||
throw new Error('Invalid Client');
|
||||
}
|
||||
|
||||
// Update mothership
|
||||
await client.mutate({ mutation: SEND_DASHBOARD_PAYLOAD_MUTATION, variables: { apiKey: getters.config().remote.apikey, data: dataPacket } });
|
||||
} else {
|
||||
dashboardLogger.error('DataPacket Was Empty');
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error && isApolloError(error)) {
|
||||
dashboardLogger.error('Failed publishing with GQL Errors: %s, \nClient Errors: %s', error.graphQLErrors.map(error => error.message).join(','), error.clientErrors.join(', '));
|
||||
}
|
||||
|
||||
if (DEBUG) dashboardLogger.error(error);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,21 +1,14 @@
|
||||
import { GraphQLClient } from '@app/mothership/graphql-client';
|
||||
import { type Nginx } from '@app/core/types/states/nginx';
|
||||
import { type RootState, store, getters } from '@app/store';
|
||||
import { type RootState, store } from '@app/store';
|
||||
import {
|
||||
type NetworkInput,
|
||||
URL_TYPE,
|
||||
type AccessUrlInput,
|
||||
} from '@app/graphql/generated/client/graphql';
|
||||
import { dashboardLogger, logger } from '@app/core';
|
||||
import { isEqual } from 'lodash';
|
||||
import { SEND_NETWORK_MUTATION } from '@app/graphql/mothership/mutations';
|
||||
import { saveNetworkPacket } from '@app/store/modules/dashboard';
|
||||
import { ApolloError } from '@apollo/client/core/core.cjs';
|
||||
import { logger } from '@app/core';
|
||||
import {
|
||||
AccessUrlInputSchema,
|
||||
NetworkInputSchema,
|
||||
} from '@app/graphql/generated/client/validators';
|
||||
import { ZodError } from 'zod';
|
||||
import { type AccessUrl } from '@app/graphql/generated/api/types';
|
||||
|
||||
interface UrlForFieldInput {
|
||||
url: string;
|
||||
@@ -72,10 +65,6 @@ export type NginxUrlFields = Extract<
|
||||
| 'lanIp6'
|
||||
| 'lanName'
|
||||
| 'lanMdns'
|
||||
| 'lanFqdn'
|
||||
| 'lanFqdn6'
|
||||
| 'wanFqdn'
|
||||
| 'wanFqdn6'
|
||||
>;
|
||||
|
||||
/**
|
||||
@@ -126,10 +115,24 @@ export const getUrlForServer = ({
|
||||
);
|
||||
};
|
||||
|
||||
const getUrlTypeFromFqdn = (fqdnType: string): URL_TYPE => {
|
||||
switch (fqdnType) {
|
||||
case 'LAN':
|
||||
return URL_TYPE.LAN;
|
||||
case 'WAN':
|
||||
return URL_TYPE.WAN;
|
||||
case 'WG':
|
||||
return URL_TYPE.WIREGUARD;
|
||||
default:
|
||||
// HACK: This should be added as a new type (e.g. OTHER or CUSTOM)
|
||||
return URL_TYPE.WIREGUARD;
|
||||
}
|
||||
};
|
||||
|
||||
// eslint-disable-next-line complexity
|
||||
export const getServerIps = (
|
||||
state: RootState = store.getState()
|
||||
): { urls: AccessUrlInput[]; errors: Error[] } => {
|
||||
): { urls: AccessUrl[]; errors: Error[] } => {
|
||||
const { nginx } = state.emhttp;
|
||||
const {
|
||||
remote: { wanport },
|
||||
@@ -222,87 +225,22 @@ export const getServerIps = (
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Lan FQDN URL
|
||||
const lanFqdnUrl = getUrlForServer({ nginx, field: 'lanFqdn' });
|
||||
urls.push({
|
||||
name: 'LAN FQDN',
|
||||
type: URL_TYPE.LAN,
|
||||
ipv4: lanFqdnUrl,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
errors.push(error);
|
||||
} else {
|
||||
logger.warn('Uncaught error in network resolver', error);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// Lan FQDN6 URL
|
||||
const lanFqdn6Url = getUrlForServer({ nginx, field: 'lanFqdn6' });
|
||||
urls.push({
|
||||
name: 'LAN FQDNv6',
|
||||
type: URL_TYPE.LAN,
|
||||
ipv6: lanFqdn6Url,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
errors.push(error);
|
||||
} else {
|
||||
logger.warn('Uncaught error in network resolver', error);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// WAN FQDN URL
|
||||
const wanFqdnUrl = getUrlForField({
|
||||
url: nginx.wanFqdn,
|
||||
portSsl: Number(wanport || 443),
|
||||
});
|
||||
urls.push({
|
||||
name: 'WAN FQDN',
|
||||
type: URL_TYPE.WAN,
|
||||
ipv4: wanFqdnUrl,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
errors.push(error);
|
||||
} else {
|
||||
logger.warn('Uncaught error in network resolver', error);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
// WAN FQDN6 URL
|
||||
const wanFqdn6Url = getUrlForField({
|
||||
url: nginx.wanFqdn6,
|
||||
portSsl: Number(wanport),
|
||||
});
|
||||
urls.push({
|
||||
name: 'WAN FQDNv6',
|
||||
type: URL_TYPE.WAN,
|
||||
ipv6: wanFqdn6Url,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
errors.push(error);
|
||||
} else {
|
||||
logger.warn('Uncaught error in network resolver', error);
|
||||
}
|
||||
}
|
||||
|
||||
for (const wgFqdn of nginx.wgFqdns) {
|
||||
// Now Process the FQDN Urls
|
||||
nginx.fqdnUrls.forEach((fqdnUrl) => {
|
||||
try {
|
||||
// WG FQDN URL
|
||||
const wgFqdnUrl = getUrlForField({
|
||||
url: wgFqdn.fqdn,
|
||||
portSsl: nginx.httpsPort,
|
||||
const urlType = getUrlTypeFromFqdn(fqdnUrl.interface);
|
||||
const fqdnUrlToUse = getUrlForField({
|
||||
url: fqdnUrl.fqdn,
|
||||
portSsl:
|
||||
urlType === URL_TYPE.WAN
|
||||
? Number(wanport)
|
||||
: nginx.httpsPort,
|
||||
});
|
||||
|
||||
urls.push({
|
||||
name: `WG FQDN ${wgFqdn.id}`,
|
||||
type: URL_TYPE.WIREGUARD,
|
||||
ipv4: wgFqdnUrl,
|
||||
name: `FQDN ${fqdnUrl.interface}${fqdnUrl.id !== null ? ` ${fqdnUrl.id}` : ''}`,
|
||||
type: getUrlTypeFromFqdn(fqdnUrl.interface),
|
||||
ipv4: fqdnUrlToUse,
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
if (error instanceof Error) {
|
||||
@@ -311,7 +249,7 @@ export const getServerIps = (
|
||||
logger.warn('Uncaught error in network resolver', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const safeUrls = urls
|
||||
.map((url) => AccessUrlInputSchema().safeParse(url))
|
||||
@@ -326,66 +264,3 @@ export const getServerIps = (
|
||||
|
||||
return { urls: safeUrls, errors };
|
||||
};
|
||||
|
||||
export const publishNetwork = async () => {
|
||||
try {
|
||||
const client = GraphQLClient.getInstance();
|
||||
|
||||
const datapacket = getServerIps();
|
||||
if (datapacket.errors) {
|
||||
const zodErrors = datapacket.errors.filter(
|
||||
(error) => error instanceof ZodError
|
||||
);
|
||||
if (zodErrors.length) {
|
||||
dashboardLogger.warn(
|
||||
'Validation Errors Encountered with Network Payload: %s',
|
||||
zodErrors.map((error) => error.message).join(',')
|
||||
);
|
||||
}
|
||||
}
|
||||
const networkPacket: NetworkInput = { accessUrls: datapacket.urls };
|
||||
const validatedNetwork = NetworkInputSchema().parse(networkPacket);
|
||||
|
||||
const { lastNetworkPacket } = getters.dashboard();
|
||||
const { apikey: apiKey } = getters.config().remote;
|
||||
if (
|
||||
isEqual(
|
||||
JSON.stringify(lastNetworkPacket),
|
||||
JSON.stringify(validatedNetwork)
|
||||
)
|
||||
) {
|
||||
dashboardLogger.trace('[DASHBOARD] Skipping Update');
|
||||
} else if (client) {
|
||||
dashboardLogger.info(
|
||||
{ validatedNetwork },
|
||||
'Sending data packet for network'
|
||||
);
|
||||
const result = await client.mutate({
|
||||
mutation: SEND_NETWORK_MUTATION,
|
||||
variables: {
|
||||
apiKey,
|
||||
data: validatedNetwork,
|
||||
},
|
||||
});
|
||||
dashboardLogger.debug(
|
||||
{ result },
|
||||
'Sent network mutation with %s urls',
|
||||
datapacket.urls.length
|
||||
);
|
||||
store.dispatch(
|
||||
saveNetworkPacket({ lastNetworkPacket: validatedNetwork })
|
||||
);
|
||||
}
|
||||
} catch (error: unknown) {
|
||||
dashboardLogger.trace('ERROR', error);
|
||||
if (error instanceof ApolloError) {
|
||||
dashboardLogger.error(
|
||||
'Failed publishing with GQL Errors: %s, \nClient Errors: %s',
|
||||
error.graphQLErrors.map((error) => error.message).join(','),
|
||||
error.clientErrors.join(', ')
|
||||
);
|
||||
} else {
|
||||
dashboardLogger.error(error);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -31,7 +31,8 @@ input arrayDiskInput {
|
||||
slot: Int
|
||||
}
|
||||
|
||||
type Array {
|
||||
type Array implements Node {
|
||||
id: ID!
|
||||
"""Array state before this query/mutation"""
|
||||
previousState: ArrayState
|
||||
"""Array state after this query/mutation"""
|
||||
|
||||
@@ -3,6 +3,7 @@ scalar Long
|
||||
scalar UUID
|
||||
scalar DateTime
|
||||
scalar Port
|
||||
scalar URL
|
||||
|
||||
type Welcome {
|
||||
message: String!
|
||||
@@ -16,7 +17,6 @@ type Query {
|
||||
|
||||
type Mutation {
|
||||
login(username: String!, password: String!): String
|
||||
sendNotification(notification: NotificationInput!): Notification
|
||||
shutdown: String
|
||||
reboot: String
|
||||
}
|
||||
@@ -25,4 +25,9 @@ type Subscription {
|
||||
ping: String!
|
||||
info: Info!
|
||||
online: Boolean!
|
||||
}
|
||||
|
||||
# An object with a Globally Unique ID: see https://graphql.org/learn/global-object-identification/
|
||||
interface Node {
|
||||
id: ID!
|
||||
}
|
||||
20
api/src/graphql/schema/types/config/config.graphql
Normal file
20
api/src/graphql/schema/types/config/config.graphql
Normal file
@@ -0,0 +1,20 @@
|
||||
enum ConfigErrorState {
|
||||
UNKNOWN_ERROR
|
||||
INVALID
|
||||
NO_KEY_SERVER
|
||||
WITHDRAWN
|
||||
}
|
||||
|
||||
type Config implements Node {
|
||||
id: ID!
|
||||
valid: Boolean
|
||||
error: ConfigErrorState
|
||||
}
|
||||
|
||||
type Query {
|
||||
config: Config!
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
config: Config!
|
||||
}
|
||||
@@ -27,15 +27,52 @@ enum WAN_FORWARD_TYPE {
|
||||
STATIC
|
||||
}
|
||||
|
||||
type RemoteAccess {
|
||||
accessType: WAN_ACCESS_TYPE!
|
||||
forwardType: WAN_FORWARD_TYPE
|
||||
port: Port
|
||||
}
|
||||
|
||||
input SetupRemoteAccessInput {
|
||||
accessType: WAN_ACCESS_TYPE!
|
||||
forwardType: WAN_FORWARD_TYPE
|
||||
port: Port
|
||||
}
|
||||
|
||||
|
||||
|
||||
input EnableDynamicRemoteAccessInput {
|
||||
url: AccessUrlInput!
|
||||
enabled: Boolean!
|
||||
}
|
||||
|
||||
enum DynamicRemoteAccessType {
|
||||
STATIC
|
||||
UPNP
|
||||
DISABLED
|
||||
}
|
||||
|
||||
type DynamicRemoteAccessStatus {
|
||||
enabledType: DynamicRemoteAccessType!
|
||||
runningType: DynamicRemoteAccessType!
|
||||
error: String
|
||||
}
|
||||
|
||||
type Connect implements Node {
|
||||
id: ID!
|
||||
dynamicRemoteAccess: DynamicRemoteAccessStatus!
|
||||
}
|
||||
|
||||
type Query {
|
||||
remoteAccess: RemoteAccess!
|
||||
extraAllowedOrigins: [String!]!
|
||||
connect: Connect!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
connectSignIn(input: ConnectSignInInput!): Boolean!
|
||||
connectSignOut: Boolean!
|
||||
enableDynamicRemoteAccess(input: EnableDynamicRemoteAccessInput!): Boolean!
|
||||
setAdditionalAllowedOrigins(input: AllowedOriginInput!): [String!]!
|
||||
setupRemoteAccess(input: SetupRemoteAccessInput!): Boolean!
|
||||
}
|
||||
@@ -49,6 +49,7 @@ enum DiskFsType {
|
||||
xfs
|
||||
btrfs
|
||||
vfat
|
||||
zfs
|
||||
}
|
||||
|
||||
enum DiskInterfaceType {
|
||||
|
||||
19
api/src/graphql/schema/types/display/display.graphql
Normal file
19
api/src/graphql/schema/types/display/display.graphql
Normal file
@@ -0,0 +1,19 @@
|
||||
type Query {
|
||||
display: Display
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
display: Display
|
||||
}
|
||||
|
||||
type Display {
|
||||
id: ID!
|
||||
case: Case
|
||||
}
|
||||
|
||||
type Case {
|
||||
icon: String
|
||||
url: String
|
||||
error: String
|
||||
base64: String
|
||||
}
|
||||
9
api/src/graphql/schema/types/docker/docker.graphql
Normal file
9
api/src/graphql/schema/types/docker/docker.graphql
Normal file
@@ -0,0 +1,9 @@
|
||||
type Docker implements Node {
|
||||
id: ID!
|
||||
containers: [DockerContainer!]
|
||||
networks: [DockerNetwork!]
|
||||
}
|
||||
|
||||
type Query {
|
||||
docker: Docker!
|
||||
}
|
||||
3
api/src/graphql/schema/types/info/info.graphql
Normal file
3
api/src/graphql/schema/types/info/info.graphql
Normal file
@@ -0,0 +1,3 @@
|
||||
type Info implements Node {
|
||||
id: ID!
|
||||
}
|
||||
3
api/src/graphql/schema/types/info/time.graphql
Normal file
3
api/src/graphql/schema/types/info/time.graphql
Normal file
@@ -0,0 +1,3 @@
|
||||
type Info {
|
||||
time: DateTime!
|
||||
}
|
||||
32
api/src/graphql/schema/types/network/network.graphql
Normal file
32
api/src/graphql/schema/types/network/network.graphql
Normal file
@@ -0,0 +1,32 @@
|
||||
enum URL_TYPE {
|
||||
LAN
|
||||
WIREGUARD
|
||||
WAN
|
||||
MDNS
|
||||
OTHER
|
||||
DEFAULT
|
||||
}
|
||||
|
||||
|
||||
input AccessUrlInput {
|
||||
type: URL_TYPE!
|
||||
name: String
|
||||
ipv4: URL
|
||||
ipv6: URL
|
||||
}
|
||||
|
||||
type AccessUrl {
|
||||
type: URL_TYPE!
|
||||
name: String
|
||||
ipv4: URL
|
||||
ipv6: URL
|
||||
}
|
||||
|
||||
type Query {
|
||||
network: Network
|
||||
}
|
||||
|
||||
type Network implements Node {
|
||||
id: ID!
|
||||
accessUrls: [AccessUrl!]
|
||||
}
|
||||
@@ -22,10 +22,6 @@ input NotificationFilter {
|
||||
limit: Int!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
sendNotification(notification: NotificationInput!): Notification
|
||||
}
|
||||
|
||||
type Query {
|
||||
notifications(filter: NotificationFilter!): [Notification!]!
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
type Query {
|
||||
registration: Registration
|
||||
}
|
||||
|
||||
type Subscription {
|
||||
registration: Registration!
|
||||
}
|
||||
|
||||
type KeyFile {
|
||||
location: String
|
||||
contents: String
|
||||
}
|
||||
|
||||
type Registration {
|
||||
guid: String
|
||||
type: registrationType
|
||||
keyFile: KeyFile
|
||||
state: RegistrationState
|
||||
expiration: String
|
||||
updateExpiration: String
|
||||
}
|
||||
19
api/src/graphql/schema/types/services/service.graphql
Normal file
19
api/src/graphql/schema/types/services/service.graphql
Normal file
@@ -0,0 +1,19 @@
|
||||
type Subscription {
|
||||
service(name: String!): [Service!]
|
||||
}
|
||||
|
||||
type Uptime {
|
||||
timestamp: String
|
||||
}
|
||||
|
||||
type Service implements Node {
|
||||
id: ID!
|
||||
name: String
|
||||
online: Boolean
|
||||
uptime: Uptime
|
||||
version: String
|
||||
}
|
||||
|
||||
type Query {
|
||||
services: [Service!]!
|
||||
}
|
||||
@@ -13,7 +13,8 @@ enum ConfigErrorState {
|
||||
WITHDRAWN
|
||||
}
|
||||
|
||||
type Vars {
|
||||
type Vars implements Node {
|
||||
id: ID!
|
||||
"""
|
||||
Unraid version
|
||||
"""
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import 'reflect-metadata';
|
||||
import 'global-agent/bootstrap';
|
||||
|
||||
import { am } from 'am';
|
||||
import http from 'http';
|
||||
import https from 'https';
|
||||
@@ -100,7 +102,8 @@ void am(
|
||||
unlinkUnixPort();
|
||||
|
||||
shutdownApiEvent();
|
||||
process.exitCode = 0;
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
},
|
||||
async (error: NodeJS.ErrnoException) => {
|
||||
@@ -110,6 +113,6 @@ void am(
|
||||
}
|
||||
shutdownApiEvent();
|
||||
// Kill application
|
||||
process.exitCode = 1;
|
||||
process.exit(1);
|
||||
}
|
||||
);
|
||||
|
||||
88
api/src/mothership/jobs/ping-timeout-jobs.ts
Normal file
88
api/src/mothership/jobs/ping-timeout-jobs.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
import { KEEP_ALIVE_INTERVAL_MS, ONE_MINUTE_MS } from '@app/consts';
|
||||
import { minigraphLogger, mothershipLogger, remoteAccessLogger } from '@app/core/log';
|
||||
import { DynamicRemoteAccessType, MinigraphStatus } from '@app/graphql/generated/api/types';
|
||||
import { isAPIStateDataFullyLoaded } from '@app/mothership/graphql-client';
|
||||
import { setGraphqlConnectionStatus } from '@app/store/actions/set-minigraph-status';
|
||||
import { store } from '@app/store/index';
|
||||
import { setRemoteAccessRunningType } from '@app/store/modules/dynamic-remote-access';
|
||||
import { clearSubscription } from '@app/store/modules/remote-graphql';
|
||||
import { Cron, Expression, Initializer } from '@reflet/cron';
|
||||
|
||||
export class PingTimeoutJobs extends Initializer<typeof PingTimeoutJobs> {
|
||||
@Cron.PreventOverlap
|
||||
@Cron(Expression.EVERY_MINUTE)
|
||||
@Cron.Start
|
||||
async checkForPingTimeouts() {
|
||||
const state = store.getState()
|
||||
if (!isAPIStateDataFullyLoaded(state)) {
|
||||
mothershipLogger.warn(
|
||||
'State data not fully loaded, but job has been started'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check for ping timeouts in remote graphql events
|
||||
const subscriptionsToClear = state.remoteGraphQL.subscriptions.filter(
|
||||
(subscription) =>
|
||||
Date.now() - subscription.lastPing > KEEP_ALIVE_INTERVAL_MS
|
||||
);
|
||||
if (subscriptionsToClear.length > 0) {
|
||||
mothershipLogger.debug(
|
||||
`Clearing %s / %s subscriptions that are older than ${
|
||||
KEEP_ALIVE_INTERVAL_MS / 1_000 / 60
|
||||
} minutes`,
|
||||
subscriptionsToClear.length,
|
||||
state.remoteGraphQL.subscriptions.length
|
||||
);
|
||||
}
|
||||
|
||||
subscriptionsToClear.forEach((sub) =>
|
||||
store.dispatch(clearSubscription(sub.sha256))
|
||||
);
|
||||
|
||||
// Check for ping timeouts in mothership
|
||||
if (
|
||||
state.minigraph.lastPing &&
|
||||
Date.now() - state.minigraph.lastPing > KEEP_ALIVE_INTERVAL_MS &&
|
||||
state.minigraph.status === MinigraphStatus.CONNECTED
|
||||
) {
|
||||
minigraphLogger.error(
|
||||
`NO PINGS RECEIVED IN ${
|
||||
KEEP_ALIVE_INTERVAL_MS / 1_000 / 60
|
||||
} MINUTES, SOCKET MUST BE RECONNECTED`
|
||||
);
|
||||
store.dispatch(
|
||||
setGraphqlConnectionStatus({
|
||||
status: MinigraphStatus.PING_FAILURE,
|
||||
error: 'Ping Receive Exceeded Timeout',
|
||||
})
|
||||
);
|
||||
}
|
||||
// Check for ping timeouts from mothership events
|
||||
if (
|
||||
state.minigraph.selfDisconnectedSince &&
|
||||
Date.now() - state.minigraph.selfDisconnectedSince >
|
||||
KEEP_ALIVE_INTERVAL_MS &&
|
||||
state.minigraph.status === MinigraphStatus.CONNECTED
|
||||
) {
|
||||
minigraphLogger.error(
|
||||
`SELF DISCONNECTION EVENT NEVER CLEARED, SOCKET MUST BE RECONNECTED`
|
||||
);
|
||||
store.dispatch(
|
||||
setGraphqlConnectionStatus({
|
||||
status: MinigraphStatus.PING_FAILURE,
|
||||
error: 'Received disconnect event for own server',
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// Check for ping timeouts in remote access
|
||||
if (state.dynamicRemoteAccess.lastPing && Date.now() - state.dynamicRemoteAccess.lastPing > ONE_MINUTE_MS) {
|
||||
remoteAccessLogger.error(
|
||||
`NO PINGS RECEIVED IN 1 MINUTE, REMOTE ACCESS MUST BE DISABLED`
|
||||
);
|
||||
store.dispatch(setRemoteAccessRunningType(DynamicRemoteAccessType.DISABLED));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2,20 +2,14 @@
|
||||
import { minigraphLogger, mothershipLogger } from '@app/core/log';
|
||||
import { GraphQLClient } from './graphql-client';
|
||||
import { store } from '@app/store';
|
||||
import {
|
||||
startDashboardProducer,
|
||||
stopDashboardProducer,
|
||||
} from '@app/store/modules/dashboard';
|
||||
|
||||
import {
|
||||
EVENTS_SUBSCRIPTION,
|
||||
RemoteAccess_Fragment,
|
||||
RemoteGraphQL_Fragment,
|
||||
} from '@app/graphql/mothership/subscriptions';
|
||||
|
||||
import { ClientType } from '@app/graphql/generated/client/graphql';
|
||||
import { notNull } from '@app/utils';
|
||||
import { handleRemoteAccessEvent } from '@app/store/actions/handle-remote-access-event';
|
||||
import { useFragment } from '@app/graphql/generated/client/fragment-masking';
|
||||
import { handleRemoteGraphQLEvent } from '@app/store/actions/handle-remote-graphql-event';
|
||||
import {
|
||||
@@ -41,7 +35,10 @@ export const subscribeToEvents = async (apiKey: string) => {
|
||||
errors.join(',')
|
||||
);
|
||||
} else if (data) {
|
||||
mothershipLogger.trace({ events: data.events }, 'Got events from mothership');
|
||||
mothershipLogger.trace(
|
||||
{ events: data.events },
|
||||
'Got events from mothership'
|
||||
);
|
||||
|
||||
for (const event of data.events?.filter(notNull) ?? []) {
|
||||
switch (event.__typename) {
|
||||
@@ -57,15 +54,6 @@ export const subscribeToEvents = async (apiKey: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// Dashboard Connected to Mothership
|
||||
|
||||
if (
|
||||
type === ClientType.DASHBOARD &&
|
||||
apiKey === eventApiKey
|
||||
) {
|
||||
store.dispatch(startDashboardProducer());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -80,32 +68,6 @@ export const subscribeToEvents = async (apiKey: string) => {
|
||||
}
|
||||
}
|
||||
|
||||
// The dashboard was closed or went idle
|
||||
|
||||
if (
|
||||
type === ClientType.DASHBOARD &&
|
||||
apiKey === eventApiKey
|
||||
) {
|
||||
store.dispatch(stopDashboardProducer());
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case 'RemoteAccessEvent': {
|
||||
const eventAsRemoteAccessEvent = useFragment(
|
||||
RemoteAccess_Fragment,
|
||||
event
|
||||
);
|
||||
|
||||
if (eventAsRemoteAccessEvent.data.apiKey === apiKey) {
|
||||
void store.dispatch(
|
||||
handleRemoteAccessEvent(
|
||||
eventAsRemoteAccessEvent
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -122,10 +84,6 @@ export const subscribeToEvents = async (apiKey: string) => {
|
||||
break;
|
||||
}
|
||||
|
||||
case 'UpdateEvent': {
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
34
api/src/remoteAccess/handlers/remote-access-interface.ts
Normal file
34
api/src/remoteAccess/handlers/remote-access-interface.ts
Normal file
@@ -0,0 +1,34 @@
|
||||
import { type AccessUrl } from '@app/graphql/generated/api/types';
|
||||
import { type AppDispatch, type RootState } from '@app/store/index';
|
||||
|
||||
export interface GenericRemoteAccess {
|
||||
beginRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}): Promise<AccessUrl | null>;
|
||||
stopRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}): Promise<void>;
|
||||
getRemoteAccessUrl({
|
||||
getState,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
}): AccessUrl | null;
|
||||
}
|
||||
|
||||
export interface IRemoteAccessController extends GenericRemoteAccess {
|
||||
extendRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}): void;
|
||||
}
|
||||
55
api/src/remoteAccess/handlers/static-remote-access.ts
Normal file
55
api/src/remoteAccess/handlers/static-remote-access.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { remoteAccessLogger } from '@app/core/log';
|
||||
|
||||
import { type AccessUrl, DynamicRemoteAccessType, URL_TYPE } from '@app/graphql/generated/api/types';
|
||||
import { getServerIps } from '@app/graphql/resolvers/subscription/network';
|
||||
import { type GenericRemoteAccess } from '@app/remoteAccess/handlers/remote-access-interface';
|
||||
import { setWanAccessAndReloadNginx } from '@app/store/actions/set-wan-access-with-reload';
|
||||
import { type AppDispatch, type RootState } from '@app/store/index';
|
||||
|
||||
export class StaticRemoteAccess implements GenericRemoteAccess {
|
||||
|
||||
public getRemoteAccessUrl({
|
||||
getState,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
}): AccessUrl | null {
|
||||
const url = getServerIps(getState()).urls.find(
|
||||
(url) => url.type === URL_TYPE.WAN
|
||||
);
|
||||
return url ?? null;
|
||||
}
|
||||
|
||||
async beginRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}): Promise<AccessUrl | null> {
|
||||
const {
|
||||
config: {
|
||||
remote: { dynamicRemoteAccessType },
|
||||
},
|
||||
} = getState();
|
||||
if (dynamicRemoteAccessType === DynamicRemoteAccessType.STATIC) {
|
||||
remoteAccessLogger.debug(
|
||||
'Enabling remote access for Static Client'
|
||||
);
|
||||
await dispatch(setWanAccessAndReloadNginx('yes'));
|
||||
return this.getRemoteAccessUrl({ getState });
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Invalid Parameters Passed to Static Remote Access Enabler'
|
||||
);
|
||||
}
|
||||
|
||||
async stopRemoteAccess({
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}): Promise<void> {
|
||||
await dispatch(setWanAccessAndReloadNginx('no'));
|
||||
}
|
||||
}
|
||||
77
api/src/remoteAccess/handlers/upnp-remote-access.ts
Normal file
77
api/src/remoteAccess/handlers/upnp-remote-access.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import { remoteAccessLogger } from '@app/core/log';
|
||||
import { type AccessUrl, DynamicRemoteAccessType, URL_TYPE } from '@app/graphql/generated/api/types';
|
||||
import { getServerIps } from '@app/graphql/resolvers/subscription/network';
|
||||
|
||||
import { type GenericRemoteAccess } from '@app/remoteAccess/handlers/remote-access-interface';
|
||||
import { setWanAccessAndReloadNginx } from '@app/store/actions/set-wan-access-with-reload';
|
||||
import { type AppDispatch, type RootState } from '@app/store/index';
|
||||
import { disableUpnp, enableUpnp } from '@app/store/modules/upnp';
|
||||
|
||||
export class UpnpRemoteAccess implements GenericRemoteAccess {
|
||||
async stopRemoteAccess({
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}) {
|
||||
// Stop
|
||||
await dispatch(disableUpnp());
|
||||
await dispatch(setWanAccessAndReloadNginx('no'));
|
||||
}
|
||||
|
||||
public getRemoteAccessUrl({ getState }: { getState: () => RootState }): AccessUrl | null {
|
||||
const urlsForServer = getServerIps(getState());
|
||||
const url = urlsForServer.urls.find((url) => url.type === URL_TYPE.WAN);
|
||||
|
||||
return url ?? null;
|
||||
}
|
||||
|
||||
async beginRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}) {
|
||||
// Stop Close Event
|
||||
const state = getState();
|
||||
const { dynamicRemoteAccessType } = state.config.remote;
|
||||
if (
|
||||
dynamicRemoteAccessType === DynamicRemoteAccessType.UPNP &&
|
||||
!state.upnp.upnpEnabled
|
||||
) {
|
||||
const { portssl } = state.emhttp.var;
|
||||
try {
|
||||
const upnpEnableResult = await dispatch(
|
||||
enableUpnp({ portssl })
|
||||
).unwrap();
|
||||
await dispatch(setWanAccessAndReloadNginx('yes'));
|
||||
|
||||
remoteAccessLogger.debug(
|
||||
'UPNP Enable Result',
|
||||
upnpEnableResult
|
||||
);
|
||||
|
||||
if (!upnpEnableResult.wanPortForUpnp) {
|
||||
throw new Error('Failed to get a WAN Port from UPNP');
|
||||
}
|
||||
|
||||
return this.getRemoteAccessUrl({ getState });
|
||||
} catch (error: unknown) {
|
||||
remoteAccessLogger.warn(
|
||||
'Caught error, disabling UPNP and re-throwing'
|
||||
);
|
||||
await this.stopRemoteAccess({ dispatch, getState });
|
||||
throw new Error(
|
||||
`UPNP Dynamic Remote Access Error: ${
|
||||
error instanceof Error ? error.message : 'Unknown Error'
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
'Invalid Parameters Passed to UPNP Remote Access Enabler'
|
||||
);
|
||||
}
|
||||
}
|
||||
150
api/src/remoteAccess/remote-access-controller.ts
Normal file
150
api/src/remoteAccess/remote-access-controller.ts
Normal file
@@ -0,0 +1,150 @@
|
||||
import { remoteAccessLogger } from '@app/core/log';
|
||||
import { UnraidLocalNotifier } from '@app/core/notifiers/unraid-local';
|
||||
import {
|
||||
type AccessUrl,
|
||||
DynamicRemoteAccessType,
|
||||
} from '@app/graphql/generated/api/types';
|
||||
import { type IRemoteAccessController } from '@app/remoteAccess/handlers/remote-access-interface';
|
||||
import { StaticRemoteAccess } from '@app/remoteAccess/handlers/static-remote-access';
|
||||
import { UpnpRemoteAccess } from '@app/remoteAccess/handlers/upnp-remote-access';
|
||||
import { getters, type AppDispatch, type RootState } from '@app/store/index';
|
||||
import {
|
||||
clearPing,
|
||||
receivedPing,
|
||||
setDynamicRemoteAccessError,
|
||||
setRemoteAccessRunningType,
|
||||
} from '@app/store/modules/dynamic-remote-access';
|
||||
export class RemoteAccessController implements IRemoteAccessController {
|
||||
static _instance: RemoteAccessController | null = null;
|
||||
activeRemoteAccess: UpnpRemoteAccess | StaticRemoteAccess | null = null;
|
||||
notifier: UnraidLocalNotifier = new UnraidLocalNotifier({ level: 'info' });
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-useless-constructor, @typescript-eslint/no-empty-function
|
||||
constructor() {}
|
||||
|
||||
public static get instance(): RemoteAccessController {
|
||||
if (!RemoteAccessController._instance) {
|
||||
RemoteAccessController._instance = new RemoteAccessController();
|
||||
}
|
||||
|
||||
return RemoteAccessController._instance;
|
||||
}
|
||||
|
||||
getRunningRemoteAccessType() {
|
||||
return getters.dynamicRemoteAccess().runningType;
|
||||
}
|
||||
|
||||
public getRemoteAccessUrl({
|
||||
getState,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
}): AccessUrl | null {
|
||||
if (!this.activeRemoteAccess) {
|
||||
return null;
|
||||
}
|
||||
return this.activeRemoteAccess.getRemoteAccessUrl({ getState });
|
||||
}
|
||||
|
||||
async beginRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}) {
|
||||
const state = getState();
|
||||
const {
|
||||
config: {
|
||||
remote: { dynamicRemoteAccessType },
|
||||
},
|
||||
dynamicRemoteAccess: { runningType },
|
||||
} = state;
|
||||
|
||||
if (!dynamicRemoteAccessType) {
|
||||
// Should never get here
|
||||
return null;
|
||||
}
|
||||
|
||||
remoteAccessLogger.debug(
|
||||
'Beginning remote access',
|
||||
runningType,
|
||||
dynamicRemoteAccessType
|
||||
);
|
||||
if (runningType !== dynamicRemoteAccessType) {
|
||||
await this.activeRemoteAccess?.stopRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
});
|
||||
}
|
||||
|
||||
switch (dynamicRemoteAccessType) {
|
||||
case DynamicRemoteAccessType.DISABLED:
|
||||
this.activeRemoteAccess = null;
|
||||
remoteAccessLogger.debug(
|
||||
'Received begin event, but DRA is disabled.'
|
||||
);
|
||||
break;
|
||||
case DynamicRemoteAccessType.UPNP:
|
||||
remoteAccessLogger.debug('UPNP DRA Begin');
|
||||
this.activeRemoteAccess = new UpnpRemoteAccess();
|
||||
break;
|
||||
case DynamicRemoteAccessType.STATIC:
|
||||
remoteAccessLogger.debug('Static DRA Begin');
|
||||
this.activeRemoteAccess = new StaticRemoteAccess();
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Essentially a super call to the active type
|
||||
try {
|
||||
await this.activeRemoteAccess?.beginRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
});
|
||||
dispatch(setRemoteAccessRunningType(dynamicRemoteAccessType));
|
||||
this.extendRemoteAccess({ getState, dispatch });
|
||||
await this.notifier.send({
|
||||
title: 'Remote Access Started',
|
||||
data: { message: 'Remote access has been started' },
|
||||
});
|
||||
} catch (error: unknown) {
|
||||
dispatch(
|
||||
setDynamicRemoteAccessError(
|
||||
error instanceof Error ? error.message : 'Unknown Error'
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public extendRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}) {
|
||||
dispatch(receivedPing());
|
||||
return this.getRemoteAccessUrl({ getState });
|
||||
}
|
||||
|
||||
async stopRemoteAccess({
|
||||
getState,
|
||||
dispatch,
|
||||
}: {
|
||||
getState: () => RootState;
|
||||
dispatch: AppDispatch;
|
||||
}) {
|
||||
remoteAccessLogger.debug('Stopping remote access');
|
||||
dispatch(clearPing());
|
||||
await this.activeRemoteAccess?.stopRemoteAccess({ getState, dispatch });
|
||||
|
||||
dispatch(setRemoteAccessRunningType(DynamicRemoteAccessType.DISABLED));
|
||||
await this.notifier.send({
|
||||
title: 'Remote Access Stopped',
|
||||
data: { message: 'Remote access has been stopped' },
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
export enum DynamicRemoteAccessType {
|
||||
UPNP = 'UPNP',
|
||||
STATIC = 'STATIC',
|
||||
DISABLED = 'DISABLED',
|
||||
}
|
||||
@@ -1,50 +0,0 @@
|
||||
import { remoteAccessLogger } from '@app/core/log';
|
||||
import { RemoteAccessEventActionType, type RemoteAccessEventFragmentFragment } from '@app/graphql/generated/client/graphql';
|
||||
import { RemoteAccessController } from '@app/remoteAccess/remote-access-controller';
|
||||
import { DynamicRemoteAccessType } from '@app/remoteAccess/types';
|
||||
import { type AppDispatch, type RootState } from '@app/store/index';
|
||||
import { setAllowedRemoteAccessUrls } from '@app/store/modules/dynamic-remote-access';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
export const handleRemoteAccessEvent = createAsyncThunk<void, RemoteAccessEventFragmentFragment, { state: RootState; dispatch: AppDispatch }>('dynamicRemoteAccess/handleRemoteAccessEvent', async (event, { getState, dispatch }) => {
|
||||
const state = getState();
|
||||
const pluginApiKey = state.config.remote.apikey;
|
||||
if (pluginApiKey !== event.data.apiKey) {
|
||||
remoteAccessLogger.error('Remote Access Event Not For This Client');
|
||||
return;
|
||||
}
|
||||
|
||||
const { dynamicRemoteAccessType } = state.config.remote;
|
||||
if (!dynamicRemoteAccessType || dynamicRemoteAccessType === DynamicRemoteAccessType.DISABLED) {
|
||||
remoteAccessLogger.error('Received Remote Access Event, but Dynamic Remote Access is not enabled.');
|
||||
return;
|
||||
}
|
||||
|
||||
switch (event.data.type) {
|
||||
case RemoteAccessEventActionType.INIT:
|
||||
remoteAccessLogger.debug('Init Event');
|
||||
// Init - Begin listening, transmit an ACK event back from the client.
|
||||
if (event.data.url) {
|
||||
// @todo use this URL to set the only allowed access url
|
||||
dispatch(setAllowedRemoteAccessUrls(event.data.url));
|
||||
}
|
||||
|
||||
await RemoteAccessController.instance.beginRemoteAccess({ getState, dispatch });
|
||||
// @TODO Move this logic into the remote access manager class
|
||||
|
||||
break;
|
||||
case RemoteAccessEventActionType.ACK:
|
||||
// Ack - these events come from the API (this client), so we don't need to respond
|
||||
break;
|
||||
case RemoteAccessEventActionType.PING:
|
||||
// Ping - would continue remote access if necessary;
|
||||
RemoteAccessController.instance.extendRemoteAccess({ dispatch });
|
||||
break;
|
||||
case RemoteAccessEventActionType.END:
|
||||
// End
|
||||
await RemoteAccessController.instance.stopRemoteAccess({ getState, dispatch });
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
});
|
||||
@@ -1,9 +1,9 @@
|
||||
import {
|
||||
DynamicRemoteAccessType,
|
||||
type SetupRemoteAccessInput,
|
||||
WAN_ACCESS_TYPE,
|
||||
WAN_FORWARD_TYPE,
|
||||
} from '@app/graphql/generated/api/types';
|
||||
import { DynamicRemoteAccessType } from '@app/remoteAccess/types';
|
||||
import { type AppDispatch, type RootState } from '@app/store/index';
|
||||
import { type MyServersConfig } from '@app/types/my-servers-config';
|
||||
import { createAsyncThunk } from '@reduxjs/toolkit';
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { logDestination, logger } from '@app/core/log';
|
||||
import { MinigraphStatus } from '@app/graphql/generated/api/types';
|
||||
import { DynamicRemoteAccessType } from '@app/remoteAccess/types';
|
||||
import { DynamicRemoteAccessType, MinigraphStatus } from '@app/graphql/generated/api/types';
|
||||
import { setGraphqlConnectionStatus } from '@app/store/actions/set-minigraph-status';
|
||||
import { store } from '@app/store/index';
|
||||
import { stopListeners } from '@app/store/listeners/stop-listeners';
|
||||
@@ -18,7 +17,7 @@ export const shutdownApiEvent = () => {
|
||||
logger.debug('Writing final configs');
|
||||
writeConfigSync('flash');
|
||||
writeConfigSync('memory');
|
||||
|
||||
logger.debug('Shutting down log destination');
|
||||
logDestination.flushSync();
|
||||
logDestination.destroy();
|
||||
logDestination.destroy();
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ import { configReducer } from '@app/store/modules/config';
|
||||
import { emhttp } from '@app/store/modules/emhttp';
|
||||
import { registration } from '@app/store/modules/registration';
|
||||
import { cache } from '@app/store/modules/cache';
|
||||
import { dashboard } from '@app/store/modules/dashboard';
|
||||
import { docker } from '@app/store/modules/docker';
|
||||
import { upnp } from '@app/store/modules/upnp';
|
||||
import { listenerMiddleware } from '@app/store/listeners/listener-middleware';
|
||||
@@ -27,7 +26,6 @@ export const store = configureStore({
|
||||
remoteGraphQL: remoteGraphQLReducer,
|
||||
notifications: notificationReducer,
|
||||
cache: cache.reducer,
|
||||
dashboard: dashboard.reducer,
|
||||
docker: docker.reducer,
|
||||
upnp: upnp.reducer,
|
||||
dynamix: dynamix.reducer,
|
||||
@@ -45,8 +43,8 @@ export const getters = {
|
||||
apiKey: () => store.getState().apiKey,
|
||||
cache: () => store.getState().cache,
|
||||
config: () => store.getState().config,
|
||||
dashboard: () => store.getState().dashboard,
|
||||
docker: () => store.getState().docker,
|
||||
dynamicRemoteAccess: () => store.getState().dynamicRemoteAccess,
|
||||
dynamix: () => store.getState().dynamix,
|
||||
emhttp: () => store.getState().emhttp,
|
||||
minigraph: () => store.getState().minigraph,
|
||||
|
||||
45
api/src/store/listeners/dynamic-remote-access-listener.ts
Normal file
45
api/src/store/listeners/dynamic-remote-access-listener.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { startAppListening } from '@app/store/listeners/listener-middleware';
|
||||
import { type RootState } from '@app/store';
|
||||
import { remoteAccessLogger } from '@app/core/log';
|
||||
import { loadConfigFile } from '@app/store/modules/config';
|
||||
import { FileLoadStatus } from '@app/store/types';
|
||||
import { isAnyOf } from '@reduxjs/toolkit';
|
||||
import { RemoteAccessController } from '@app/remoteAccess/remote-access-controller';
|
||||
import { DynamicRemoteAccessType } from '@app/graphql/generated/api/types';
|
||||
|
||||
const shouldDynamicRemoteAccessBeEnabled = (state: RootState | null): boolean => {
|
||||
if (state?.config.status !== FileLoadStatus.LOADED || state?.emhttp.status !== FileLoadStatus.LOADED) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (state.config.remote.dynamicRemoteAccessType && state.config.remote.dynamicRemoteAccessType !== DynamicRemoteAccessType.DISABLED) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const isStateOrConfigUpdate = isAnyOf(loadConfigFile.fulfilled);
|
||||
|
||||
export const enableDynamicRemoteAccessListener = () => startAppListening({
|
||||
predicate(action, currentState, previousState) {
|
||||
if ((isStateOrConfigUpdate(action) || !action?.type)
|
||||
&& (shouldDynamicRemoteAccessBeEnabled(currentState) !== shouldDynamicRemoteAccessBeEnabled(previousState))) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}, async effect(_, { getState, dispatch }) {
|
||||
const state = getState();
|
||||
const remoteAccessType = state.config.remote?.dynamicRemoteAccessType;
|
||||
if (!remoteAccessType) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (remoteAccessType === DynamicRemoteAccessType.DISABLED) {
|
||||
remoteAccessLogger.info('[Listener] Disabling Dynamic Remote Access Feature');
|
||||
await RemoteAccessController.instance.stopRemoteAccess({ getState, dispatch });
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
@@ -13,7 +13,7 @@ import merge from 'lodash/merge';
|
||||
import { FileLoadStatus } from '@app/store/types';
|
||||
import { F_OK } from 'constants';
|
||||
import { type RecursivePartial } from '@app/types';
|
||||
import { MinigraphStatus, type Owner } from '@app/graphql/generated/api/types';
|
||||
import { DynamicRemoteAccessType, MinigraphStatus, type Owner } from '@app/graphql/generated/api/types';
|
||||
import { type RootState } from '@app/store';
|
||||
import { randomBytes } from 'crypto';
|
||||
import { logger } from '@app/core/log';
|
||||
@@ -22,7 +22,6 @@ import { getWriteableConfig } from '@app/core/utils/files/config-file-normalizer
|
||||
import { writeFileSync } from 'fs';
|
||||
import { safelySerializeObjectToIni } from '@app/core/utils/files/safe-ini-serializer';
|
||||
import { PUBSUB_CHANNEL, pubsub } from '@app/core/pubsub';
|
||||
import { DynamicRemoteAccessType } from '@app/remoteAccess/types';
|
||||
import { isEqual } from 'lodash';
|
||||
import { setupRemoteAccessThunk } from '@app/store/actions/setup-remote-access';
|
||||
|
||||
|
||||
72
api/src/store/modules/dynamic-remote-access.ts
Normal file
72
api/src/store/modules/dynamic-remote-access.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { remoteAccessLogger } from '@app/core/log';
|
||||
import { type AccessUrlInput, type AccessUrl, DynamicRemoteAccessType, URL_TYPE } from '@app/graphql/generated/api/types';
|
||||
import { createSlice, type PayloadAction } from '@reduxjs/toolkit';
|
||||
|
||||
interface DynamicRemoteAccessState {
|
||||
runningType: DynamicRemoteAccessType; // Is Dynamic Remote Access actively running - shows type of access currently running
|
||||
error: string | null;
|
||||
lastPing: number | null;
|
||||
allowedUrl: AccessUrl | null; // Not used yet, will be used to facilitate allowlisting clients
|
||||
}
|
||||
|
||||
const initialState: DynamicRemoteAccessState = {
|
||||
runningType: DynamicRemoteAccessType.DISABLED,
|
||||
error: null,
|
||||
lastPing: null,
|
||||
allowedUrl: null
|
||||
};
|
||||
|
||||
const dynamicRemoteAccess = createSlice({
|
||||
name: 'dynamicRemoteAccess',
|
||||
initialState,
|
||||
reducers: {
|
||||
receivedPing(state) {
|
||||
remoteAccessLogger.info('ping');
|
||||
state.lastPing = Date.now();
|
||||
},
|
||||
clearPing(state) {
|
||||
remoteAccessLogger.info('clearing ping');
|
||||
state.lastPing = null;
|
||||
},
|
||||
setRemoteAccessRunningType(
|
||||
state,
|
||||
action: PayloadAction<DynamicRemoteAccessType>
|
||||
) {
|
||||
state.error = null;
|
||||
state.runningType = action.payload;
|
||||
if (action.payload === DynamicRemoteAccessType.DISABLED) {
|
||||
state.lastPing = null;
|
||||
} else {
|
||||
state.lastPing = Date.now();
|
||||
}
|
||||
},
|
||||
setDynamicRemoteAccessError(state, action: PayloadAction<string>) {
|
||||
state.error = action.payload;
|
||||
},
|
||||
setAllowedRemoteAccessUrl(
|
||||
state,
|
||||
action: PayloadAction<AccessUrlInput | null>
|
||||
) {
|
||||
if (action.payload) {
|
||||
console.log(action.payload);
|
||||
state.allowedUrl = {
|
||||
ipv4: action.payload.ipv4,
|
||||
ipv6: action.payload.ipv6,
|
||||
type: action.payload.type ?? URL_TYPE.WAN,
|
||||
name: action.payload.name,
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { actions, reducer } = dynamicRemoteAccess;
|
||||
|
||||
export const {
|
||||
receivedPing,
|
||||
clearPing,
|
||||
setAllowedRemoteAccessUrl,
|
||||
setRemoteAccessRunningType,
|
||||
setDynamicRemoteAccessError,
|
||||
} = actions;
|
||||
export const dynamicRemoteAccessReducer = reducer;
|
||||
@@ -2,29 +2,54 @@ import { createSlice } from '@reduxjs/toolkit';
|
||||
import { join, resolve as resolvePath } from 'path';
|
||||
|
||||
const initialState = {
|
||||
core: __dirname,
|
||||
'unraid-api-base': '/usr/local/bin/unraid-api/' as const,
|
||||
'unraid-data': resolvePath(process.env.PATHS_UNRAID_DATA ?? '/boot/config/plugins/dynamix.my.servers/data/' as const),
|
||||
'docker-autostart': '/var/lib/docker/unraid-autostart' as const,
|
||||
'docker-socket': '/var/run/docker.sock' as const,
|
||||
'parity-checks': '/boot/config/parity-checks.log' as const,
|
||||
htpasswd: '/etc/nginx/htpasswd' as const,
|
||||
'emhttpd-socket': '/var/run/emhttpd.socket' as const,
|
||||
states: resolvePath(process.env.PATHS_STATES ?? '/usr/local/emhttp/state/' as const),
|
||||
'dynamix-base': resolvePath(process.env.PATHS_DYNAMIX_BASE ?? '/boot/config/plugins/dynamix/' as const),
|
||||
'dynamix-config': resolvePath(process.env.PATHS_DYNAMIX_CONFIG ?? '/boot/config/plugins/dynamix/dynamix.cfg' as const),
|
||||
'myservers-base': '/boot/config/plugins/dynamix.my.servers/' as const,
|
||||
'myservers-config': resolvePath(process.env.PATHS_MY_SERVERS_CONFIG ?? '/boot/config/plugins/dynamix.my.servers/myservers.cfg' as const),
|
||||
'myservers-config-states': join(resolvePath(process.env.PATHS_STATES ?? '/usr/local/emhttp/state/' as const), 'myservers.cfg' as const),
|
||||
'myservers-env': '/boot/config/plugins/dynamix.my.servers/env' as const,
|
||||
'keyfile-base': resolvePath(process.env.PATHS_KEYFILE_BASE ?? '/boot/config' as const),
|
||||
'machine-id': resolvePath(process.env.PATHS_MACHINE_ID ?? '/var/lib/dbus/machine-id' as const),
|
||||
'log-base': resolvePath('/var/log/unraid-api/' as const),
|
||||
'var-run': '/var/run' as const,
|
||||
core: __dirname,
|
||||
'unraid-api-base': '/usr/local/bin/unraid-api/' as const,
|
||||
'unraid-data': resolvePath(
|
||||
process.env.PATHS_UNRAID_DATA ??
|
||||
('/boot/config/plugins/dynamix.my.servers/data/' as const)
|
||||
),
|
||||
'docker-autostart': '/var/lib/docker/unraid-autostart' as const,
|
||||
'docker-socket': '/var/run/docker.sock' as const,
|
||||
'parity-checks': '/boot/config/parity-checks.log' as const,
|
||||
htpasswd: '/etc/nginx/htpasswd' as const,
|
||||
'emhttpd-socket': '/var/run/emhttpd.socket' as const,
|
||||
states: resolvePath(
|
||||
process.env.PATHS_STATES ?? ('/usr/local/emhttp/state/' as const)
|
||||
),
|
||||
'dynamix-base': resolvePath(
|
||||
process.env.PATHS_DYNAMIX_BASE ??
|
||||
('/boot/config/plugins/dynamix/' as const)
|
||||
),
|
||||
'dynamix-config': resolvePath(
|
||||
process.env.PATHS_DYNAMIX_CONFIG ??
|
||||
('/boot/config/plugins/dynamix/dynamix.cfg' as const)
|
||||
),
|
||||
'myservers-base': '/boot/config/plugins/dynamix.my.servers/' as const,
|
||||
'myservers-config': resolvePath(
|
||||
process.env.PATHS_MY_SERVERS_CONFIG ??
|
||||
('/boot/config/plugins/dynamix.my.servers/myservers.cfg' as const)
|
||||
),
|
||||
'myservers-config-states': join(
|
||||
resolvePath(
|
||||
process.env.PATHS_STATES ?? ('/usr/local/emhttp/state/' as const)
|
||||
),
|
||||
'myservers.cfg' as const
|
||||
),
|
||||
'myservers-env': '/boot/config/plugins/dynamix.my.servers/env' as const,
|
||||
'myservers-keepalive':
|
||||
process.env.PATHS_MY_SERVERS_FB ?? ('/boot/config/plugins/dynamix.my.servers/fb_keepalive' as const),
|
||||
'keyfile-base': resolvePath(
|
||||
process.env.PATHS_KEYFILE_BASE ?? ('/boot/config' as const)
|
||||
),
|
||||
'machine-id': resolvePath(
|
||||
process.env.PATHS_MACHINE_ID ?? ('/var/lib/dbus/machine-id' as const)
|
||||
),
|
||||
'log-base': resolvePath('/var/log/unraid-api/' as const),
|
||||
'var-run': '/var/run' as const,
|
||||
};
|
||||
|
||||
export const paths = createSlice({
|
||||
name: 'paths',
|
||||
initialState,
|
||||
reducers: {},
|
||||
name: 'paths',
|
||||
initialState,
|
||||
reducers: {},
|
||||
});
|
||||
|
||||
70
api/src/store/state-parsers/nginx.ts
Normal file
70
api/src/store/state-parsers/nginx.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import type { IniStringBooleanOrAuto } from '@app/core/types/ini';
|
||||
import { type FqdnEntry } from '@app/core/types/states/nginx';
|
||||
import type { StateFileToIniParserMap } from '@app/store/types';
|
||||
|
||||
// Allow upper or lowercase FQDN6
|
||||
const fqdnRegex = /^nginx(.*?)fqdn6?$/i;
|
||||
|
||||
export type NginxIni = {
|
||||
nginxCertname: string;
|
||||
nginxCertpath: string;
|
||||
nginxDefaulturl: string;
|
||||
nginxLanip: string;
|
||||
nginxLanip6: string;
|
||||
nginxLanmdns: string;
|
||||
nginxLanname: string;
|
||||
nginxPort: string;
|
||||
nginxPortssl: string;
|
||||
nginxUsessl: IniStringBooleanOrAuto;
|
||||
nginxWanip: string;
|
||||
nginxWanaccess: string;
|
||||
[nginxInterfaceFqdn: string]: string;
|
||||
};
|
||||
|
||||
export const parse: StateFileToIniParserMap['nginx'] = (state) => {
|
||||
const fqdnKeys = Object.keys(state).filter((key) => fqdnRegex.test(key));
|
||||
|
||||
const interfaceId = new Map<string, number>();
|
||||
const fqdnUrls: FqdnEntry[] = fqdnKeys.reduce<FqdnEntry[]>((acc, key) => {
|
||||
const match = fqdnRegex.exec(key);
|
||||
if (match && state[key]) {
|
||||
// We need to pull the number from the interface to get it by itself
|
||||
const interfaceType = match[1].replace(/[0-9]/g, '').toUpperCase();
|
||||
|
||||
// Count the number of interfaces we've already added to the list
|
||||
const isIPv6 = key.endsWith('6');
|
||||
const currInterfaceId = interfaceId.get(interfaceType) || 0;
|
||||
interfaceId.set(interfaceType, currInterfaceId + 1);
|
||||
acc.push({
|
||||
interface: interfaceType,
|
||||
id: currInterfaceId,
|
||||
fqdn: state[key],
|
||||
isIpv6: isIPv6,
|
||||
});
|
||||
}
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
fqdnUrls.forEach((fqdn) => {
|
||||
if ((interfaceId.get(fqdn.interface) || 0) <= 1 ) {
|
||||
fqdn.id = null;
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
certificateName: state.nginxCertname,
|
||||
certificatePath: state.nginxCertpath,
|
||||
defaultUrl: state.nginxDefaulturl,
|
||||
httpPort: Number(state.nginxPort),
|
||||
httpsPort: Number(state.nginxPortssl),
|
||||
lanIp: state.nginxLanip,
|
||||
lanIp6: state.nginxLanip6,
|
||||
lanMdns: state.nginxLanmdns,
|
||||
lanName: state.nginxLanname,
|
||||
sslEnabled: state.nginxUsessl !== 'no',
|
||||
sslMode: state.nginxUsessl,
|
||||
wanAccessEnabled: state.nginxWanaccess === 'yes',
|
||||
wanIp: state.nginxWanip,
|
||||
fqdnUrls: fqdnUrls as FqdnEntry[],
|
||||
};
|
||||
};
|
||||
@@ -104,6 +104,7 @@ export type VarIni = {
|
||||
regGuid: string;
|
||||
regTm: string;
|
||||
regTm2: string;
|
||||
regExp?: string;
|
||||
regTo: string;
|
||||
regTy: string;
|
||||
regState: string;
|
||||
@@ -249,6 +250,7 @@ export const parse: StateFileToIniParserMap['var'] = (iniFile) => {
|
||||
regTy:
|
||||
registrationType[iniFile.regTy?.toUpperCase()] ??
|
||||
registrationType.INVALID,
|
||||
regExp: iniFile.regExp ?? null,
|
||||
// Make sure to use a || not a ?? as regCheck can be an empty string
|
||||
regState:
|
||||
RegistrationState[
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user