Compare commits

..

306 Commits

Author SHA1 Message Date
Zack Spear
fbbda5926b refactor: update my servers include file to handle CSS values
This commit updates the `myservers1.php` file in the `dynamix.my.servers` plugin. It adds functionality to handle CSS values in addition to the existing file handling logic. The code now checks for CSS values in the local manifest and includes them in the rendered HTML if found.
2024-09-30 11:56:37 -07:00
mdatelle
8a0a053a80 test: add notification component directory 2024-09-27 15:39:23 -04:00
mdatelle
026d643b49 test: add poc drawer component 2024-09-27 15:35:54 -04:00
Zack Spear
d2a34acfb9 refactor: always show footer in CheckUpdateResponseModal 2024-09-12 20:14:10 -07:00
Zack Spear
3dc60b6106 feat: add deviceCount to serverAccountPayload for callbacks 2024-09-12 20:14:10 -07:00
Eli Bosley
57587b9175 chore(release): 3.11.0 2024-09-11 13:25:19 -04:00
ljm42
5ee7cb2647 feat: reduce how often rc.flashbackup checks for changes
Instead of checking once per minute, check once every 30 minutes
2024-09-10 12:48:40 -04:00
ljm42
911a3f8f1a feat: send api_version to flash/activate endpoint
also use _var() function in a few more places for consistency
2024-09-10 09:30:17 -07:00
ljm42
d426001372 feat: update ProvisionCert.php to clean hosts file when it runs 2024-09-09 12:49:15 -07:00
ljm42
2d0c65aaf4 fix: remove local flash backup ratelimit file on uninstall/update 2024-09-06 16:38:10 -07:00
ljm42
fd4605b956 chore: prevent corner case issue and fix php warning
* Update remoteerror in flashback.ini if it gets out of sync with gitratelimit (can happen during testing if you delete flashbackup.ini)
* Fix php warning for retry_after
2024-09-06 11:27:17 -07:00
Eli Bosley
3f84b6bbfd chore(release): 3.10.1 2024-09-03 14:43:08 -04:00
github-actions[bot]
5ad10af303 chore: release main 2024-09-03 14:36:05 -04:00
Eli Bosley
9aa11faaaa fix: don't release to github 2024-09-03 14:32:04 -04:00
Eli Bosley
bfa98574f1 fix: single tag for both components 2024-09-03 14:30:03 -04:00
github-actions[bot]
dd2dc40ff1 chore: release main 2024-09-03 14:24:36 -04:00
ljm42
8a3265d7b1 Feat: flash backup supports keyserver rate limits 2024-09-03 11:14:56 -07:00
ljm42
a240a031a8 feat: set OS minver to 6.12.0 2024-08-30 12:50:44 -07:00
ljm42
979e41fe41 fix: remove hard-coded entry for keyserver from hosts file 2024-08-30 10:52:50 -04:00
ljm42
03dc404aa7 Use "go links" when linking to Docs 2024-08-29 16:35:02 -07:00
Eli Bosley
364320ffc9 fix: unify pull requests for release-please 2024-08-28 16:51:26 -04:00
Eli Bosley
2492f4cec9 feat: web version set 2024-08-28 16:37:15 -04:00
Eli Bosley
1a643b3eef feat: set API version 2024-08-28 16:36:34 -04:00
Eli Bosley
58ee3b958b feat: remove plugin as part of release please 2024-08-28 16:15:00 -04:00
Eli Bosley
2928cf5821 fix: add manifest 2024-08-28 15:58:02 -04:00
Eli Bosley
b21b276151 fix: infer release type 2024-08-28 15:55:58 -04:00
Eli Bosley
d80f25dc96 feat: begin release-please setup 2024-08-28 15:53:24 -04:00
Eli Bosley
f5f5a081e6 fix: unused import 2024-08-28 15:44:03 -04:00
Eli Bosley
f60474b4d7 fix: lint 2024-08-28 15:44:03 -04:00
Eli Bosley
364373df0c fix: revert myservers.cfg to fix test 2024-08-28 15:44:03 -04:00
Eli Bosley
bb38533bb2 fix: update snapshots 2024-08-28 15:44:03 -04:00
Eli Bosley
836801c524 feat: dynamic remote access using remote queries 2024-08-28 15:44:03 -04:00
Eli Bosley
b54cf5ede9 feat: move dynamic remote access to be fully api controlled 2024-08-28 15:44:03 -04:00
Eli Bosley
1a20c66c02 fix: permission for dashboard payload 2024-08-28 15:44:03 -04:00
Eli Bosley
587bbb3b4d feat: create stable hash based on apikey rather than hostname 2024-08-28 15:44:03 -04:00
Eli Bosley
e95f7a1a03 feat: remove dashboard types 2024-08-28 15:44:03 -04:00
Eli Bosley
9c75f6e2ca feat: update tests and snapshots 2024-08-28 15:44:03 -04:00
Eli Bosley
822042ab9c feat: remove dashboard resolver completely in favor of direct field resolvers 2024-08-28 15:44:03 -04:00
Eli Bosley
3a843b6e16 feat: disable all legacy dashboard and network logic 2024-08-28 15:44:03 -04:00
renovate[bot]
6072387c37 chore(deps): update dependency @types/node to v18.19.46 (#795)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-27 14:27:11 -04:00
renovate[bot]
313162dbf2 fix(deps): update dependency wtfnode to v0.9.3 (#901)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-08-27 14:26:50 -04:00
Eli Bosley
495515abac feat: move FQDN urls to a generic parser (#899)
* feat: move FQDN urls to a generic parser

* feat: update myservers.cfg

* feat: update parser to begin changing ID fields

* fix: ID parser issues resolved

* fix: remove console log

* fix: update snapshots
2024-08-21 10:38:16 -04:00
Zack Spear
09087040e9 fix: flash backup activated detection in account payload (#898)
fix: flash backup activated detection in account payload to warn users to download back before unregistering with connect
2024-08-16 10:08:42 -04:00
Eli Bosley
4423829911 feat: add global agent (#897)
* feat: add global agent

* feat: add proxy setup to rc.unraid-api

* feat: update myservers.cfg to latest version
2024-08-14 14:52:36 -04:00
Eli Bosley
c8f469c4fb chore(release): 3.8.1 2024-08-13 14:11:02 -04:00
Zack Spear
bc61b45f9f refactor: registration component remove contact support 2024-08-13 14:04:54 -04:00
Eli Bosley
f530d9ea82 chore(release): 3.8.0 2024-08-13 13:50:07 -04:00
ljm42
2046fa5310 refactor: change flag that skips delete on uninstall (#892) 2024-08-13 13:40:18 -04:00
Zack Spear
9ea2327fa0 refactor: registration transfer check ineligible copy 2024-08-13 10:31:50 -07:00
Zack Spear
ff67b54a1b refactor: doc urls use /go links 2024-08-13 10:31:31 -07:00
ljm42
e6bd7a54be feat: always force push 2024-08-13 10:30:49 -07:00
Eli Bosley
5827b5ffa3 feat: swap to docker compose from docker-compose 2024-08-07 11:04:54 -04:00
ljm42
572a1310e0 Use "go links" when linking to Docs (#891) 2024-08-07 10:41:57 -04:00
ljm42
c1403d3826 feat: don't allow flash backup repos larger than 500MB (#890)
* feat: don't allow flash backup repos larger than 500MB

* fix: don't backup dynamix.file.integrity/logs

* feat: max file size for backup limited to 10mb

* feat: limit max repo size to 100MB

* feat: delete large repo again after 90 days
2024-08-07 10:41:36 -04:00
Eli Bosley
29afe9b9e8 feat: settings through the API (#867)
* feat: api settings fully working
* refactor: nuxt config ConnectSettings

---------

Co-authored-by: Zack Spear <hi@zackspear.com>
2024-07-03 13:38:09 -04:00
Zack Spear
e9ff33d263 feat: downgradeOs callback for non stable osCurrentBranch 2024-05-28 11:57:05 -07:00
Zack Spear
a62f60a436 fix: update status button alignment 2024-05-28 11:57:05 -07:00
Eli Bosley
838964c6ef chore: update package.json with new dependencies (#886)
* chore: update package.json with new dependencies

* feat: run codegen

* fix: got and reflect metadata revert version

* fix: pino version mismatch

* feat: update package-lock.json
2024-05-17 11:21:55 -04:00
Zack Spear
800fc12c15 refactor: server state refresh and response mutations 2024-05-16 14:13:01 -07:00
Zack Spear
80175241e3 fix: lint error for web components 2024-05-16 14:13:01 -07:00
Zack Spear
5d801f22f5 chore: ts-expect-error description for webgui troubleshoot form 2024-05-16 14:13:01 -07:00
Zack Spear
ba772add54 refactor: instantiation of web components 2024-05-16 14:13:01 -07:00
Zack Spear
ff24f12cae refactor: optional chaining for click props 2024-05-16 14:13:01 -07:00
Eli Bosley
487f5c1865 fix: tailwind config types 2024-05-16 14:13:01 -07:00
Eli Bosley
e0c90037fb fix: swap undefined to null 2024-05-16 14:13:01 -07:00
Eli Bosley
aa5f603cba fix: apolloClient types 2024-05-16 14:13:01 -07:00
Eli Bosley
409db43973 fix: ts-expect-error unneeded 2024-05-16 14:13:01 -07:00
Zack Spear
cef1b29355 fix: type check 2024-05-16 14:13:01 -07:00
Zack Spear
045750c87e fix: lint issues 2024-05-16 14:13:01 -07:00
Zack Spear
85802e7af7 fix: formattedRegTm type 2024-05-16 14:13:01 -07:00
Zack Spear
4bfdb66d46 fix: i18n t prop type 2024-05-16 14:13:01 -07:00
Zack Spear
81a6a52d9f fix: type errors round 1 2024-05-16 14:13:01 -07:00
Zack Spear
7759fe1dc3 chore(web): update deps + eslint (#887)
* chore: update deps + eslint

* fix: lint + type errors
2024-05-16 09:26:39 -04:00
renovate[bot]
3b2acb29b5 chore(deps): update dependency @vueuse/nuxt to v10.9.0 (#797)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 15:23:11 -04:00
renovate[bot]
5f2b949ecf chore(deps): update dependency @nuxtjs/tailwindcss to v6.12.0 (#794)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 15:22:43 -04:00
renovate[bot]
1b956d563e fix(deps): update dependency @vue/apollo-composable to v4.0.2 (#787)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-15 15:22:30 -04:00
Eli Bosley
c6a97f5082 chore(release): 3.7.1 2024-05-15 14:33:43 -04:00
Zack Spear
7f512e47e9 fix: reboot required and available edge case (#885)
* fix: reboot required and available edge case

* chore: add missing web component translations

* chore: translations sort unique ascending, case insensitive

* fix: translation json
2024-05-15 12:24:06 -04:00
Eli Bosley
5d725b0e76 chore(release): 3.7.0 2024-05-14 15:18:07 -04:00
renovate[bot]
fe63607260 chore(deps): update dependency @types/dockerode to v3.3.29 (#768)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-05-13 16:03:35 -04:00
Zack Spear
0a1d4daf6e refactor: update os status updateAvailable 2024-05-13 10:52:30 -07:00
Zack Spear
9e9e385bef chore: UnraidUpdateCancel reorganize 2024-05-13 10:52:30 -07:00
Zack Spear
6fed39e05b chore: update cancel comment 2024-05-13 10:52:30 -07:00
Zack Spear
3dec53d13d chore: UpdateOS Status unused import 2024-05-13 10:52:30 -07:00
Zack Spear
f0ded9f5be fix: update os cancel refresh on update page 2024-05-13 10:52:30 -07:00
Zack Spear
7d55a1c2cd chore: UpdateOS Status unused import 2024-05-13 10:52:30 -07:00
Zack Spear
f3dc9663b8 feat: UI Update OS Cancel 2024-05-13 10:52:30 -07:00
Zack Spear
05c7c481a9 chore: update cancel script 2024-05-13 10:52:30 -07:00
Zack Spear
adcc1543f0 feat: UnraidUpdateCancel script 2024-05-13 10:52:30 -07:00
Zack Spear
95f873c752 refactor: UnraidCheck use current unRAIDServer.plg 2024-05-13 10:52:30 -07:00
Zack Spear
ec90f8b295 fix: plugin file deployment script 2024-05-13 10:52:30 -07:00
Zack Spear
f84195a98d fix(web): lint unused rebootVersion 2024-05-13 10:52:30 -07:00
Zack Spear
5e98a68e2e feat: ui to allow second update without reboot 2024-05-13 10:52:30 -07:00
Zack Spear
b91dbca144 refactor: btnStyle prop for CallbackButton component 2024-05-13 10:52:30 -07:00
Zack Spear
79a01da18d refactor: ButtonStyle type 2024-05-13 10:52:30 -07:00
Zack Spear
14951d3004 refactor: reboot details added to server payload to account 2024-05-13 10:52:30 -07:00
Zack Spear
64c2061bea chore: dev deployment script improvements 2024-05-13 10:52:30 -07:00
Zack Spear
e3adc9a29a chore: dev deployment script improvements 2024-05-13 10:52:30 -07:00
ljm42
6b689ffcce Chore: sync http_get_contents() with webgui (#883) 2024-05-10 12:57:57 -07:00
ljm42
c995a4c5c8 Fix: rc.flashbackup needs to check both signed in and connected (#882)
because /var/local/emhttp/myservers.cfg does not clear the connected status when the user signs out
2024-05-10 10:44:21 -07:00
Zack Spear
8d1e0f67d1 refactor: simplify version_compare in reboot-details 2024-05-08 12:47:29 -07:00
Zack Spear
7877a5dca2 refactor: reboot type detection for downgrade via callback 2024-05-08 12:47:29 -07:00
Zack Spear
16db278ffd feat: downgradeOs callback 2024-05-08 12:47:29 -07:00
ljm42
521b4381f2 Fix bug in flash backup rate limiter (#880)
Don't try to read from an empty file
2024-05-07 17:11:36 -07:00
ljm42
9ae9d40f94 fix: keep minor enhancements from #872 (#878) 2024-05-07 08:39:46 -07:00
Zack Spear
1d562d404c fix(web): registration component remove unused ref 2024-05-06 10:44:16 -07:00
Zack Spear
7ac1b268d9 refactor(web): registration linked learn more callback to my keys 2024-05-06 10:44:16 -07:00
Zack Spear
4833e9dccf chore: translations 2024-05-06 10:44:16 -07:00
Zack Spear
f28b7510fa feat(web): Registration key linked to account status 2024-05-06 10:44:16 -07:00
Zack Spear
37b717b142 refactor(web): button component no style option 2024-05-06 10:44:16 -07:00
Zack Spear
fd8b40d9aa feat(web): callback types myKeys & linkKey 2024-05-06 10:44:16 -07:00
Eli Bosley
1d944781cf feat: add a timestamp to flash backup (#877)
* feat: add a timestamp to flash backup

* feat: update gitignore

* feat: random interval is now 30 minutes
2024-05-06 13:40:42 -04:00
Zack Spear
1f4c64d022 feat(plg): install prevent downgrade of shared page & php files (#873)
* feat(plg): install prevent downgrade of shared page & php files

* chore(plg): remove debug echo

* fix(plg): remove extra char
2024-05-02 14:08:38 -07:00
Zack Spear
f69b5130a3 refactor(web): copy Ineligible for feature updates (#875)
* refactor(web): copy Ineligible for feature updates

* refactor(web): Eligible for free feature updates
2024-05-02 14:04:16 -07:00
Zack Spear
f8b143904b fix: prevent corrupt case model in state.php (#874)
fix: prevent corruprt case model in state.php
2024-05-02 14:00:29 -07:00
Zack Spear
31a5413643 feat(web): registration page array status messaging 2024-05-01 12:21:24 -07:00
Zack Spear
a95fc5ed07 chore: lint fix 2024-05-01 12:21:24 -07:00
Zack Spear
fcd7bb790e refactor(web): ineligible release messaging 2024-05-01 12:21:24 -07:00
Zack Spear
008e10948e fix: prevent local dev from throwing ssl error 2024-05-01 12:21:24 -07:00
Zack Spear
c97a4f1268 feat: registration page server error heading + subheading 2024-05-01 12:21:24 -07:00
Zack Spear
3eba95b8cc feat: array state on registration page 2024-05-01 12:21:24 -07:00
Zack Spear
2bf8f0b937 fix(api): readme discord url 2024-04-30 17:34:46 -07:00
Zack Spear
9ae45d1258 fix(web): discord url 2024-04-30 17:34:46 -07:00
Zack Spear
1835a4cf3f chore(plg): comment explain web component downgrade prevention 2024-04-30 17:12:33 -07:00
Zack Spear
2ab44b894d feat(plg): plg install prevent web component downgrade 2024-04-30 17:12:33 -07:00
Zack Spear
1108f49b07 feat: postbuild script to add timestamp to web component manifest 2024-04-30 17:12:33 -07:00
ljm42
cc69213beb Feat: Flash Backup requires connection to mothership (#868)
* fix: branding

* feat: flash backup requires connection to mothership

* feat: flash backup requires connection to mothership
2024-04-26 12:01:42 -04:00
ljm42
460e557dd8 Flash Backup: exclude large files from repo (#866) 2024-04-23 21:21:01 -04:00
Zack Spear
05e29468d2 refactor: trial messaging replace pro with unleashed (#865)
* refactor: trial messaging replace pro with unleashed

* fix: trial messaging grammar

* refactor: web component translations trial messaging
2024-04-03 13:46:25 -04:00
ljm42
4d3a311fb4 Feat: add support for outgoing proxies (#863) 2024-03-27 15:14:18 -07:00
Zack Spear
bc62d210ec refactor: config error messages (#862) 2024-03-26 12:30:34 -04:00
Eli Bosley
43d3ea6553 chore(release): 3.6.0 2024-03-26 10:19:31 -04:00
Zack Spear
882e3e1ef4 feat: server config enum message w/ ineligible support (#861)
* test: serverState local components data tweaks

* feat: server config enum message w/ ineligible support

* refactor: config error messages

* chore: lint
2024-03-26 09:57:04 -04:00
Eli Bosley
b33c86c99c chore(release): 3.5.3 2024-03-25 09:22:14 -04:00
Zack Spear
cd0248e4c9 refactor: upgrade action button for unleashed to lifetime (#859) 2024-03-20 10:27:33 -04:00
Zack Spear
ecb3ed5003 fix: regDevs usage to allow more flexibility for STARTER (#860)
* fix: regDevs usage to allow more flexibility for STARTER

* fix: lint and type-check
2024-03-20 08:50:02 -04:00
Zack Spear
0569339a41 refactor(upc): remove UpdateDNS requests 2024-03-12 16:36:04 -07:00
ljm42
3e9faead43 Replace UpdateDNS.php with a stub (#857)
* This new stub file makes no network calls and always returns success
* It is meant to be backwards compatible with older releases of Unraid that expect the script to exist
2024-03-12 15:57:17 -04:00
Eli Bosley
6e700b2385 chore(release): 3.5.2 2024-03-06 10:20:09 -05:00
renovate[bot]
464fc4993c fix(deps): update dependency vue-i18n to v9.10.1 (#813)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:08:22 -05:00
renovate[bot]
4316c72809 chore(deps): update dependency terser to v5.28.1 (#802)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:08:05 -05:00
renovate[bot]
ce0cebe09c chore(deps): update dependency node to v18.19.1 (#801)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:07:51 -05:00
renovate[bot]
23b90a0d56 fix(deps): update dependency wretch to v2.8.0 (#814)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-03-05 15:07:38 -05:00
Zack Spear
3f8b3536b5 refactor: ENOKEYFILE messaging + button order (#856) 2024-03-05 15:07:20 -05:00
Zack Spear
0dcf785b45 fix: update os check modal button conditionals 2024-02-29 14:49:20 -08:00
Zack Spear
8cf4aff622 fix: update os check modal ineligible date format 2024-02-29 14:16:09 -08:00
Eli Bosley
cefda7c42b chore(release): 3.5.1 2024-02-29 12:43:48 -05:00
Eli Bosley
0393b2382c fix: build docker command updated to use dc.sh script 2024-02-29 12:43:41 -05:00
renovate[bot]
23e900f7fd chore(deps): update docker/setup-buildx-action action to v3 (#827)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 09:36:32 -05:00
renovate[bot]
2d6aafc257 chore(deps): update dependency eslint to v8.57.0 (#798)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 09:29:55 -05:00
renovate[bot]
b191efece1 chore(deps): update vitest monorepo to v1.3.1 (#784)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 09:29:40 -05:00
renovate[bot]
2a7f0043f5 fix(deps): update dependency @heroicons/vue to v2.1.1 (#804)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 09:29:17 -05:00
renovate[bot]
607c7e3704 fix(deps): update dependency @apollo/client to v3.9.5 (#785)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 09:29:01 -05:00
renovate[bot]
c246a443c5 fix(deps): update dependency graphql-ws to v5.15.0 (#790)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 09:25:35 -05:00
renovate[bot]
fd495e1f5c fix(deps): update dependency focus-trap to v7.5.4 (#788)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-02-29 09:24:58 -05:00
Zack Spear
621a06cafa fix: unraid-api.php $param1 fallback 2024-02-28 21:11:21 -08:00
Zack Spear
f890b05151 fix: unraid-api missing start command + var defaults 2024-02-28 21:11:21 -08:00
Zack Spear
567d8fdd6d fix: state php special chars for html attributes (#853)
* fix: state php special chars for html attributes

* refactor: upc description as v-html to latest state php change
2024-02-28 13:42:11 -05:00
ljm42
7d996906ad fix: date format in UnraidCheck.php (#852) 2024-02-27 13:43:01 -05:00
Zack Spear
b5ec076279 fix: os updates rc to stable 2024-02-23 13:47:35 -08:00
Zack Spear
de8dfe3dba fix: state php breaking with double quotes in server description 2024-02-23 13:46:09 -08:00
Zack Spear
7249956d40 fix: state connect values without connect installed 2024-02-23 13:46:09 -08:00
Zack Spear
e6eb56466e fix: display dropdown for pro key no connect installed (#848) 2024-02-14 11:10:28 -05:00
Zack Spear
8954700bcb fix: dropdown reboot link text (#849)
fix: dropdown reboot link
2024-02-14 10:50:03 -05:00
Eli Bosley
eb595cea9e chore(release): 3.5.0 2024-02-07 12:51:01 -05:00
Eli Bosley
9a1a0a54e6 feat: ship production to different bucket (#846) 2024-02-07 12:47:35 -05:00
Zack Spear
134396b602 refactor: state class webgui global fallback 2024-02-05 12:58:15 -08:00
Zack Spear
2aa65fdb68 fix: state php usage from cli 2024-02-05 12:58:15 -08:00
Zack Spear
a9c4d7d5dd chore: comment to handle error on sha256 fetch 2024-02-05 12:58:15 -08:00
Zack Spear
2cbbd5ee40 refactor: remove client side auto renewal 2024-02-05 12:58:15 -08:00
Zack Spear
c84c55761c fix: State Class usage in other files 2024-02-05 12:58:15 -08:00
Zack Spear
77eed36990 refactor: replaceRenewCheck only fire on Tools > Registration 2024-02-05 12:58:15 -08:00
Eli Bosley
5c2d84d8b4 feat: ship preview to different bucket (#845) 2024-02-02 12:01:18 -05:00
Eli Bosley
9883f0f82f feat: also ship to cloudflare (#844) 2024-02-02 10:53:24 -05:00
Zack Spear
e62b05b6f6 fix: extraLinks when no updates available 2024-02-01 12:38:10 -08:00
Zack Spear
8e6ee8b770 refactor: copy Extend Key to Extend License 2024-02-01 12:11:38 -08:00
Zack Spear
666b51a28a refactor: update os response modal button ordering 2024-02-01 11:48:14 -08:00
Zack Spear
1962097a66 refactor: callback modal button icons 2024-01-31 18:45:42 -08:00
Zack Spear
7f010854b5 refactor: availableWithRenewal determined by updateOsResponse isEligible
refactor: updateOsResponse changelogPretty key renamed
2024-01-31 14:52:04 -08:00
Zack Spear
17288a4c02 refactor: removing ignored release uncheck ignoreThisRelease 2024-01-31 14:52:04 -08:00
ljm42
ea48def9fc fix: backport _var() PHP function to older versions of Unraid 2024-01-31 14:06:15 -08:00
Zack Spear
a1d5c29ffb fix: state data humanReadable switch fallthrus 2024-01-31 13:33:34 -08:00
Zack Spear
bf99eb25c8 feat: update os notifications enabled usage + link to enable & more options to account app 2024-01-31 12:39:14 -08:00
Zack Spear
b35a440792 refactor: callback modal spacing 2024-01-31 11:02:37 -08:00
Zack Spear
58f9eec8b1 refactor: targeting keyType strings for Starter / Unleashed 2024-01-31 11:02:37 -08:00
Zack Spear
26841aa10d refactor: Registration component onBeforeMount 2024-01-31 11:02:37 -08:00
Zack Spear
e18a8d670e refactor: UnraidCheck writeJsonFile JSON_UNESCAPED_SLASHES 2024-01-31 11:02:37 -08:00
Zack Spear
49d077db97 refactor: UnraidCheck removeAllIgnored fail silently 2024-01-31 11:02:37 -08:00
Zack Spear
9dafe165b0 chore: UnraidCheck.checkForUpdate todo comment 2024-01-31 11:02:37 -08:00
Zack Spear
cce1953cb8 fix: ServerUpdateOsResponse type 2024-01-31 11:02:37 -08:00
Zack Spear
7e33b25593 refactor: header os version update status only when no state error 2024-01-31 11:02:37 -08:00
Zack Spear
78fb49a6fc refactor: only display UPC update links when no stateDataError 2024-01-31 11:02:37 -08:00
Zack Spear
f1e0d93bc5 refactor: header os version reboot type status simplify 2024-01-31 11:02:37 -08:00
Zack Spear
195a178d15 refactor: unraidcheck to use UnraidCheck class 2024-01-31 11:02:37 -08:00
Zack Spear
b9257fce28 refactor: state php to use UnraidCheck class 2024-01-31 11:02:37 -08:00
Zack Spear
41eaf4ef1b fix: lint 2024-01-31 11:02:37 -08:00
Zack Spear
93d0c08955 fix: check update response modal expired key button styles 2024-01-31 11:02:37 -08:00
Zack Spear
c5bc3454ff refactor: UnraidCheck clean up 2024-01-31 11:02:37 -08:00
Zack Spear
c33b4ef709 refactor: consolidate UpdateOS php files into a single class 2024-01-31 11:02:37 -08:00
Zack Spear
ce3ba7d070 refactor: update os check conditional altUrl param 2024-01-31 11:02:37 -08:00
Zack Spear
639eb08291 refactor: modal close button spacing 2024-01-31 11:02:37 -08:00
Zack Spear
6d109b4c4c refactor: upc dropdown conditional update os buttons 2024-01-31 11:02:37 -08:00
Zack Spear
6f3971dc47 refactor: check update response modal copy + alignment 2024-01-31 11:02:37 -08:00
Zack Spear
2ccb503dc8 refactor: lint clean up 2024-01-31 11:02:37 -08:00
Zack Spear
3cb9fdf102 refactor: modal spacing 2024-01-31 11:02:37 -08:00
Zack Spear
40d81a4081 refactor: translations 2024-01-31 11:02:37 -08:00
Zack Spear
5a85f55be8 refactor: header os version update os status pills 2024-01-31 11:02:37 -08:00
Zack Spear
5455e211bc chore: @todo for changelog_pretty 2024-01-31 11:02:37 -08:00
Zack Spear
cb4cc989c7 fix: missing translations 2024-01-31 11:02:37 -08:00
Zack Spear
037aa479bf refactor: improve responsive modal 2024-01-31 11:02:37 -08:00
Zack Spear
08567f287a fix: marked-base-url install 2024-01-31 11:02:37 -08:00
Zack Spear
a57f1d890d fix: changlog relative links and external links 2024-01-31 11:02:37 -08:00
Zack Spear
3ab406e012 refactor: modal styles & content scrollable 2024-01-31 11:02:37 -08:00
Zack Spear
f36f4702a2 fix: lint unused value 2024-01-31 11:02:37 -08:00
Zack Spear
62697f7972 feat: updateOs check response determines if update auth is required 2024-01-31 11:02:37 -08:00
Zack Spear
ec8d2bc0e8 feat: getOsReleaseBySha256 cached endpoint with keyfile header 2024-01-31 11:02:37 -08:00
Zack Spear
d83664b6a3 refactor: update os change modal continue button 2024-01-31 11:02:37 -08:00
Zack Spear
6910a020d2 chore: clean up console log 2024-01-31 11:02:37 -08:00
Zack Spear
60e5c6e3e8 fix: regTm format when already set 2024-01-31 11:02:37 -08:00
Zack Spear
90b1432875 fix: regTm format after key install without page refresh 2024-01-31 11:02:37 -08:00
Zack Spear
f1059aa381 refactor: header os update available badge open update modal 2024-01-31 11:02:37 -08:00
Zack Spear
01b4937f35 refactor: update os ignore release text 2024-01-31 11:02:37 -08:00
Zack Spear
3e051815c5 feat: update os ignore release 2024-01-31 11:02:37 -08:00
Zack Spear
e976daf8b0 refactor: ignore release switch colors 2024-01-31 11:02:37 -08:00
Zack Spear
422046dc03 refactor: registration item label text-right 2024-01-31 11:02:37 -08:00
Zack Spear
9a270971d1 refactor: center registration item without label 2024-01-31 11:02:37 -08:00
Zack Spear
0742382ae1 refactor: registration page conditionals 2024-01-31 11:02:37 -08:00
Zack Spear
763c38430e feat: add manage account link to all versions of upc dropdown 2024-01-31 11:02:37 -08:00
Zack Spear
4d926bba8e refactor: remove update os callback link from upc dropdown 2024-01-31 11:02:37 -08:00
Zack Spear
4acc4ea9a9 fix: ignore release localStorage 2024-01-31 11:02:37 -08:00
Zack Spear
565bf47818 fix: translations 2024-01-31 11:02:37 -08:00
Zack Spear
176a0f30be refactor: check update modal styles 2024-01-31 11:02:37 -08:00
Zack Spear
6f4d983d89 chore: uninstall pinia-plugin-persistedstate 2024-01-31 11:02:37 -08:00
Zack Spear
6a0e258cf2 test: component viewer 2024-01-31 11:02:37 -08:00
Zack Spear
8d82064888 refactor: translations 2024-01-31 11:02:37 -08:00
Zack Spear
4300179b67 refactor: check update response modal styling 2024-01-31 11:02:37 -08:00
Zack Spear
7e31ae2ebf refactor: changelog modal improvements 2024-01-31 11:02:37 -08:00
Zack Spear
7a27560b0d fix: type issue with changlelog modal visibility 2024-01-31 11:02:37 -08:00
Zack Spear
93655fef62 refactor: tailwind prose styles 2024-01-31 11:02:37 -08:00
Zack Spear
a581a95cb4 chore: formatting 2024-01-31 11:02:37 -08:00
Zack Spear
261fdda47c test: update data 2024-01-31 11:02:37 -08:00
Zack Spear
7a2a243a21 refactor: translations for new check update modals 2024-01-31 11:02:37 -08:00
Zack Spear
bead4256af feat: new check update buttons in dropdown 2024-01-31 11:02:37 -08:00
Zack Spear
e8dfd7e3b3 feat: update modals 2024-01-31 11:02:37 -08:00
Zack Spear
e456b7fcac feat: changelog modal 2024-01-31 11:02:37 -08:00
Zack Spear
fbe5e417ef feat: check update response modal 2024-01-31 11:02:37 -08:00
Zack Spear
5f80053a33 refactor: test page 2024-01-31 11:02:37 -08:00
Zack Spear
fa520a2d3e feat: button add underline-hover-red style option 2024-01-31 11:02:37 -08:00
Zack Spear
cf54f01945 chore: install marked 2024-01-31 11:02:37 -08:00
Zack Spear
44d2d58f12 refactor: modal spacing 2024-01-31 11:02:37 -08:00
Zack Spear
daba2a352f feat: updateOs store call local server-side endpoint & add modal support 2024-01-31 11:02:37 -08:00
Zack Spear
d1ff2b1fad refactor: button props type usage 2024-01-31 11:02:37 -08:00
Zack Spear
b1bd71f2e2 refactor: updateOs callback button action 2024-01-31 11:02:37 -08:00
Zack Spear
7f49816275 refactor: use account store updateOs callback 2024-01-31 11:02:37 -08:00
Zack Spear
d73d460e88 feat: create WebguiCheckForUpdate endpoint 2024-01-31 11:02:37 -08:00
Zack Spear
ab1e852b6c refactor: abstract button compnoent props type 2024-01-31 11:02:37 -08:00
Zack Spear
117b7430db chore: organize npm scripts & install pinia-plugin-persistedstate 2024-01-31 11:02:37 -08:00
Zack Spear
2e73f9e37a refactor: account updateOs callback 2024-01-31 11:02:37 -08:00
Zack Spear
d3158983b4 refactor: ServerUpdateOsResponse type 2024-01-31 11:02:37 -08:00
Zack Spear
dae7baa6ad refactor: server state parsedRegExp & set updateOsResponse 2024-01-31 11:02:37 -08:00
Zack Spear
e29f5e1adf feat: WebguiCheckForUpdate using server-side check 2024-01-31 11:02:37 -08:00
Zack Spear
e8d15c7dbb refactor: nuxt auto import components 2024-01-31 11:02:37 -08:00
Zack Spear
58be009da4 feat: unraidcheck callable from webgui with altUrl & json output 2024-01-31 11:02:37 -08:00
Eli Bosley
d4eb0ce3f2 feat: add new staging url for connect website (#841)
* feat: add new staging url for connect website

* feat: add url to plg
2024-01-12 13:42:15 -05:00
Eli Bosley
d73324a141 feat: upgrade a ton of dependencies (#842)
* feat: upgrade a ton of dependencies
2024-01-12 13:05:51 -05:00
renovate[bot]
7061be60f4 chore(deps): update docker/build-push-action action to v5 (#826)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-12 11:03:19 -05:00
renovate[bot]
2a65f64ac1 fix(deps): update dependency ws to v8.16.0 (#815)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-12 11:03:02 -05:00
Eli Bosley
120ba3e447 chore(release): 3.4.0 2024-01-11 17:28:39 -05:00
Eli Bosley
a527c7183a fix: run hourly 2024-01-11 17:25:57 -05:00
Eli Bosley
a0dfbb4e15 feat: add logrotate to cron in nestjs (#839)
* feat: add logrotate to cron in nestjs

* fix: set max size to 5m and remove old logs

* fix: logrotate command invalid
2024-01-11 17:17:23 -05:00
Eli Bosley
764f65ff61 fix: allow failure for log deletion 2024-01-11 16:44:25 -05:00
Eli Bosley
1615e8623c fix: excessive logging 2024-01-11 16:34:10 -05:00
Eli Bosley
d7bb9ff073 fix: allowed origins check not working without spaces (#838)
* fix: allowed origins check not working without spaces

* fix: broken test
2024-01-11 10:52:42 -05:00
Eli Bosley
d896581e12 chore(release): 3.3.0 2024-01-09 17:07:42 -05:00
ljm42
f833fa1fab fix: patch ShowChanges.php in 6.10 2024-01-09 13:51:00 -08:00
Zack Spear
2823517b26 fix: 6.10 view release notes js 2024-01-09 13:51:00 -08:00
Zack Spear
3e0a8d0070 fix: unraid-api server state refresh after key extension use regExp 2024-01-09 12:41:59 -08:00
Eli Bosley
b3768d65aa fix: codegen on web run 2024-01-09 12:15:38 -08:00
Eli Bosley
d2e17c0051 fix: local container startup commands cleaned up 2024-01-09 11:29:23 -08:00
Eli Bosley
d0354c2ef2 feat: local start command 2024-01-09 11:29:23 -08:00
Eli Bosley
17fc1181c2 feat: add support for expiration in var.ini (#833)
* feat: add support for expiration in var.ini

* tests: update snapshots
2024-01-09 12:19:59 -05:00
renovate[bot]
dbe7c5fb93 chore(deps): update actions/checkout action to v4 (#817)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 09:59:26 -05:00
renovate[bot]
54b421d01f chore(deps): update actions/download-artifact action to v4 (#818)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 09:59:14 -05:00
renovate[bot]
0e9611f802 chore(deps): update actions/upload-artifact action to v4 (#820)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 09:59:02 -05:00
renovate[bot]
4743a2439d chore(deps): update actions/setup-node action to v4 (#819)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-09 09:58:34 -05:00
Zack Spear
242c167f82 fix: refreshServerState check regExp 2024-01-08 13:12:33 -06:00
Zack Spear
e071b994cf refactor: installKey.install failure handling 2024-01-08 13:12:14 -06:00
Zack Spear
60bb8aa0fa chore: translations 2024-01-08 13:11:47 -06:00
Zack Spear
9f56f34ea4 fix: renew callback messaging in modal 2024-01-08 13:11:23 -06:00
Zack Spear
d66b33e600 fix: replaceRenew response cache use & purge 2024-01-08 12:31:20 -06:00
Zack Spear
63b7c0361e feat: npm scripts to prevent webgui builds with wrong urls 2024-01-08 11:13:22 -06:00
renovate[bot]
dea66ff49d fix(deps): update nest monorepo (#816)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-05 13:41:48 -05:00
renovate[bot]
c29621741e chore(deps): update dependency @swc/core to v1.3.102 (#700)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-05 09:52:22 -05:00
renovate[bot]
6d7d013f7a fix(deps): update graphql-tools monorepo (major) (#693)
fix(deps): update graphql-tools monorepo

Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2024-01-05 09:51:46 -05:00
Eli Bosley
ed8e2420a5 fix: logrotate not working due to invalid ownership of unraid-api folder 2024-01-05 09:47:27 -05:00
Zack Spear
92ad66dd59 refactor: removed new key type translations 2024-01-04 12:04:00 -08:00
Zack Spear
5699e34ae9 refactor: sort translations alpha order case insensitive 2024-01-04 12:04:00 -08:00
Zack Spear
5f9dc26173 refactor: Registration page link order (#760) 2024-01-04 11:19:36 -07:00
Zack Spear
6d29ec2b90 fix: azure and gray theme custom colors 2024-01-03 13:50:36 -08:00
Eli Bosley
f91ae5c7a0 fix: rearrange exit hook to try to fix closing 2024-01-03 15:12:17 -05:00
Eli Bosley
33c69bf76f feat: fix exit hook and cleanup docker scripts (#758)
* feat: cleanup docker scripts

* feat: make logging directory if non-existent to fix stop behavior
2024-01-03 12:12:37 -05:00
ljm42
3e95bb259f fix: plugin install should suppress output from unraid-api stop (#757) 2024-01-03 10:10:37 -07:00
Eli Bosley
64b6bee559 fix: exit with process.exit not process.exitcode 2024-01-03 11:23:38 -05:00
Eli Bosley
61cb029780 feat: stretch downgrade component buttons 2024-01-03 08:13:56 -08:00
Eli Bosley
c02d823618 feat: fix logging format on start and stop 2024-01-03 10:43:00 -05:00
ljm42
fec36919c2 feat: change sort order of Update/Downgrade (#754) 2024-01-03 10:39:36 -05:00
262 changed files with 22188 additions and 13938 deletions

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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: |

View 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
View File

@@ -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

View File

@@ -0,0 +1 @@
{"api":"3.10.0","web":"3.10.0"}

View File

@@ -29,5 +29,6 @@
"i18n-ally.localesPaths": [
"locales"
],
"i18n-ally.keystyle": "flat"
"i18n-ally.keystyle": "flat",
"eslint.experimental.useFlatConfig": true,
}

View File

@@ -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
View 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

View File

@@ -1 +1 @@
18.17.1
18.19.1

View File

@@ -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.

View File

@@ -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
View 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.

View File

@@ -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"

View File

@@ -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
View 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
View 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"

View File

@@ -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
View 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

File diff suppressed because it is too large Load Diff

View File

@@ -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
View 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 "$@"

View 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",
]
`);
});

View File

@@ -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);

View File

@@ -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": [
"*",

View File

@@ -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,

View File

@@ -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();
});

View File

@@ -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",

View File

@@ -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,
},
}
`);
});

View 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"
"
`);
});

View File

@@ -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)

View 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],
]
`);
});

View 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);
});

View File

@@ -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: {

View File

@@ -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",

View File

@@ -20,6 +20,7 @@ test('Returns paths', async () => {
"myservers-config",
"myservers-config-states",
"myservers-env",
"myservers-keepalive",
"keyfile-base",
"machine-id",
"log-base",

View File

@@ -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": "",
}
`;

View 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();
});

View File

@@ -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",

View File

@@ -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();
};

View File

@@ -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();

View File

@@ -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) {

View File

@@ -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');

View 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));

View File

@@ -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;
};

View File

@@ -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;

View File

@@ -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 =

View File

@@ -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
}

View File

@@ -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,

View File

@@ -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,
};
};

View File

@@ -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 (

View File

@@ -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';

View File

@@ -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: '*' },

View 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[];
}

View File

@@ -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. */

View File

@@ -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,

View 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');
};

View File

@@ -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';

View File

@@ -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(),

View File

@@ -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>;

View File

@@ -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';

View 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;

View File

@@ -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>;

View 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)
}
`);

View File

@@ -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
}
}

View File

@@ -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);
}
};

View File

@@ -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);
}
}
};

View File

@@ -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"""

View File

@@ -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!
}

View 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!
}

View File

@@ -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!
}

View File

@@ -49,6 +49,7 @@ enum DiskFsType {
xfs
btrfs
vfat
zfs
}
enum DiskInterfaceType {

View 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
}

View File

@@ -0,0 +1,9 @@
type Docker implements Node {
id: ID!
containers: [DockerContainer!]
networks: [DockerNetwork!]
}
type Query {
docker: Docker!
}

View File

@@ -0,0 +1,3 @@
type Info implements Node {
id: ID!
}

View File

@@ -0,0 +1,3 @@
type Info {
time: DateTime!
}

View 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!]
}

View File

@@ -22,10 +22,6 @@ input NotificationFilter {
limit: Int!
}
type Mutation {
sendNotification(notification: NotificationInput!): Notification
}
type Query {
notifications(filter: NotificationFilter!): [Notification!]!
}

View File

@@ -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
}

View 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!]!
}

View File

@@ -13,7 +13,8 @@ enum ConfigErrorState {
WITHDRAWN
}
type Vars {
type Vars implements Node {
id: ID!
"""
Unraid version
"""

View File

@@ -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);
}
);

View 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));
}
}
}

View File

@@ -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;
}

View 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;
}

View 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'));
}
}

View 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'
);
}
}

View 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' },
});
}
}

View File

@@ -1,5 +0,0 @@
export enum DynamicRemoteAccessType {
UPNP = 'UPNP',
STATIC = 'STATIC',
DISABLED = 'DISABLED',
}

View File

@@ -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;
}
});

View File

@@ -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';

View File

@@ -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();
};

View File

@@ -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,

View 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 });
}
},
});

View File

@@ -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';

View 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;

View File

@@ -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: {},
});

View 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[],
};
};

View File

@@ -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