mirror of
https://github.com/unraid/api.git
synced 2026-01-02 22:50:02 -06:00
Compare commits
108 Commits
v3.5.1
...
refactor/c
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff1651f523 | ||
|
|
95b203285b | ||
|
|
e85bb28498 | ||
|
|
368cec8641 | ||
|
|
0ca57389f3 | ||
|
|
1229a0ce69 | ||
|
|
c8f469c4fb | ||
|
|
bc61b45f9f | ||
|
|
f530d9ea82 | ||
|
|
2046fa5310 | ||
|
|
9ea2327fa0 | ||
|
|
ff67b54a1b | ||
|
|
e6bd7a54be | ||
|
|
5827b5ffa3 | ||
|
|
572a1310e0 | ||
|
|
c1403d3826 | ||
|
|
29afe9b9e8 | ||
|
|
e9ff33d263 | ||
|
|
a62f60a436 | ||
|
|
838964c6ef | ||
|
|
800fc12c15 | ||
|
|
80175241e3 | ||
|
|
5d801f22f5 | ||
|
|
ba772add54 | ||
|
|
ff24f12cae | ||
|
|
487f5c1865 | ||
|
|
e0c90037fb | ||
|
|
aa5f603cba | ||
|
|
409db43973 | ||
|
|
cef1b29355 | ||
|
|
045750c87e | ||
|
|
85802e7af7 | ||
|
|
4bfdb66d46 | ||
|
|
81a6a52d9f | ||
|
|
7759fe1dc3 | ||
|
|
3b2acb29b5 | ||
|
|
5f2b949ecf | ||
|
|
1b956d563e | ||
|
|
c6a97f5082 | ||
|
|
7f512e47e9 | ||
|
|
5d725b0e76 | ||
|
|
fe63607260 | ||
|
|
0a1d4daf6e | ||
|
|
9e9e385bef | ||
|
|
6fed39e05b | ||
|
|
3dec53d13d | ||
|
|
f0ded9f5be | ||
|
|
7d55a1c2cd | ||
|
|
f3dc9663b8 | ||
|
|
05c7c481a9 | ||
|
|
adcc1543f0 | ||
|
|
95f873c752 | ||
|
|
ec90f8b295 | ||
|
|
f84195a98d | ||
|
|
5e98a68e2e | ||
|
|
b91dbca144 | ||
|
|
79a01da18d | ||
|
|
14951d3004 | ||
|
|
64c2061bea | ||
|
|
e3adc9a29a | ||
|
|
6b689ffcce | ||
|
|
c995a4c5c8 | ||
|
|
8d1e0f67d1 | ||
|
|
7877a5dca2 | ||
|
|
16db278ffd | ||
|
|
521b4381f2 | ||
|
|
9ae9d40f94 | ||
|
|
1d562d404c | ||
|
|
7ac1b268d9 | ||
|
|
4833e9dccf | ||
|
|
f28b7510fa | ||
|
|
37b717b142 | ||
|
|
fd8b40d9aa | ||
|
|
1d944781cf | ||
|
|
1f4c64d022 | ||
|
|
f69b5130a3 | ||
|
|
f8b143904b | ||
|
|
31a5413643 | ||
|
|
a95fc5ed07 | ||
|
|
fcd7bb790e | ||
|
|
008e10948e | ||
|
|
c97a4f1268 | ||
|
|
3eba95b8cc | ||
|
|
2bf8f0b937 | ||
|
|
9ae45d1258 | ||
|
|
1835a4cf3f | ||
|
|
2ab44b894d | ||
|
|
1108f49b07 | ||
|
|
cc69213beb | ||
|
|
460e557dd8 | ||
|
|
05e29468d2 | ||
|
|
4d3a311fb4 | ||
|
|
bc62d210ec | ||
|
|
43d3ea6553 | ||
|
|
882e3e1ef4 | ||
|
|
b33c86c99c | ||
|
|
cd0248e4c9 | ||
|
|
ecb3ed5003 | ||
|
|
0569339a41 | ||
|
|
3e9faead43 | ||
|
|
6e700b2385 | ||
|
|
464fc4993c | ||
|
|
4316c72809 | ||
|
|
ce0cebe09c | ||
|
|
23b90a0d56 | ||
|
|
3f8b3536b5 | ||
|
|
0dcf785b45 | ||
|
|
8cf4aff622 |
4
.github/workflows/main.yml
vendored
4
.github/workflows/main.yml
vendored
@@ -81,10 +81,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:
|
||||
|
||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -83,4 +83,6 @@ deploy/*
|
||||
.cache
|
||||
.output
|
||||
.env*
|
||||
!.env.example
|
||||
!.env.example
|
||||
|
||||
fb_keepalive
|
||||
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@@ -29,5 +29,6 @@
|
||||
"i18n-ally.localesPaths": [
|
||||
"locales"
|
||||
],
|
||||
"i18n-ally.keystyle": "flat"
|
||||
"i18n-ally.keystyle": "flat",
|
||||
"eslint.experimental.useFlatConfig": true,
|
||||
}
|
||||
11
api/.env.test
Normal file
11
api/.env.test
Normal file
@@ -0,0 +1,11 @@
|
||||
VERSION="THIS_WILL_BE_REPLACED_WHEN_BUILT"
|
||||
|
||||
PATHS_UNRAID_DATA=./dev/data # Where we store plugin data (e.g. permissions.json)
|
||||
PATHS_STATES=./dev/states # Where .ini files live (e.g. vars.ini)
|
||||
PATHS_DYNAMIX_BASE=./dev/dynamix # Dynamix's data directory
|
||||
PATHS_DYNAMIX_CONFIG=./dev/dynamix/dynamix.cfg # Dynamix's config file
|
||||
PATHS_MY_SERVERS_CONFIG=./dev/Unraid.net/myservers.cfg # My servers config file
|
||||
PATHS_MY_SERVERS_FB=./dev/Unraid.net/fb_keepalive # My servers flashbackup timekeeper file
|
||||
PATHS_KEYFILE_BASE=./dev/Unraid.net # Keyfile location
|
||||
PORT=5000
|
||||
NODE_ENV=test
|
||||
@@ -1 +1 @@
|
||||
18.17.1
|
||||
18.19.1
|
||||
@@ -2,6 +2,102 @@
|
||||
|
||||
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.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)
|
||||
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
58
api/README.md
Normal file
58
api/README.md
Normal file
@@ -0,0 +1,58 @@
|
||||
# @unraid/api
|
||||
|
||||
## Installation
|
||||
|
||||
Install the production plugin via the apps tab (search for "my servers") on Unraid 6.9.2 or later.
|
||||
|
||||
## CLI
|
||||
|
||||
If you're on a unraid v6.9.2 or later machine this should be available by running `unraid-api` in any directory.
|
||||
|
||||
```bash
|
||||
root@Devon:~# unraid-api
|
||||
|
||||
Unraid API
|
||||
|
||||
Thanks for using the official Unraid API
|
||||
|
||||
Usage:
|
||||
|
||||
$ unraid-api command <options>
|
||||
|
||||
Commands:
|
||||
|
||||
start/stop/restart/version/status/report/switch-env
|
||||
|
||||
Options:
|
||||
|
||||
-h, --help Prints this usage guide.
|
||||
-d, --debug Enabled debug mode.
|
||||
-p, --port string Set the graphql port.
|
||||
--environment production/staging/development Set the working environment.
|
||||
--log-level ALL/TRACE/DEBUG/INFO/WARN/ERROR/FATAL/MARK/OFF Set the log level.
|
||||
|
||||
Copyright © 2022 Lime Technology, Inc.
|
||||
|
||||
```
|
||||
|
||||
## Report
|
||||
To view the current status of the unraid-api and its connection to mothership, run:
|
||||
```
|
||||
unraid-api report
|
||||
```
|
||||
|
||||
To view verbose data (anonymized), run:
|
||||
```
|
||||
unraid-api report -v
|
||||
```
|
||||
|
||||
To view non-anonymized verbose data, run:
|
||||
```
|
||||
unraid-api report -vv
|
||||
```
|
||||
|
||||
## Secrets
|
||||
If you found this file you're likely a developer. If you'd like to know more about the API and when it's available please join [our discord](https://discord.unraid.net/).
|
||||
|
||||
## License
|
||||
Copyright 2019-2022 Lime Technology Inc. All rights reserved.
|
||||
@@ -1,5 +1,5 @@
|
||||
[api]
|
||||
version="3.4.0"
|
||||
version="3.5.2+20f10951"
|
||||
extraOrigins="https://google.com,https://test.com"
|
||||
[local]
|
||||
[notifier]
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
[api]
|
||||
version="3.4.0"
|
||||
version="3.5.2+20f10951"
|
||||
extraOrigins="https://google.com,https://test.com"
|
||||
[local]
|
||||
[notifier]
|
||||
@@ -21,4 +21,5 @@ dynamicRemoteAccessType="DISABLED"
|
||||
[upc]
|
||||
apikey="unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810"
|
||||
[connectionStatus]
|
||||
minigraph="PRE_INIT"
|
||||
minigraph="ERROR_RETRYING"
|
||||
upnpStatus="Success: UPNP Lease Renewed [4/24/2024 5:04:54 PM] Public Port [59138] Local Port [443]"
|
||||
|
||||
229
api/docs/development.md
Normal file
229
api/docs/development.md
Normal file
@@ -0,0 +1,229 @@
|
||||
# Development
|
||||
|
||||
## Installation
|
||||
|
||||
Install the [production](https://unraid-dl.sfo2.digitaloceanspaces.com/unraid-api/dynamix.unraid.net.plg) or [staging](https://unraid-dl.sfo2.digitaloceanspaces.com/unraid-api/dynamix.unraid.net.staging.plg) plugin on Unraid 6.9.0-rc1 or later (6.9.2 or higher recommended).
|
||||
|
||||
## Connecting to the API
|
||||
|
||||
### HTTP
|
||||
|
||||
This can be accessed by default via `http://tower.local/graphql`.
|
||||
|
||||
See <https://graphql.org/learn/serving-over-http/#http-methods-headers-and-body>
|
||||
|
||||
### WS
|
||||
|
||||
If you're using the ApolloClient please see <https://github.com/apollographql/subscriptions-transport-ws#full-websocket-transport> otherwise see <https://github.com/apollographql/subscriptions-transport-ws/blob/master/PROTOCOL.md>
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Building in Docker
|
||||
|
||||
To get a development environment for testing start by running this docker command:
|
||||
|
||||
``docker compose run build-interactive``
|
||||
|
||||
which will give you an interactive shell inside of the newly build linux container.
|
||||
|
||||
To automatically build the plugin run the command below:
|
||||
|
||||
``docker compose run builder``
|
||||
|
||||
The builder command will build the plugin into deploy/release, and the interactive plugin lets you build the plugin or install node modules how you like.
|
||||
|
||||
## Logs
|
||||
|
||||
Logging can be configured via environment variables.
|
||||
|
||||
Log levels can be set when the api starts via `LOG_LEVEL=all/trace/debug/info/warn/error/fatal/mark/off`.
|
||||
|
||||
Additional detail for the log entry can be added with `LOG_CONTEXT=true` (warning, generates a lot of data).
|
||||
|
||||
By default, logs will be sent to syslog. Or you can set `LOG_TRANSPORT=file` to have logs saved in `/var/log/unraid-api/stdout.log`. Or enable debug mode to view logs inline.
|
||||
|
||||
Examples:
|
||||
|
||||
* `unraid-api start`
|
||||
* `LOG_LEVEL=debug unraid-api start --debug`
|
||||
* `LOG_LEVEL=trace LOG_CONTEXT=true LOG_TRANSPORT=file unraid-api start`
|
||||
|
||||
Log levels can be increased without restarting the api by issuing this command:
|
||||
|
||||
```
|
||||
kill -s SIGUSR2 `pidof unraid-api`
|
||||
```
|
||||
|
||||
and decreased via:
|
||||
|
||||
```
|
||||
kill -s SIGUSR1 `pidof unraid-api`
|
||||
```
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Viewing data sent to mothership
|
||||
|
||||
If the environment variable `LOG_MOTHERSHIP_MESSAGES=true` exists, any data the unraid-api sends to mothership will be saved in clear text here: `/var/log/unraid-api/relay-messages.log`
|
||||
|
||||
Examples:
|
||||
|
||||
* `LOG_MOTHERSHIP_MESSAGES=true unraid-api start`
|
||||
* `LOG_MOTHERSHIP_MESSAGES=true LOG_LEVEL=debug unraid-api start --debug`
|
||||
<br>
|
||||
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Debug mode
|
||||
|
||||
Debug mode can be enabled with the `-d` or `--debug` flag.
|
||||
This will enable the graphql playground and prevent the application starting as a daemon. Logs will be shown inline rather than saved to a file.
|
||||
|
||||
Examples:
|
||||
|
||||
* `unraid-api start --debug`
|
||||
* `LOG_LEVEL=debug unraid-api start --debug`
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Crash API On Demand
|
||||
|
||||
The `PLEASE_SEGFAULT_FOR_ME` env var can be to used to make the api crash after 30 seconds:
|
||||
|
||||
Examples:
|
||||
|
||||
* `PLEASE_SEGFAULT_FOR_ME=true LOG_LEVEL=debug unraid-api start --debug`
|
||||
* `PLEASE_SEGFAULT_FOR_ME=true unraid-api start`
|
||||
|
||||
The crash log will be stored here:
|
||||
|
||||
* `/var/log/unraid-api/crash.log`
|
||||
* `/var/log/unraid-api/crash.json`
|
||||
|
||||
`crash.json` just includes the most recent crash, while the reports get appended to `crash.log`.
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Switching between staging and production environments
|
||||
|
||||
1. Stop the api: `unraid-api stop`
|
||||
2. Switch environments: `unraid-api switch-env`
|
||||
3. Start the api: `unraid-api start`
|
||||
4. Confirm the environment: `unraid-api report`
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Playground
|
||||
|
||||
The playground can be access via `http://tower.local/graphql` while in debug mode.
|
||||
To get your API key open a terminal on your server and run `cat /boot/config/plugins/dynamix.my.servers/myservers.cfg | grep apikey=\"unraid | cut -d '"' -f2`. Add that API key in the "HTTP headers" panel of the playground.
|
||||
|
||||
```json
|
||||
{
|
||||
"x-api-key":"__REPLACE_ME_WITH_API_KEY__"
|
||||
}
|
||||
```
|
||||
|
||||
Next add the query you want to run and hit the play icon.
|
||||
|
||||
```gql
|
||||
query welcome {
|
||||
welcome {
|
||||
message
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
You should get something like this back.
|
||||
|
||||
```json
|
||||
{
|
||||
"data": {
|
||||
"welcome": {
|
||||
"message": "Welcome root to this Unraid 6.10.0 server"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Click the "Schema" and "Docs" button on the right side of the playground to learn more.
|
||||
|
||||
For exploring the schema visually I'd suggest using [Voyager](https://apis.guru/graphql-voyager/) (click Change Schema -> Introspection, then copy/paste the introspection query into the local Graph Playground, and copy/paste the results back into Voyager).
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Running this locally
|
||||
|
||||
```bash
|
||||
MOTHERSHIP_RELAY_WS_LINK=ws://localhost:8000 \ # Switch to local copy of mothership
|
||||
PATHS_UNRAID_DATA=$(pwd)/dev/data \ # Where we store plugin data (e.g. permissions.json)
|
||||
PATHS_STATES=$(pwd)/dev/states \ # Where .ini files live (e.g. vars.ini)
|
||||
PATHS_DYNAMIX_BASE=$(pwd)/dev/dynamix \ # Dynamix's data directory
|
||||
PATHS_DYNAMIX_CONFIG=$(pwd)/dev/dynamix/dynamix.cfg \ # Dynamix's config file
|
||||
PATHS_MY_SERVERS_CONFIG=$(pwd)/dev/Unraid.net/myservers.cfg \ # My servers config file
|
||||
PORT=8500 \ # What port unraid-api should start on (e.g. /var/run/unraid-api.sock or 8000)
|
||||
node dist/cli.js --debug # Enable debug logging
|
||||
```
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Create a new release
|
||||
|
||||
To create a new version run `npm run release` and then run **ONLY** the `git push` section of the commands it returns.
|
||||
To create a new prerelease run `npm run release -- --prerelease alpha`.
|
||||
|
||||
Pushing to this repo will cause an automatic "rolling" release to be built which can be accessed via the page for the associated Github action run.
|
||||
|
||||
<br>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
## Using a custom version (e.g. testing a new release)
|
||||
|
||||
1. Install the staging or production plugin (links in the Installation section at the top of this file)
|
||||
2. Download or build the api tgz file you want
|
||||
|
||||
* Download from [the releases page](https://github.com/unraid/api/releases)
|
||||
* Build it on your local machine (``docker compose run builder``) and copy from the `deploy/release` folder
|
||||
|
||||
3. Copy the file to `/boot/config/plugins/dynamix.my.servers/unraid-api.tgz`.
|
||||
4. Install the new api: `/etc/rc.d/rc.unraid-api (install / _install)`
|
||||
|
||||
* `_install` will no start the plugin for you after running, so you can make sure you launch in dev mode
|
||||
* `install` will start the plugin after install
|
||||
5. Start the api: `unraid-api start`
|
||||
6. Confirm the version: `unraid-api report`
|
||||
|
||||
## Cloning Secrets from AWS
|
||||
|
||||
1. Go here to create security credentials for your user [S3 Creds](https://us-east-1.console.aws.amazon.com/iam/home?region=us-east-1&skipRegion=true#/security_credentials)
|
||||
2. Export your AWS secrets OR run `aws configure` to setup your environment
|
||||
|
||||
```sh
|
||||
export AWS_ACCESS_KEY_ID=AKIAIOSFODNN7EXAMPLE
|
||||
export AWS_SECRET_ACCESS_KEY=wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY
|
||||
export AWS_DEFAULT_REGION=us-east-1
|
||||
|
||||
```
|
||||
|
||||
3. Set variables for staging and production to the ARN of the secret you would like to clone:
|
||||
|
||||
* `STAGING_SECRET_ID`
|
||||
* `PRODUCTION_SECRET_ID`
|
||||
|
||||
4. Run `scripts/copy-env-from-aws.sh` to pull down the secrets into their respective files
|
||||
4623
api/package-lock.json
generated
4623
api/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
116
api/package.json
116
api/package.json
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@unraid/api",
|
||||
"version": "3.5.1",
|
||||
"version": "3.8.1",
|
||||
"main": "dist/index.js",
|
||||
"bin": "dist/unraid-api.cjs",
|
||||
"type": "module",
|
||||
@@ -59,21 +59,21 @@
|
||||
"unraid-api"
|
||||
],
|
||||
"dependencies": {
|
||||
"@apollo/client": "^3.8.9",
|
||||
"@apollo/server": "^4.10.0",
|
||||
"@apollo/client": "^3.10.4",
|
||||
"@apollo/server": "^4.10.4",
|
||||
"@as-integrations/fastify": "^2.1.1",
|
||||
"@graphql-codegen/client-preset": "^4.1.0",
|
||||
"@graphql-codegen/client-preset": "^4.2.5",
|
||||
"@graphql-tools/load-files": "^7.0.0",
|
||||
"@graphql-tools/merge": "^9.0.1",
|
||||
"@graphql-tools/schema": "^10.0.2",
|
||||
"@graphql-tools/utils": "^10.0.12",
|
||||
"@nestjs/apollo": "^12.0.11",
|
||||
"@nestjs/core": "^10.3.0",
|
||||
"@nestjs/graphql": "^12.0.11",
|
||||
"@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.0",
|
||||
"@nestjs/schedule": "^4.0.0",
|
||||
"@reduxjs/toolkit": "^2.0.1",
|
||||
"@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",
|
||||
@@ -84,7 +84,7 @@
|
||||
"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.1",
|
||||
"cli-table": "^0.3.11",
|
||||
@@ -94,21 +94,21 @@
|
||||
"cross-fetch": "^4.0.0",
|
||||
"docker-event-emitter": "^0.3.0",
|
||||
"dockerode": "^3.3.5",
|
||||
"dotenv": "^16.3.1",
|
||||
"express": "^4.18.2",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.19.2",
|
||||
"find-process": "^1.4.7",
|
||||
"graphql": "^16.8.1",
|
||||
"graphql-fields": "^2.0.3",
|
||||
"graphql-scalars": "^1.22.4",
|
||||
"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.3",
|
||||
"graphql-ws": "^5.16.0",
|
||||
"htpasswd-js": "^1.0.2",
|
||||
"ini": "^4.1.1",
|
||||
"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",
|
||||
@@ -117,79 +117,79 @@
|
||||
"nestjs-pino": "^4.0.0",
|
||||
"node-cache": "^5.1.2",
|
||||
"node-window-polyfill": "^1.0.2",
|
||||
"openid-client": "^5.6.4",
|
||||
"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.17.2",
|
||||
"pino": "^9.1.0",
|
||||
"pino-http": "^9.0.0",
|
||||
"pino-pretty": "^10.3.1",
|
||||
"pino-pretty": "^11.0.0",
|
||||
"reflect-metadata": "^0.1.14",
|
||||
"request": "^2.88.2",
|
||||
"semver": "^7.5.4",
|
||||
"semver": "^7.6.2",
|
||||
"stoppable": "^1.1.0",
|
||||
"systeminformation": "^5.21.22",
|
||||
"systeminformation": "^5.22.9",
|
||||
"ts-command-line-args": "^2.5.1",
|
||||
"uuid": "^9.0.1",
|
||||
"ws": "^8.13.0",
|
||||
"wtfnode": "^0.9.1",
|
||||
"ws": "^8.17.0",
|
||||
"wtfnode": "^0.9.2",
|
||||
"xhr2": "^0.2.1",
|
||||
"zod": "^3.22.4"
|
||||
"zod": "^3.23.8"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/runtime": "^7.23.8",
|
||||
"@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.1",
|
||||
"@graphql-codegen/typescript": "^4.0.1",
|
||||
"@graphql-codegen/typescript-operations": "^4.0.1",
|
||||
"@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.3.0",
|
||||
"@swc/core": "^1.3.102",
|
||||
"@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.16",
|
||||
"@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.14.202",
|
||||
"@types/lodash": "^4.17.1",
|
||||
"@types/mustache": "^4.2.5",
|
||||
"@types/node": "^20.11.0",
|
||||
"@types/node": "^20.12.12",
|
||||
"@types/pidusage": "^2.0.5",
|
||||
"@types/pify": "^5.0.4",
|
||||
"@types/semver": "^7.5.6",
|
||||
"@types/semver": "^7.5.8",
|
||||
"@types/sendmail": "^1.4.7",
|
||||
"@types/stoppable": "^1.1.3",
|
||||
"@types/uuid": "^9.0.7",
|
||||
"@types/ws": "^8.5.4",
|
||||
"@types/uuid": "^9.0.8",
|
||||
"@types/ws": "^8.5.10",
|
||||
"@types/wtfnode": "^0.7.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.18.1",
|
||||
"@typescript-eslint/parser": "^6.18.1",
|
||||
"@typescript-eslint/eslint-plugin": "^7.9.0",
|
||||
"@typescript-eslint/parser": "^7.9.0",
|
||||
"@unraid/eslint-config": "github:unraid/eslint-config",
|
||||
"@vitest/coverage-v8": "^1.2.0",
|
||||
"@vitest/ui": "^1.2.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.56.0",
|
||||
"eslint-import-resolver-typescript": "^3.6.1",
|
||||
"eslint-plugin-import": "^2.29.1",
|
||||
"eslint-plugin-prettier": "^5.1.3",
|
||||
"eslint-plugin-unicorn": "^50.0.1",
|
||||
"eslint-plugin-unused-imports": "^3.0.0",
|
||||
"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.12.1",
|
||||
"got": "^13",
|
||||
"graphql-codegen-typescript-validation-schema": "^0.14.1",
|
||||
"ip-regex": "^5.0.0",
|
||||
"json-difference": "^1.16.0",
|
||||
"json-difference": "^1.16.1",
|
||||
"map-obj": "^5.0.2",
|
||||
"p-props": "^5.0.0",
|
||||
"path-exists": "^5.0.0",
|
||||
@@ -198,11 +198,11 @@
|
||||
"pretty-bytes": "^6.1.1",
|
||||
"pretty-ms": "^8.0.0",
|
||||
"standard-version": "^9.5.0",
|
||||
"tsup": "^8.0.1",
|
||||
"typescript": "^5.3.3",
|
||||
"tsup": "^8.0.2",
|
||||
"typescript": "^5.4.5",
|
||||
"typesync": "^0.12.1",
|
||||
"vite-tsconfig-paths": "^4.2.3",
|
||||
"vitest": "^1.2.0",
|
||||
"vite-tsconfig-paths": "^4.3.2",
|
||||
"vitest": "^1.6.0",
|
||||
"zx": "^7.2.3"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
#!/bin/sh
|
||||
|
||||
# Pass all entered params after the docker-compose call
|
||||
GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker-compose -f docker-compose.yml "$@"
|
||||
# Pass all entered params after the docker compose call
|
||||
GIT_SHA=$(git rev-parse --short HEAD) IS_TAGGED=$(git describe --tags --abbrev=0 --exact-match || echo '') docker compose -f docker-compose.yml "$@"
|
||||
|
||||
@@ -20,6 +20,7 @@ test('Returns paths', async () => {
|
||||
"myservers-config",
|
||||
"myservers-config-states",
|
||||
"myservers-env",
|
||||
"myservers-keepalive",
|
||||
"keyfile-base",
|
||||
"machine-id",
|
||||
"log-base",
|
||||
|
||||
@@ -68,7 +68,7 @@ const getRemoteAccessUrlsForAllowedOrigins = (
|
||||
return [];
|
||||
};
|
||||
|
||||
const getExtraOrigins = (): string[] => {
|
||||
export const getExtraOrigins = (): string[] => {
|
||||
const { extraOrigins } = getters.config().api;
|
||||
if (extraOrigins) {
|
||||
return extraOrigins
|
||||
|
||||
@@ -34,6 +34,7 @@ export const FIVE_MINUTES_MS = 5 * ONE_MINUTE;
|
||||
export const TEN_MINUTES_MS = 10 * ONE_MINUTE;
|
||||
export const THIRTY_MINUTES_MS = 30 * ONE_MINUTE;
|
||||
export const ONE_HOUR_MS = 60 * ONE_MINUTE;
|
||||
export const ONE_DAY_MS = ONE_HOUR_MS * 24;
|
||||
|
||||
// Seconds
|
||||
export const ONE_HOUR_SECS = 60 * 60;
|
||||
|
||||
@@ -2,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 { 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, RemoteAccess, Server, ServerStatus, Service, SetupRemoteAccessInput, Share, System, Temperature, Theme, 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<{
|
||||
@@ -702,6 +702,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(),
|
||||
@@ -852,6 +861,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(),
|
||||
|
||||
@@ -874,6 +874,7 @@ export type Query = {
|
||||
dockerNetwork: DockerNetwork;
|
||||
/** All Docker networks */
|
||||
dockerNetworks: Array<Maybe<DockerNetwork>>;
|
||||
extraAllowedOrigins: Array<Scalars['String']['output']>;
|
||||
flash?: Maybe<Flash>;
|
||||
info?: Maybe<Info>;
|
||||
/** Current user account */
|
||||
@@ -883,6 +884,7 @@ export type Query = {
|
||||
owner?: Maybe<Owner>;
|
||||
parityHistory?: Maybe<Array<Maybe<ParityCheck>>>;
|
||||
registration?: Maybe<Registration>;
|
||||
remoteAccess: RemoteAccess;
|
||||
server?: Maybe<Server>;
|
||||
servers: Array<Server>;
|
||||
/** Network Shares */
|
||||
@@ -990,6 +992,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'];
|
||||
@@ -1646,6 +1655,7 @@ export type ResolversTypes = ResolversObject<{
|
||||
Registration: ResolverTypeWrapper<Registration>;
|
||||
RegistrationState: RegistrationState;
|
||||
RelayResponse: ResolverTypeWrapper<RelayResponse>;
|
||||
RemoteAccess: ResolverTypeWrapper<RemoteAccess>;
|
||||
Server: ResolverTypeWrapper<Server>;
|
||||
ServerStatus: ServerStatus;
|
||||
Service: ResolverTypeWrapper<Service>;
|
||||
@@ -1739,6 +1749,7 @@ export type ResolversParentTypes = ResolversObject<{
|
||||
Query: {};
|
||||
Registration: Registration;
|
||||
RelayResponse: RelayResponse;
|
||||
RemoteAccess: RemoteAccess;
|
||||
Server: Server;
|
||||
Service: Service;
|
||||
SetupRemoteAccessInput: SetupRemoteAccessInput;
|
||||
@@ -2312,6 +2323,7 @@ export type QueryResolvers<ContextType = Context, ParentType extends ResolversPa
|
||||
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>;
|
||||
@@ -2320,6 +2332,7 @@ export type QueryResolvers<ContextType = Context, ParentType extends ResolversPa
|
||||
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>;
|
||||
shares?: Resolver<Maybe<Array<Maybe<ResolversTypes['Share']>>>, ParentType, ContextType>;
|
||||
@@ -2347,6 +2360,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>;
|
||||
@@ -2755,6 +2775,7 @@ 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>;
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -27,12 +27,23 @@ 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
|
||||
}
|
||||
|
||||
type Query {
|
||||
remoteAccess: RemoteAccess!
|
||||
extraAllowedOrigins: [String!]!
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
connectSignIn(input: ConnectSignInInput!): Boolean!
|
||||
connectSignOut: Boolean!
|
||||
|
||||
@@ -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: {},
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
import { LogCleanupService } from '@app/unraid-api/cron/log-cleanup.service';
|
||||
import { WriteFlashFileService } from '@app/unraid-api/cron/write-flash-file.service';
|
||||
import { Module } from '@nestjs/common';
|
||||
import { ScheduleModule } from '@nestjs/schedule';
|
||||
|
||||
@Module({
|
||||
imports: [ScheduleModule.forRoot()],
|
||||
providers: [LogCleanupService],
|
||||
providers: [LogCleanupService, WriteFlashFileService],
|
||||
})
|
||||
export class CronModule {}
|
||||
|
||||
55
api/src/unraid-api/cron/write-flash-file.service.ts
Normal file
55
api/src/unraid-api/cron/write-flash-file.service.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ONE_DAY_MS, THIRTY_MINUTES_MS } from '@app/consts';
|
||||
import { sleep } from '@app/core/utils/misc/sleep';
|
||||
import { convertToFuzzyTime } from '@app/mothership/utils/convert-to-fuzzy-time';
|
||||
import { getters } from '@app/store/index';
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
import { Cron } from '@nestjs/schedule';
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
|
||||
@Injectable()
|
||||
export class WriteFlashFileService {
|
||||
constructor() {}
|
||||
private readonly logger = new Logger(WriteFlashFileService.name);
|
||||
private fileLocation = getters.paths()['myservers-keepalive'];
|
||||
public randomizeWriteTime = true;
|
||||
public writeNewTimestamp = async (): Promise<number> => {
|
||||
const wait = this.randomizeWriteTime
|
||||
? convertToFuzzyTime(0, THIRTY_MINUTES_MS)
|
||||
: 0;
|
||||
await sleep(wait);
|
||||
const newDate = new Date();
|
||||
try {
|
||||
await writeFile(this.fileLocation, newDate.toISOString());
|
||||
} catch (error) {
|
||||
this.logger.error(error);
|
||||
}
|
||||
return newDate.getTime();
|
||||
};
|
||||
|
||||
public getOrCreateTimestamp = async (): Promise<number> => {
|
||||
try {
|
||||
const file = (
|
||||
await readFile(this.fileLocation, 'utf-8')
|
||||
).toString();
|
||||
return Date.parse(file);
|
||||
} catch (error) {
|
||||
return await this.writeNewTimestamp();
|
||||
}
|
||||
};
|
||||
|
||||
@Cron('0 * * * *')
|
||||
async handleCron() {
|
||||
try {
|
||||
const currentDate = new Date().getTime();
|
||||
const prevDate = await this.getOrCreateTimestamp();
|
||||
if (currentDate - prevDate > ONE_DAY_MS * 7) {
|
||||
// Write new timestamp
|
||||
await this.writeNewTimestamp();
|
||||
}
|
||||
} catch (error) {
|
||||
// File does not exist, write it
|
||||
await this.writeNewTimestamp();
|
||||
this.logger.error(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
43
api/src/unraid-api/cron/write-flash-file.spec.ts
Normal file
43
api/src/unraid-api/cron/write-flash-file.spec.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
import { Test, type TestingModule } from '@nestjs/testing';
|
||||
import { WriteFlashFileService } from './write-flash-file.service';
|
||||
import { readFileSync, writeFileSync } from 'fs';
|
||||
import { getters } from '@app/store/index';
|
||||
|
||||
describe('WriteFlashFileService', () => {
|
||||
let service: WriteFlashFileService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [WriteFlashFileService],
|
||||
}).compile();
|
||||
|
||||
service = module.get<WriteFlashFileService>(WriteFlashFileService);
|
||||
service.randomizeWriteTime = false;
|
||||
});
|
||||
|
||||
it('should be defined', () => {
|
||||
expect(service).toBeDefined();
|
||||
});
|
||||
|
||||
it('should write and update the file when called', async () => {
|
||||
const timestamp = await service.writeNewTimestamp();
|
||||
expect(timestamp).toBeGreaterThan(0);
|
||||
|
||||
const file = readFileSync(getters.paths()['myservers-keepalive'], 'utf8').toString();
|
||||
expect(file).toBe(new Date(timestamp).toISOString(), 'file contents match the returned timestamp');
|
||||
|
||||
// Now make the file very old
|
||||
writeFileSync(getters.paths()['myservers-keepalive'], '2021-01-01T00:00:00.000Z');
|
||||
expect(readFileSync(getters.paths()['myservers-keepalive'], 'utf8').toString()).toBe('2021-01-01T00:00:00.000Z', 'file was updated');
|
||||
await service.handleCron();
|
||||
expect(readFileSync(getters.paths()['myservers-keepalive'], 'utf8').toString()).not.toBe('2021-01-01T00:00:00.000Z', 'file was updated');
|
||||
|
||||
// Now make the file kind of old (one day )
|
||||
writeFileSync(getters.paths()['myservers-keepalive'], new Date(Date.now() - (1_000 * 60 * 60 * 24)).toISOString());
|
||||
const now = Date.now();
|
||||
await service.handleCron();
|
||||
const contents = readFileSync(getters.paths()['myservers-keepalive'], 'utf8').toString();
|
||||
expect(new Date(contents).getTime() + (1_000 * 60 * 60 * 12)).toBeLessThan(new Date(now).getTime(), 'file was updated but is still older than today');
|
||||
|
||||
});
|
||||
});
|
||||
@@ -1,15 +1,22 @@
|
||||
import { getAllowedOrigins } from '@app/common/allowed-origins';
|
||||
import {
|
||||
getAllowedOrigins,
|
||||
getExtraOrigins,
|
||||
} from '@app/common/allowed-origins';
|
||||
import {
|
||||
WAN_ACCESS_TYPE,
|
||||
WAN_FORWARD_TYPE,
|
||||
type ConnectSignInInput,
|
||||
type SetupRemoteAccessInput,
|
||||
} from '@app/graphql/generated/api/types';
|
||||
import type { Cloud } from '@app/graphql/generated/api/types';
|
||||
import type { Cloud, RemoteAccess } from '@app/graphql/generated/api/types';
|
||||
|
||||
import { connectSignIn } from '@app/graphql/resolvers/mutation/connect/connect-sign-in';
|
||||
import { checkApi } from '@app/graphql/resolvers/query/cloud/check-api';
|
||||
import { checkCloud } from '@app/graphql/resolvers/query/cloud/check-cloud';
|
||||
import { checkMinigraphql } from '@app/graphql/resolvers/query/cloud/check-minigraphql';
|
||||
import { DynamicRemoteAccessType } from '@app/remoteAccess/types';
|
||||
import { setupRemoteAccessThunk } from '@app/store/actions/setup-remote-access';
|
||||
import { store } from '@app/store/index';
|
||||
import { getters, store } from '@app/store/index';
|
||||
import { logoutUser } from '@app/store/modules/config';
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
import { UseRoles } from 'nest-access-control';
|
||||
@@ -45,6 +52,44 @@ export class CloudResolver {
|
||||
};
|
||||
}
|
||||
|
||||
@Query()
|
||||
@UseRoles({
|
||||
resource: 'connect',
|
||||
action: 'read',
|
||||
possession: 'own',
|
||||
})
|
||||
public async remoteAccess(): Promise<RemoteAccess> {
|
||||
const hasWanAccess = getters.config().remote.wanaccess === 'yes';
|
||||
const dynamicRemoteAccessSettings: RemoteAccess = {
|
||||
accessType: hasWanAccess
|
||||
? getters.config().remote.dynamicRemoteAccessType !==
|
||||
DynamicRemoteAccessType.DISABLED
|
||||
? WAN_ACCESS_TYPE.DYNAMIC
|
||||
: WAN_ACCESS_TYPE.ALWAYS
|
||||
: WAN_ACCESS_TYPE.DISABLED,
|
||||
forwardType: getters.config().remote.upnpEnabled
|
||||
? WAN_FORWARD_TYPE.UPNP
|
||||
: WAN_FORWARD_TYPE.STATIC,
|
||||
port: getters.config().remote.wanport
|
||||
? Number(getters.config().remote.wanport)
|
||||
: null,
|
||||
};
|
||||
|
||||
return dynamicRemoteAccessSettings;
|
||||
}
|
||||
|
||||
@Query()
|
||||
@UseRoles({
|
||||
resource: 'connect',
|
||||
action: 'read',
|
||||
possession: 'own',
|
||||
})
|
||||
public async extraAllowedOrigins(): Promise<Array<string>> {
|
||||
const extraOrigins = getExtraOrigins();
|
||||
|
||||
return extraOrigins;
|
||||
}
|
||||
|
||||
@Mutation()
|
||||
@UseRoles({
|
||||
resource: 'connect',
|
||||
|
||||
@@ -161,11 +161,8 @@ exit 0
|
||||
<?
|
||||
$msini = @parse_ini_file('/boot/config/plugins/dynamix.my.servers/myservers.cfg', true);
|
||||
|
||||
# for convenience, scan myservers.cfg for deleteOnUninstall="no" and if that exists,
|
||||
# then skip the rest of the cleanup.
|
||||
$deleteOnUninstall = ($msini === false || empty($msini['plugin']['deleteOnUninstall']) || $msini['plugin']['deleteOnUninstall'] == 'yes');
|
||||
|
||||
if (!$deleteOnUninstall) {
|
||||
# if no_delete_on_uninstall exists on flash drive then skip the rest of the cleanup (useful when switching between staging and production)
|
||||
if (file_exists("/boot/config/plugins/dynamix.my.servers/no_delete_on_uninstall")) {
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@@ -320,16 +317,24 @@ if [ -e /etc/rc.d/rc.unraid-api ]; then
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/showchanges && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/MyServers.page && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/Registration.page && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers1.php && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers2.php && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/sbin/upgradepkg && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
DIR=/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components && [[ -d "$DIR-" ]] && mv -f "$DIR-" "$DIR"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/data/server-state.php && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/reboot-details.php && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php && [[ -f "$FILE-" ]] && mv -f "$FILE-" "$FILE"
|
||||
DIR=/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components
|
||||
# certain instances where the directory is not present and others where it is, ensure we delete it before we restore it
|
||||
if [[ -d "$DIR" ]]; then
|
||||
rm -rf "$DIR"
|
||||
fi
|
||||
if [[ -d "$DIR-" ]]; then
|
||||
mv -f "$DIR-" "$DIR"
|
||||
fi
|
||||
# delete plugin files from flash drive and OS
|
||||
rm -f /boot/config/plugins/dynamix.my.servers/.gitignore
|
||||
rm -f /etc/rc.d/rc.unraid-api
|
||||
@@ -407,23 +412,53 @@ echo
|
||||
|
||||
# NOTE: any 'exit 1' after this point will result in a broken install
|
||||
|
||||
# Preserve in case plugin is removed
|
||||
FILE=/usr/local/emhttp/plugins/dynamix/Registration.page && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.plugin.manager/Downgrade.page && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.plugin.manager/Update.page && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/MyServers.page && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/Registration.page && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers1.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers2.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/sbin/upgradepkg && [[ -f "$FILE" ]] && cp -f "$FILE" "$FILE-"
|
||||
DIR=/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components && [[ -d "$DIR" ]] && mv -f "$DIR" "$DIR-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/data/server-state.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/reboot-details.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
FILE=/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php && [[ -f "$FILE" ]] && mv -f "$FILE" "$FILE-"
|
||||
# Loop through the array of preserveFilesDirs and perform actions
|
||||
# string param format
|
||||
# "{move|copy|move_dir}:{path}:{preventDowngrade|skip}"
|
||||
# move: move the file to a backup file
|
||||
# copy: copy the file to a backup file
|
||||
# move_dir: move the directory to a backup directory
|
||||
# preventDowngrade: during plg install, if the file exists, do not overwrite it if the plg manifest version is less than the installed webgui version
|
||||
# skip: do not perform any action if there is a manifest version difference
|
||||
preserveFilesDirs=(
|
||||
"move:/usr/local/emhttp/plugins/dynamix/Registration.page:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix/include/UpdateDNS.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/Downgrade.page:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/Update.page:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/scripts/unraidcheck:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.plugin.manager/include/UnraidCheck.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/MyServers.page:skip"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/Connect.page:skip"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/Registration.page:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers1.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/include/myservers2.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/include/state.php:preventDowngrade"
|
||||
"copy:/sbin/upgradepkg:skip"
|
||||
"move_dir:/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components:move_dir:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/data/server-state.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/include/reboot-details.php:preventDowngrade"
|
||||
"move:/usr/local/emhttp/plugins/dynamix.my.servers/include/translations.php:preventDowngrade"
|
||||
)
|
||||
|
||||
preserveAction() {
|
||||
local action="$1"
|
||||
local path="$2"
|
||||
|
||||
if [[ "$action" == "move" ]]; then
|
||||
[[ -f "$path" ]] && mv -f "$path" "$path-"
|
||||
elif [[ "$action" == "copy" ]]; then
|
||||
[[ -f "$path" ]] && cp -f "$path" "$path-"
|
||||
elif [[ "$action" == "move_dir" ]]; then
|
||||
[[ -d "$path" ]] && mv -f "$path" "$path-"
|
||||
fi
|
||||
}
|
||||
|
||||
# Loop through the array of preserveFilesDirs and perform actions
|
||||
for obj in "${preserveFilesDirs[@]}"
|
||||
do
|
||||
IFS=':' read -r action path preventType <<< "$obj"
|
||||
preserveAction "$action" "$path" "$preventType"
|
||||
done
|
||||
|
||||
# patch DefaultPageLayout.php
|
||||
# search text: <?=_('Version')?>: <?=_var($var,'version','?')?><?=$notes?>
|
||||
@@ -502,24 +537,101 @@ if test -f "${FILE}" && grep -q "top.Shadowbox" "${FILE}" &>/dev/null; then
|
||||
sed -i 's/top.Shadowbox/parent.Shadowbox/gm' "${FILE}"
|
||||
fi
|
||||
|
||||
# ensure _var() is defined
|
||||
# brings older versions of Unraid in sync with 6.12.0
|
||||
# ensure _var() is defined, brings older versions of Unraid in sync with 6.12.0
|
||||
FILE=/usr/local/emhttp/plugins/dynamix/include/Wrappers.php
|
||||
if test -f "${FILE}" && ! grep -q "_var" "${FILE}" &>/dev/null; then
|
||||
TEXT=$(
|
||||
if test -f "${FILE}" && ! grep -q "function _var" "${FILE}" &>/dev/null; then
|
||||
ADDTEXT1=$(
|
||||
cat <<'END_HEREDOC'
|
||||
// backported by Unraid Connect
|
||||
function _var(&$name, $key=null, $default='') {
|
||||
return is_null($key) ? ($name ?? $default) : ($name[$key] ?? $default);
|
||||
}
|
||||
?>
|
||||
END_HEREDOC
|
||||
)
|
||||
fi
|
||||
# ensure my_logger() is defined, brings older versions of Unraid in sync with 6.13.0
|
||||
if test -f "${FILE}" && ! grep -q "function my_logger" "${FILE}" &>/dev/null; then
|
||||
ADDTEXT2=$(
|
||||
cat <<'END_HEREDOC'
|
||||
// backported by Unraid Connect
|
||||
// ensure params passed to logger are properly escaped
|
||||
function my_logger($message, $logger='webgui') {
|
||||
exec('logger -t '.escapeshellarg($logger).' -- '.escapeshellarg($message));
|
||||
}
|
||||
END_HEREDOC
|
||||
)
|
||||
fi
|
||||
# ensure http_get_contents() is defined, brings older versions of Unraid in sync with 6.13.0
|
||||
if test -f "${FILE}" && ! grep -q "function http_get_contents" "${FILE}" &>/dev/null; then
|
||||
ADDTEXT3=$(
|
||||
cat <<'END_HEREDOC'
|
||||
// backported by Unraid Connect
|
||||
// Original PHP code by Chirp Internet: www.chirpinternet.eu
|
||||
// Please acknowledge use of this code by including this header.
|
||||
// https://www.the-art-of-web.com/php/http-get-contents/
|
||||
// Modified for Unraid
|
||||
/**
|
||||
* Fetches URL and returns content
|
||||
* @param string $url The URL to fetch
|
||||
* @param array $opts Array of options to pass to curl_setopt()
|
||||
* @param array $getinfo Empty array passed by reference, will contain results of curl_getinfo and curl_error
|
||||
* @return string|false $out The fetched content
|
||||
*/
|
||||
function http_get_contents(string $url, array $opts = [], array &$getinfo = NULL) {
|
||||
$ch = curl_init();
|
||||
if(isset($getinfo)) {
|
||||
curl_setopt($ch, CURLINFO_HEADER_OUT, TRUE);
|
||||
}
|
||||
curl_setopt($ch, CURLOPT_URL, $url);
|
||||
curl_setopt($ch, CURLOPT_FRESH_CONNECT, true);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 15);
|
||||
curl_setopt($ch, CURLOPT_TIMEOUT, 45);
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "");
|
||||
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
|
||||
curl_setopt($ch, CURLOPT_REFERER, "");
|
||||
curl_setopt($ch, CURLOPT_FAILONERROR, true);
|
||||
curl_setopt($ch, CURLOPT_USERAGENT, 'Unraid');
|
||||
if(is_array($opts) && $opts) {
|
||||
foreach($opts as $key => $val) {
|
||||
curl_setopt($ch, $key, $val);
|
||||
}
|
||||
}
|
||||
$out = curl_exec($ch);
|
||||
if (curl_errno($ch) == 23) {
|
||||
// error 23 detected, try CURLOPT_ENCODING = "deflate"
|
||||
curl_setopt($ch, CURLOPT_ENCODING, "deflate");
|
||||
$out = curl_exec($ch);
|
||||
}
|
||||
if (isset($getinfo)) {
|
||||
$getinfo = curl_getinfo($ch);
|
||||
}
|
||||
if ($errno = curl_errno($ch)) {
|
||||
$msg = "Curl error $errno: " . (curl_error($ch) ?: curl_strerror($errno)) . ". Requested url: '$url'";
|
||||
if(isset($getinfo)) {
|
||||
$getinfo['error'] = $msg;
|
||||
}
|
||||
my_logger($msg, "http_get_contents");
|
||||
}
|
||||
curl_close($ch);
|
||||
return $out;
|
||||
}
|
||||
END_HEREDOC
|
||||
)
|
||||
fi
|
||||
if [[ -n "${ADDTEXT1}" || -n "${ADDTEXT2}" || -n "${ADDTEXT3}" ]]; then
|
||||
TMP="$FILE.$RANDOM"
|
||||
cp -f "$FILE" "$TMP"
|
||||
cp -f "$FILE" "$FILE-"
|
||||
# delete last line of the file if it contains `?>`
|
||||
if test $( tail -n 1 "${FILE}" ) = '?>' ; then
|
||||
sed -i '$ d' "${FILE}"
|
||||
if test $( tail -n 1 "${TMP}" ) = '?>' ; then
|
||||
sed -i '$ d' "${TMP}"
|
||||
fi
|
||||
echo "${TEXT}" >>"${FILE}"
|
||||
[[ -n "${ADDTEXT1}" ]] && echo "${ADDTEXT1}" >>"${TMP}"
|
||||
[[ -n "${ADDTEXT2}" ]] && echo "${ADDTEXT2}" >>"${TMP}"
|
||||
[[ -n "${ADDTEXT3}" ]] && echo "${ADDTEXT3}" >>"${TMP}"
|
||||
echo "?>" >>"${TMP}"
|
||||
mv "${TMP}" "${FILE}"
|
||||
fi
|
||||
|
||||
# install the main txz
|
||||
@@ -613,6 +725,54 @@ if [[ "${CHANGED}" == "yes" ]]; then
|
||||
fi
|
||||
fi
|
||||
|
||||
# Prevent web component file downgrade if the webgui version is newer than the plugin version
|
||||
# Function to extract "ts" value from JSON file
|
||||
extract_ts() {
|
||||
local filepath="$1"
|
||||
local ts_value=null
|
||||
ts_value=$(jq -r '.ts' "$filepath" 2>/dev/null)
|
||||
echo "$ts_value"
|
||||
}
|
||||
|
||||
preventDowngradeAction() {
|
||||
local action="$1"
|
||||
local path="$2"
|
||||
local preventType="$3" # preventDowngrade or skip
|
||||
|
||||
# if skip, do nothing
|
||||
if [[ "$preventType" == "skip" ]]; then
|
||||
return
|
||||
fi
|
||||
|
||||
# restore the "backup" but keep the original backup for the uninstall plg script
|
||||
# otherwise, the uninstall script will NOT be able to restore the original file
|
||||
if [[ "$action" == "move" || "$action" == "copy" ]]; then
|
||||
[[ -f "$path-" ]] && cp -f "$path-" "$path"
|
||||
elif [[ "$action" == "move_dir" ]]; then
|
||||
# if directory exists rm the original and copy the backup
|
||||
# glob expansion via "$path-/"* …yes the * is necessary as we want to copy the contents of the directory
|
||||
[[ -d "$path-" ]] && rm -rf "$path" && mkdir "$path" && cp -rf "$path-/"* "$path"
|
||||
fi
|
||||
}
|
||||
|
||||
# Extract "ts" values from both files
|
||||
plgWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components"
|
||||
backupWebComponentPath="/usr/local/emhttp/plugins/dynamix.my.servers/unraid-components-"
|
||||
|
||||
plgManifestTs=$(extract_ts "$plgWebComponentPath/manifest.json")
|
||||
webguiManifestTs=$(extract_ts "$backupWebComponentPath/manifest.json")
|
||||
|
||||
# Compare the "ts" values and return the file path of the higher value
|
||||
if [[ "$webguiManifestTs" -gt "$plgManifestTs" ]]; then
|
||||
# Loop through the array of preserveFilesDirs and perform actions
|
||||
for obj in "${preserveFilesDirs[@]}"
|
||||
do
|
||||
IFS=':' read -r action path preventType <<< "$obj"
|
||||
preventDowngradeAction "$action" "$path" "$preventType"
|
||||
done
|
||||
echo "♻️ Reverted to stock web component files"
|
||||
fi
|
||||
|
||||
# start background process to install/start the api and flash backup
|
||||
echo
|
||||
if [ -f /var/local/emhttp/var.ini ]; then
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Arguments
|
||||
# $1: SSH server name
|
||||
# $2: {--wc-deploy|--wc-build|--wc-skip} / deploy or build web components w/o prompt
|
||||
|
||||
# Path to store the last used server name
|
||||
state_file="$HOME/.deploy_state"
|
||||
|
||||
@@ -38,6 +42,30 @@ echo "$rsync_command"
|
||||
eval "$rsync_command"
|
||||
exit_code=$?
|
||||
|
||||
# if $2 is --wc-deploy, deploy the web components without prompting
|
||||
if [ "$2" = "--wc-deploy" ]; then
|
||||
deploy="yes"
|
||||
elif [ "$2" = "--wc-build" ]; then
|
||||
deploy="build"
|
||||
elif [ "$2" = "--wc-skip" ]; then
|
||||
deploy="no"
|
||||
fi
|
||||
|
||||
# if not deploy yes then ask
|
||||
if [ -z "$deploy" ]; then
|
||||
echo
|
||||
echo
|
||||
read -rp "Do you want to also deploy the built web components? (yes/no/build): " deploy
|
||||
fi
|
||||
|
||||
if [ "$deploy" = "yes" ]; then
|
||||
cd web || exit
|
||||
npm run deploy-wc:dev
|
||||
elif [ "$deploy" = "build" ]; then
|
||||
cd web || exit
|
||||
npm run build:dev
|
||||
fi
|
||||
|
||||
# Play built-in sound based on the operating system
|
||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
||||
# macOS
|
||||
|
||||
217
plugin/source/dynamix.unraid.net/etc/rc.d/rc.flash_backup
Executable file
217
plugin/source/dynamix.unraid.net/etc/rc.d/rc.flash_backup
Executable file
@@ -0,0 +1,217 @@
|
||||
#!/bin/bash
|
||||
# This file is /etc/rc.d/rc.flash_backup
|
||||
# use at queue "f" for flash backup
|
||||
QUEUE=" -q f "
|
||||
TASKNAME="/etc/rc.d/rc.flash_backup watch"
|
||||
TASKACTION="/usr/local/emhttp/plugins/dynamix.my.servers/scripts/UpdateFlashBackup update"
|
||||
last=$(date +%s)
|
||||
# set GIT_OPTIONAL_LOCKS=0 globally to reduce/eliminate writes to /boot
|
||||
export GIT_OPTIONAL_LOCKS=0
|
||||
|
||||
FAST=1 # 1 second delay when waiting for git
|
||||
SLOW=10 # 10 second delay when waiting for git
|
||||
# wait for existing git commands to complete
|
||||
# $1 is the time in seconds to sleep when waiting. SLOW or FAST
|
||||
_waitforgit() {
|
||||
while [[ $(pgrep -f '^git -C /boot' -c) -ne 0 ]]; do
|
||||
sleep "$1"
|
||||
done
|
||||
}
|
||||
# log to syslog, then wait for existing git commands to complete
|
||||
# $1 is the time in seconds to sleep when waiting. SLOW or FAST
|
||||
_waitforgitlog() {
|
||||
if [[ $(pgrep -f '^git -C /boot' -c) -ne 0 ]]; then
|
||||
logger "waiting for current backup to complete" --tag flash_backup
|
||||
_waitforgit "$1"
|
||||
fi
|
||||
}
|
||||
status() {
|
||||
_connected && CONNECTED="system is connected to Unraid Connect Cloud." || CONNECTED="system is not connected to Unraid Connect Cloud."
|
||||
if _watching; then
|
||||
echo "flash backup monitor is running. ${CONNECTED}"
|
||||
_hasqueue && echo "changes detected, backup queued."
|
||||
exit 0
|
||||
else
|
||||
if _enabled; then
|
||||
echo "flash backup is enabled but the monitor is not running. ${CONNECTED}"
|
||||
else
|
||||
echo "flash backup is disabled so the monitor is disabled. ${CONNECTED}"
|
||||
fi
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
start() {
|
||||
_start
|
||||
exit 0
|
||||
}
|
||||
stop() {
|
||||
_stop
|
||||
exit 0
|
||||
}
|
||||
reload() {
|
||||
_start
|
||||
sleep 1
|
||||
status
|
||||
}
|
||||
_start() {
|
||||
# Note: can start if not signed in, but watcher loop will not process until signed in
|
||||
# only run if flash_backup is enabled
|
||||
if ! _enabled; then
|
||||
logger "flash backup disabled, exiting" --tag flash_backup
|
||||
exit 1
|
||||
fi
|
||||
_stop
|
||||
# start watcher loop as background process
|
||||
exec ${TASKNAME} &>/dev/null &
|
||||
}
|
||||
_stop() {
|
||||
if _watching; then
|
||||
logger "stop watching for file changes" --tag flash_backup
|
||||
# terminate watcher loop/process
|
||||
pkill --full "${TASKNAME}" &>/dev/null
|
||||
fi
|
||||
# do not flush. better to have unsaved changes than to corrupt the backup during shutdown
|
||||
# note that an existing git process could still be running
|
||||
}
|
||||
flush() {
|
||||
# remove any queued jobs
|
||||
_removequeue
|
||||
# wait for existing git commands to finish before flushing
|
||||
_waitforgitlog "${FAST}"
|
||||
logger "flush: ${TASKACTION}" --tag flash_backup
|
||||
# if _connected, push any changes ad-hoc
|
||||
if _connected; then
|
||||
# shellcheck disable=SC2086
|
||||
echo "${TASKACTION}_nolimit &>/dev/null" | at ${QUEUE} -M now &>/dev/null
|
||||
fi
|
||||
}
|
||||
_watching() {
|
||||
local flash_backup_pid
|
||||
flash_backup_pid=$(pgrep --full "${TASKNAME}")
|
||||
if [[ ${flash_backup_pid} ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
_watch() {
|
||||
# safely clean up git *.lock files
|
||||
_clearlocks
|
||||
# flush: this will ensure we start with a clean repo
|
||||
flush
|
||||
# wait for flush to complete
|
||||
sleep 3
|
||||
_waitforgitlog "${FAST}"
|
||||
logger "start watching for file changes" --tag flash_backup
|
||||
# start watcher loop
|
||||
while true; do
|
||||
# if system is connected to Unraid Connect Cloud, see if there are updates to process
|
||||
_connected && _f1
|
||||
sleep 60
|
||||
done
|
||||
}
|
||||
_f1() {
|
||||
# wait for existing git commands to finish before checking for updates
|
||||
_waitforgit "${SLOW}"
|
||||
if [ "$(git -C /boot status -s)" ]; then
|
||||
_hasqueue || _f2
|
||||
elif _haserror && _beenawhile; then
|
||||
# we are in an error state and it has been 3 hours since we last tried submitting. run the task now.
|
||||
_runtaskaction
|
||||
fi
|
||||
}
|
||||
_f2() {
|
||||
if ! _haserror || [[ $(($(date +"%M") % 10)) -eq 0 ]]; then
|
||||
logger "adding task: ${TASKACTION}" --tag flash_backup
|
||||
fi
|
||||
sed -i "s@uptodate=yes@uptodate=no@" /var/local/emhttp/flashbackup.ini &>/dev/null
|
||||
_runtaskaction
|
||||
}
|
||||
_hasqueue() {
|
||||
# returns false if the queue is empty, true otherwise
|
||||
# shellcheck disable=SC2086
|
||||
if [ -z "$(atq ${QUEUE})" ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
_removequeue() {
|
||||
# delete any at jobs in queue f
|
||||
# @TODO shellcheck SC2162
|
||||
# shellcheck disable=SC2086
|
||||
atq ${QUEUE} | while read line; do
|
||||
id=$(echo ${line} | cut -d " " -f 1)
|
||||
atrm ${id}
|
||||
done
|
||||
}
|
||||
_runtaskaction() {
|
||||
# shellcheck disable=SC2086
|
||||
echo "${TASKACTION} &>/dev/null" | at ${QUEUE} -M now +1 minute &>/dev/null
|
||||
last=$(date +%s)
|
||||
}
|
||||
_enabled() {
|
||||
local output
|
||||
output=$(git -C /boot config --get remote.origin.url 2>&1)
|
||||
if [[ "${output}" == *"backup.unraid.net"* ]]; then
|
||||
return 0
|
||||
fi
|
||||
return 1
|
||||
}
|
||||
_connected() {
|
||||
CFG=/var/local/emhttp/myservers.cfg
|
||||
[[ ! -f "${CFG}" ]] && return 1
|
||||
# shellcheck disable=SC1090
|
||||
source <(sed -nr '/\[remote\]/,/\[/{/username/p}' "${CFG}" 2>/dev/null)
|
||||
# ensure signed in
|
||||
if [ -z "${username}" ]; then
|
||||
return 1
|
||||
fi
|
||||
# shellcheck disable=SC1090
|
||||
source <(sed -nr '/\[connectionStatus\]/,/\[/{/minigraph/p}' "${CFG}" 2>/dev/null)
|
||||
# ensure connected
|
||||
if [[ -z "${minigraph}" || "${minigraph}" != "CONNECTED" ]]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
_haserror() {
|
||||
errorstring=$(awk -F "=" '/error/ {print $2}' /var/local/emhttp/flashbackup.ini 2>&1 || echo '')
|
||||
if [ ${#errorstring} -le 2 ]; then
|
||||
return 1
|
||||
fi
|
||||
return 0
|
||||
}
|
||||
_beenawhile() {
|
||||
now=$(date +%s)
|
||||
age=$((now - last))
|
||||
maxage=$((3 * 60 * 60)) # three hours
|
||||
[[ $age -gt $maxage ]] && return 0
|
||||
return 1
|
||||
}
|
||||
# wait for git commands to end, then delete any stale lock files
|
||||
_clearlocks() {
|
||||
_waitforgitlog "${FAST}"
|
||||
find /boot/.git -type f -name '*.lock' -delete
|
||||
}
|
||||
case "$1" in
|
||||
'status')
|
||||
status
|
||||
;;
|
||||
'start')
|
||||
start
|
||||
;;
|
||||
'stop')
|
||||
stop
|
||||
;;
|
||||
'reload')
|
||||
reload
|
||||
;;
|
||||
'flush')
|
||||
flush
|
||||
;;
|
||||
'watch')
|
||||
_watch
|
||||
;;
|
||||
*)
|
||||
echo "usage $0 status|start|stop|reload|flush"
|
||||
;;
|
||||
esac
|
||||
182
plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api
Executable file
182
plugin/source/dynamix.unraid.net/etc/rc.d/rc.unraid-api
Executable file
@@ -0,0 +1,182 @@
|
||||
#!/bin/bash
|
||||
# unraid-api-handler
|
||||
flash="/boot/config/plugins/dynamix.my.servers"
|
||||
[[ ! -d "${flash}" ]] && echo "Please reinstall the Unraid Connect plugin" && exit 1
|
||||
[[ ! -f "${flash}/env" ]] && echo 'env=production' >"${flash}/env"
|
||||
# define env to avoid shellcheck SC2154. Will be overridden by the source command below
|
||||
env=production
|
||||
# shellcheck disable=SC1091
|
||||
source "${flash}/env"
|
||||
api_base_directory="/usr/local/bin"
|
||||
|
||||
# Only allow specific envs
|
||||
if [ "${env}" != "staging" ] && [ "${env}" != "production" ]; then
|
||||
echo "\"${env}\" is an unsupported env. Please use \"staging\" or \"production\"."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
switchenv() {
|
||||
stop
|
||||
# Get current environment from file
|
||||
local envFile="${flash}/env"
|
||||
local currentEnv
|
||||
currentEnv=$(
|
||||
# shellcheck disable=SC1090
|
||||
source "${envFile}"
|
||||
echo "${env}"
|
||||
)
|
||||
|
||||
if [[ "${currentEnv}" = "production" ]]; then
|
||||
echo "Switching from production to staging"
|
||||
echo 'env="staging"' >"${envFile}"
|
||||
cp "${api_base_directory}/unraid-api/.env.staging" "${api_base_directory}/unraid-api/.env"
|
||||
elif [[ "${currentEnv}" = "staging" ]]; then
|
||||
echo "Switching from staging to production"
|
||||
echo 'env="production"' >"${envFile}"
|
||||
cp "${api_base_directory}/unraid-api/.env.production" "${api_base_directory}/unraid-api/.env"
|
||||
fi
|
||||
echo "Run \"unraid-api start\" to start the API."
|
||||
}
|
||||
raiseloglevel() {
|
||||
kill -s SIGUSR2 "$(pidof unraid-api)"
|
||||
}
|
||||
lowerloglevel() {
|
||||
kill -s SIGUSR1 "$(pidof unraid-api)"
|
||||
}
|
||||
status() {
|
||||
LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" status
|
||||
}
|
||||
start() {
|
||||
LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" start 2>&1 | logger &
|
||||
}
|
||||
report() {
|
||||
LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" report "$1" "$2"
|
||||
}
|
||||
startdebug() {
|
||||
LOG_CONTEXT=true LOG_STACKTRACE=true LOG_TRACING=true LOG_LEVEL=debug "${api_base_directory}/unraid-api/unraid-api" start --debug
|
||||
}
|
||||
stop() {
|
||||
LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" stop 2>/dev/null
|
||||
}
|
||||
reload() {
|
||||
LOG_TYPE=raw "${api_base_directory}/unraid-api/unraid-api" restart
|
||||
}
|
||||
_install() {
|
||||
# process file from commandline
|
||||
if [[ -n "$1" ]]; then
|
||||
file=$(realpath "${flash}/$1")
|
||||
if [[ "${file}" == "${flash}"* ]] && [[ "${file}" == *".tgz" || "${file}" == *".zip" ]] && [[ -f "${file}" ]]; then
|
||||
[[ "${file}" == *".tgz" ]] && ext=tgz || ext=zip
|
||||
echo "installing $1"
|
||||
cp "${file}" "${flash}/unraid-api.${ext}"
|
||||
else
|
||||
echo "invalid installation file: $1"
|
||||
exit 1
|
||||
fi
|
||||
fi
|
||||
|
||||
# If this was downloaded from a Github action it'll be a zip with a tgz inside
|
||||
# Let's extract the tgz and rename it for the next step
|
||||
if [[ -f "${flash}/unraid-api.zip" ]]; then
|
||||
for f in ${flash}/unraid-api.zip; do unzip -p "${f}" >"${flash}/${f%.zip}.tgz"; done
|
||||
rm -f "${flash}/unraid-api.zip"
|
||||
fi
|
||||
|
||||
# Ensure installation tgz exists
|
||||
[[ ! -f "${flash}/unraid-api.tgz" ]] && echo "Please reinstall the Unraid Connect plugin" && exit 1
|
||||
|
||||
# Stop old process
|
||||
[[ -f "${api_base_directory}/unraid-api/unraid-api" ]] && stop
|
||||
|
||||
# Install unraid-api
|
||||
rm -rf "${api_base_directory}/unraid-api"
|
||||
mkdir -p "${api_base_directory}/unraid-api"
|
||||
tar -C "${api_base_directory}/unraid-api" -xzf "${flash}/unraid-api.tgz" --strip 1
|
||||
|
||||
# Reset permissions
|
||||
rm -f "${flash}/data/permissions.json"
|
||||
|
||||
# Copy env file
|
||||
cp "${api_base_directory}/unraid-api/.env.${env}" "${api_base_directory}/unraid-api/.env"
|
||||
|
||||
# Copy wc files from flash
|
||||
if [ -f "${flash}/webComps/unraid.min.js" ]; then
|
||||
rm -rf /usr/local/emhttp/webGui/webComps
|
||||
mkdir -p /usr/local/emhttp/webGui/webComps
|
||||
cp ${flash}/webComps/* /usr/local/emhttp/webGui/webComps
|
||||
else
|
||||
# not fatal, previous version of unraid.min.js should still exist in /usr/local/emhttp/webGui/webComps
|
||||
echo "Note: ${flash}/webComps/unraid.min.js is missing"
|
||||
fi
|
||||
|
||||
# bail if expected file does not exist
|
||||
[[ ! -f "${api_base_directory}/unraid-api/unraid-api" ]] && echo "unraid-api install failed" && exit 1
|
||||
}
|
||||
install() {
|
||||
# Install the files
|
||||
_install "$1"
|
||||
|
||||
# if nginx is running, start the api. if not, it will be started by rc.nginx
|
||||
if /etc/rc.d/rc.nginx status &>/dev/null; then
|
||||
# Start new process
|
||||
start
|
||||
# Note: do not run another unraid-api command until you see "UNRAID API started successfully!" in syslog
|
||||
sleep 3
|
||||
echo "unraid-api installed and started"
|
||||
else
|
||||
echo "unraid-api installed"
|
||||
fi
|
||||
exit 0
|
||||
}
|
||||
uninstall() {
|
||||
# Stop old process
|
||||
[[ -f "${api_base_directory}/unraid-api/unraid-api" ]] && stop
|
||||
|
||||
# Remove all unraid-api files
|
||||
rm -rf "${api_base_directory}/unraid-api"
|
||||
rm -f /var/run/unraid-api.sock
|
||||
}
|
||||
case "$1" in
|
||||
'status')
|
||||
status
|
||||
;;
|
||||
'start')
|
||||
start
|
||||
;;
|
||||
'report')
|
||||
report "$2" "$3"
|
||||
;;
|
||||
'switch-env')
|
||||
switchenv
|
||||
;;
|
||||
'start-debug')
|
||||
startdebug
|
||||
;;
|
||||
'raise-log-level')
|
||||
raiseloglevel
|
||||
;;
|
||||
'lower-log-level')
|
||||
lowerloglevel
|
||||
;;
|
||||
'stop')
|
||||
stop
|
||||
;;
|
||||
'reload')
|
||||
reload
|
||||
;;
|
||||
'restart')
|
||||
reload
|
||||
;;
|
||||
'install')
|
||||
install "$2"
|
||||
;;
|
||||
'_install')
|
||||
_install "$2"
|
||||
;;
|
||||
'uninstall')
|
||||
uninstall
|
||||
;;
|
||||
*)
|
||||
echo "usage $0 status|start|report|switch-env|start-debug|raise-log-level|lower-log-level|stop|reload|install|uninstall"
|
||||
;;
|
||||
esac
|
||||
@@ -14,6 +14,7 @@ Tag="globe"
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/state.php";
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
$serverState = new ServerState();
|
||||
|
||||
$keyfile = $serverState->keyfileBase64;
|
||||
@@ -140,7 +141,7 @@ function registerServer(button) {
|
||||
button.form.submit();
|
||||
});
|
||||
<?else:?>
|
||||
// give the unraid-api time to call rc.nginx and UpdateDNS before refreshing the page
|
||||
// give the unraid-api time to call rc.nginx before refreshing the page
|
||||
const delay = 4000;
|
||||
setTimeout(function() {
|
||||
button.form.submit();
|
||||
@@ -256,10 +257,10 @@ function changeRemoteAccess(dropdown) {
|
||||
$useConnectMsgTxt = '';
|
||||
break;
|
||||
case 'DYNAMIC_MANUAL':
|
||||
$remoteAccessMsgTxt = "<a href='https://docs.unraid.net/connect/remote-access' target='_blank'>Enable Remote Access</a> on the <a href='https://connect.myunraid.net/' target='_blank'>Connect Dashboard</a>.";
|
||||
$remoteAccessMsgTxt = "<a href='https://docs.unraid.net/go/connect-remote-access/' target='_blank'>Enable Remote Access</a> on the <a href='https://connect.myunraid.net/' target='_blank'>Connect Dashboard</a>.";
|
||||
break;
|
||||
case 'DYNAMIC_UPNP':
|
||||
$remoteAccessMsgTxt = "<a href='https://docs.unraid.net/connect/remote-access' target='_blank'>Enable Remote Access</a> on the <a href='https://connect.myunraid.net/' target='_blank'>Connect Dashboard</a>, a random WAN port will be assigned by UPnP.";
|
||||
$remoteAccessMsgTxt = "<a href='https://docs.unraid.net/go/connect-remote-access/' target='_blank'>Enable Remote Access</a> on the <a href='https://connect.myunraid.net/' target='_blank'>Connect Dashboard</a>, a random WAN port will be assigned by UPnP.";
|
||||
break;
|
||||
case 'ALWAYS_MANUAL':
|
||||
$remoteAccessMsgTxt = "Remote Access is always on.";
|
||||
@@ -521,7 +522,7 @@ _(Allow Remote Access)_:
|
||||
<?if(!$isRegistered): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until you have signed in)_</span></dd></dl>
|
||||
<?elseif(!$isMiniGraphConnected && $myServersFlashCfg['remote']['wanaccess']!="yes"): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud)_</span></dd></dl>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud - try reloading the page)_</span></dd></dl>
|
||||
<?elseif(!$hasMyUnraidNetCert): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until you Provision a myunraid.net SSL Cert)_</span><input type="hidden" id="wanport" value="0"></dd></dl>
|
||||
<?elseif(!$boolWebUIAuth): // NOTE: manually added close tags so the next section would not be indented ?>
|
||||
@@ -546,7 +547,7 @@ _(Allow Remote Access)_:
|
||||
<?endif?>
|
||||
|
||||
|
||||
: <unraid-i18n-host><unraid-wan-ip-check php-wan-ip="<?=@file_get_contents('https://wanip4.unraid.net/')?>"></unraid-wan-ip-check></unraid-i18n-host>
|
||||
: <unraid-i18n-host><unraid-wan-ip-check php-wan-ip="<?=http_get_contents('https://wanip4.unraid.net/')?>"></unraid-wan-ip-check></unraid-i18n-host>
|
||||
|
||||
<div markdown="1" id="wanpanel" style="display:'none'">
|
||||
|
||||
@@ -626,8 +627,8 @@ _(Enable Transparent 2FA for Local Access)_<!-- do not index -->:
|
||||
_(Flash backup)_:
|
||||
<?if(!$isRegistered):?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until you have signed in)_</span>
|
||||
<?elseif(!$isMiniGraphConnected && empty($flashbackup_status['activated'])):?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud)_</span>
|
||||
<?elseif(!$isMiniGraphConnected):?>
|
||||
: <span><i class="fa fa-warning icon warning"></i> _(Disabled until connected to Unraid Connect Cloud - try reloading the page)_</span>
|
||||
<?else: // begin show flash backup form ?>
|
||||
: <span id='flashbackuptext'><span class='blue p0'>_(Loading)_ <i class="fa fa-spinner fa-spin" aria-hidden="true"></i></span></span>
|
||||
|
||||
@@ -646,7 +647,7 @@ _(Flash backup)_:
|
||||
<div markdown="1" id="inactivespanel" style="display:none">
|
||||
|
||||
<?if(disk_free_space('/boot') > 1024*1000*1000):?>
|
||||
: <button type="button" onclick="enableFlashBackup(this)">_(Activate)_</button> <span>_(Please note that the flash backup is not encrypted at this time.)_ <a href="https://docs.unraid.net/connect/help#automated-flash-backup" target="_blank">_(More information.)_</a></span>
|
||||
: <button type="button" onclick="enableFlashBackup(this)">_(Activate)_</button> <span>_(Please note that the flash backup is not encrypted at this time.)_ <a href="https://docs.unraid.net/go/connect-flash-backup/" target="_blank">_(More information.)_</a></span>
|
||||
<?else:?>
|
||||
: <button type="button" disabled>_(Activate)_</button> <span><i class="fa fa-warning icon warning"></i> _(In order to activate Flash Backup there must be at least 1GB of free space on your flash drive.)_</span>
|
||||
<?endif?>
|
||||
@@ -673,7 +674,7 @@ _(Flash backup)_:
|
||||
</div>
|
||||
<div markdown="1" id="activepanel" style="display:none">
|
||||
|
||||
: <button type="button" onclick="enableFlashBackup(this)">_(Deactivate)_</button> <span>_(Please note that the flash backup is not encrypted at this time.)_ <a href="https://docs.unraid.net/connect/help#automated-flash-backup" target="_blank">_(More information.)_</a></span>
|
||||
: <button type="button" onclick="enableFlashBackup(this)">_(Deactivate)_</button> <span>_(Please note that the flash backup is not encrypted at this time.)_ <a href="https://docs.unraid.net/go/connect-flash-backup/" target="_blank">_(More information.)_</a></span>
|
||||
|
||||
<?if (in_array($_COOKIE['UPC_ENV']??'', ['development','staging']) && file_exists("/var/log/gitflash") && filesize("/var/log/gitflash")):?>
|
||||
|
||||
|
||||
@@ -70,8 +70,8 @@ function save_flash_backup_state($loading='') {
|
||||
rename($flashbackup_tmp, $flashbackup_ini);
|
||||
}
|
||||
|
||||
function load_flash_backup_state() {
|
||||
global $arrState,$flashbackup_ini,$isRegistered;
|
||||
function default_flash_backup_state() {
|
||||
global $arrState;
|
||||
|
||||
$arrState = [
|
||||
'activated' => 'no',
|
||||
@@ -80,6 +80,12 @@ function load_flash_backup_state() {
|
||||
'error' => '',
|
||||
'remoteerror' => ''
|
||||
];
|
||||
}
|
||||
|
||||
function load_flash_backup_state() {
|
||||
global $arrState,$flashbackup_ini,$isRegistered;
|
||||
|
||||
default_flash_backup_state();
|
||||
|
||||
$arrNewState = (file_exists($flashbackup_ini)) ? @parse_ini_file($flashbackup_ini) : [];
|
||||
if ($arrNewState) {
|
||||
@@ -120,7 +126,7 @@ function set_git_config($name, $value) {
|
||||
|
||||
function readFromFile($file): string {
|
||||
$text = "";
|
||||
if (file_exists($file)) {
|
||||
if (file_exists($file) && filesize($file) > 0) {
|
||||
$fp = fopen($file,"r");
|
||||
if (flock($fp, LOCK_EX)) {
|
||||
$text = fread($fp, filesize($file));
|
||||
@@ -193,6 +199,7 @@ function deleteLocalRepo() {
|
||||
if (is_dir($mainGitDir)) {
|
||||
rename($mainGitDir, $tmpGitDir);
|
||||
exec('echo "rm -rf '.$tmpGitDir.' &>/dev/null" | at -q f -M now &>/dev/null');
|
||||
write_log("local repo deleted");
|
||||
}
|
||||
|
||||
// reset state
|
||||
@@ -201,6 +208,7 @@ function deleteLocalRepo() {
|
||||
$arrState['loading'] = '';
|
||||
$arrState['error'] = '';
|
||||
$arrState['remoteerror'] = '';
|
||||
save_flash_backup_state();
|
||||
}
|
||||
|
||||
$validCommands = [
|
||||
@@ -277,7 +285,14 @@ if ($pgrep_output[0] != "0") {
|
||||
|
||||
// check if signed-in
|
||||
if (!$isRegistered) {
|
||||
response_complete(406, array('error' => 'Must be signed in to My Servers to use Flash Backup'));
|
||||
default_flash_backup_state();
|
||||
response_complete(406, array('error' => 'Must be signed in to Unraid Connect to use Flash Backup'));
|
||||
}
|
||||
|
||||
// check if connected to Unraid Connect Cloud
|
||||
if (!$isConnected) {
|
||||
default_flash_backup_state();
|
||||
response_complete(406, array('error' => 'Must be connected to Unraid Connect Cloud to use Flash Backup'));
|
||||
}
|
||||
|
||||
// keyfile
|
||||
@@ -314,12 +329,46 @@ if (!empty($loadingMessage)) {
|
||||
}
|
||||
|
||||
if ($command == 'deactivate') {
|
||||
exec_log('git -C /boot remote remove origin');
|
||||
exec('/etc/rc.d/rc.flash_backup stop &>/dev/null');
|
||||
deleteLocalRepo();
|
||||
response_complete(200, '{}');
|
||||
}
|
||||
|
||||
// determine size of local repo
|
||||
$maxRepoSize = 100 * 1000; // 100 MB, for comparison without output of 'du -s'
|
||||
$repoDelFlag = '/boot/config/plugins/dynamix.my.servers/repodeleted';
|
||||
$output = [];
|
||||
if (file_exists('/boot/.git')) exec('du -s /boot/.git/ | cut -f 1', $output);
|
||||
$repoSize = ($output && $output[0]) ? intval($output[0]) : 0;
|
||||
if ($repoSize > $maxRepoSize) {
|
||||
// the local repo is too large
|
||||
$okToDelRepo = true;
|
||||
if (file_exists($repoDelFlag)) {
|
||||
// the local repo is too large, but we have already auto-deleted it in the past. determine how long ago this happened
|
||||
$repoDelTime = intval(@trim(@file_get_contents($repoDelFlag))); // epoch
|
||||
$repoAge = round((time()-$repoDelTime)/(60*60*24)); // days
|
||||
$repoMaxAge = 90; // days
|
||||
if ($repoAge < $repoMaxAge) {
|
||||
// the local repo was deleted and recreated less than repoMaxAge days ago, do not delete
|
||||
write_log("local repo is too large ($repoSize > $maxRepoSize) but was auto-deleted recently ($repoAge < $repoMaxAge)");
|
||||
$okToDelRepo = false;
|
||||
}
|
||||
}
|
||||
if ($okToDelRepo) {
|
||||
// the local repo is too large, delete and reactivate it
|
||||
write_log("local repo is too large ($repoSize > $maxRepoSize), about to delete and reactivate");
|
||||
file_put_contents($repoDelFlag, time());
|
||||
exec('/etc/rc.d/rc.flash_backup stop &>/dev/null');
|
||||
deleteLocalRepo();
|
||||
// change command to 'activate' and continue script
|
||||
$command = 'activate';
|
||||
$loadingMessage = 'Activating';
|
||||
save_flash_backup_state($loadingMessage);
|
||||
}
|
||||
} else {
|
||||
write_log("local repo size is acceptable ($repoSize < $maxRepoSize)");
|
||||
}
|
||||
|
||||
// build a list of sha256 hashes of the bzfiles
|
||||
$bzfilehashes = [];
|
||||
$allbzfiles = ['bzimage','bzfirmware','bzmodules','bzroot','bzroot-gui'];
|
||||
@@ -422,29 +471,29 @@ if (!file_exists('/boot/.git/info/exclude')) {
|
||||
}
|
||||
|
||||
// setup a nice git description
|
||||
$gitdesc_text='Unraid flash drive for '.$var['NAME']."\n";
|
||||
$gitdesc_file='/boot/.git/description';
|
||||
if (!file_exists($gitdesc_file) || strpos(file_get_contents($gitdesc_file),$var['NAME']) === false) {
|
||||
file_put_contents($gitdesc_file, 'Unraid flash drive for '.$var['NAME']."\n");
|
||||
if (!file_exists($gitdesc_file) || (file_get_contents($gitdesc_file) != $gitdesc_text)) {
|
||||
file_put_contents($gitdesc_file, $gitdesc_text);
|
||||
}
|
||||
|
||||
// configure git to use the noprivatekeys filter
|
||||
set_git_config('filter.noprivatekeys.clean', '/usr/local/emhttp/plugins/dynamix.my.servers/scripts/git-noprivatekeys-clean');
|
||||
|
||||
// configure git to apply the noprivatekeys filter to wireguard config files
|
||||
$gitattributes_file='/boot/.gitattributes';
|
||||
if (!file_exists($gitattributes_file) || strpos(file_get_contents($gitattributes_file),'noprivatekeys') === false) {
|
||||
file_put_contents($gitattributes_file, '# file managed by Unraid, do not modify
|
||||
$gitattributes_text='# file managed by Unraid, do not modify
|
||||
config/wireguard/*.cfg filter=noprivatekeys
|
||||
config/wireguard/*.conf filter=noprivatekeys
|
||||
config/wireguard/peers/*.conf filter=noprivatekeys
|
||||
');
|
||||
';
|
||||
$gitattributes_file='/boot/.gitattributes';
|
||||
if (!file_exists($gitattributes_file) || (file_get_contents($gitattributes_file) != $gitattributes_text)) {
|
||||
file_put_contents($gitattributes_file, $gitattributes_text);
|
||||
}
|
||||
|
||||
// setup git ignore for files we dont need in the flash backup
|
||||
$gitexclude_file='/boot/.git/info/exclude';
|
||||
if (!file_exists($gitexclude_file) || strpos(file_get_contents($gitexclude_file),'# version 1.0') === false) {
|
||||
file_put_contents($gitexclude_file, '# file managed by Unraid, do not modify
|
||||
# version 1.0
|
||||
// setup master git exclude file to specify what to include/exclude from repo
|
||||
$gitexclude_text = '# file managed by Unraid, do not modify
|
||||
# version 1.2
|
||||
|
||||
# Blacklist everything
|
||||
/*
|
||||
@@ -479,8 +528,20 @@ config/plugins/**/*.tar.bz2
|
||||
config/plugins-error
|
||||
config/plugins-old-versions
|
||||
config/plugins/dockerMan/images
|
||||
config/plugins/dynamix.file.integrity/logs
|
||||
config/wireguard/peers/*.png
|
||||
');
|
||||
';
|
||||
|
||||
// find large files to exclude from flash backup
|
||||
$oversize_files = $return_var = null;
|
||||
exec('find /boot/config -type f -size +10M 2>/dev/null | sed "s|^/boot/||g" 2>/dev/null', $oversize_files, $return_var);
|
||||
if ($oversize_files && is_array($oversize_files)) {
|
||||
$gitexclude_text .= "\n# Blacklist large files on this system\n".implode("\n", $oversize_files)."\n";
|
||||
}
|
||||
|
||||
$gitexclude_file='/boot/.git/info/exclude';
|
||||
if (!file_exists($gitexclude_file) || (file_get_contents($gitexclude_file) != $gitexclude_text)) {
|
||||
file_put_contents($gitexclude_file, $gitexclude_text);
|
||||
}
|
||||
|
||||
// ensure git user is configured
|
||||
@@ -529,7 +590,7 @@ if (empty($SSH_PORT)) {
|
||||
} else {
|
||||
$arrState['loading'] = '';
|
||||
if (stripos(implode($ssh_output),'permission denied') !== false) {
|
||||
$arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to My Servers Cloud';
|
||||
$arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to Unraid Connect Cloud';
|
||||
} else {
|
||||
$arrState['error'] = 'Unable to connect to backup.unraid.net:22';
|
||||
}
|
||||
@@ -557,7 +618,7 @@ if ($command == 'activate') {
|
||||
exec_log('git -C /boot checkout -B master origin/master');
|
||||
|
||||
// establish status
|
||||
exec_log('git -C /boot status --porcelain 2>&1', $status_output, $return_var);
|
||||
exec_log('git -C /boot status --porcelain', $status_output, $return_var);
|
||||
|
||||
if ($return_var != 0) {
|
||||
// detect git submodule
|
||||
@@ -600,7 +661,7 @@ if ($command == 'activate') {
|
||||
}
|
||||
|
||||
// detect corruption #1
|
||||
exec_log('git -C /boot show --summary 2>&1', $show_output, $return_var);
|
||||
exec_log('git -C /boot show --summary', $show_output, $return_var);
|
||||
if ($return_var != 0) {
|
||||
if (stripos(implode($show_output),'fatal: your current branch appears to be broken') !== false) {
|
||||
$arrState['error'] = 'Error: Backup corrupted';
|
||||
@@ -616,24 +677,31 @@ if ($command == 'activate') {
|
||||
} // end check for ($command == 'activate')
|
||||
|
||||
if ($command == 'update' || $command == 'activate') {
|
||||
|
||||
|
||||
// note: this section only runs if there are changes detected
|
||||
if ($arrState['uptodate'] == 'no') {
|
||||
// increment git commit counter
|
||||
appendToFile($commitCountFile, $time."\n");
|
||||
|
||||
// find files that are in repo but should not be, according to /boot/.git/info/exclude and various .gitignore files
|
||||
$invalid_files = $return_var = null;
|
||||
exec_log('git -C /boot ls-files --cached --ignored --exclude-standard', $invalid_files, $return_var);
|
||||
foreach ((array) $invalid_files as $invalid_file) {
|
||||
// remove each of these files from the repo
|
||||
// this prevents future changes from being tracked but does not remove the file from history.
|
||||
exec_log("git -C /boot rm --cached --ignore-unmatch '$invalid_file'");
|
||||
}
|
||||
|
||||
// add and commit all file changes
|
||||
exec_log('git -C /boot add -A');
|
||||
exec_log('git -C /boot commit -m ' . escapeshellarg($commitmsg));
|
||||
|
||||
// push changes upstream
|
||||
exec_log('git -C /boot push --set-upstream origin master', $push_output, $return_var);
|
||||
if ($return_var != 0) {
|
||||
exec_log('git -C /boot push --force --set-upstream origin master', $push_output, $return_var);
|
||||
}
|
||||
// push changes upstream
|
||||
exec_log('git -C /boot push --force --set-upstream origin master', $push_output, $return_var);
|
||||
if ($return_var != 0) {
|
||||
// check for permission denied
|
||||
if (stripos(implode($push_output),'permission denied') !== false) {
|
||||
$arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to My Servers Cloud';
|
||||
$arrState['error'] = ($isConnected) ? 'Permission Denied' : 'Permission Denied, ensure you are connected to Unraid Connect Cloud';
|
||||
} elseif (stripos(implode($push_output),'fatal: loose object') !== false && stripos(implode($push_output),'is corrupt') !== false) {
|
||||
// detect corruption #2
|
||||
$arrState['error'] = 'Error: Backup corrupted';
|
||||
|
||||
@@ -14,15 +14,25 @@
|
||||
* Usage:
|
||||
* ```
|
||||
* $rebootDetails = new RebootDetails();
|
||||
* $rebootType = $rebootDetails->getRebootType();
|
||||
* $rebootType = $rebootDetails->rebootType;
|
||||
* ```
|
||||
*/
|
||||
class RebootDetails
|
||||
{
|
||||
/**
|
||||
* @var string $rebootType Stores the type of reboot required, which can be 'update', 'downgrade', or 'thirdPartyDriversDownloading'.
|
||||
*/
|
||||
private $rebootType = '';
|
||||
const CURRENT_CHANGES_TXT_PATH = '/boot/changes.txt';
|
||||
const CURRENT_README_RELATIVE_PATH = 'plugins/unRAIDServer/README.md';
|
||||
const CURRENT_VERSION_PATH = '/etc/unraid-version';
|
||||
const PREVIOUS_BZ_ROOT_PATH = '/boot/previous/bzroot';
|
||||
const PREVIOUS_CHANGES_TXT_PATH = '/boot/previous/changes.txt';
|
||||
|
||||
private $currentVersion = '';
|
||||
|
||||
public $rebootType = ''; // 'update', 'downgrade', 'thirdPartyDriversDownloading'
|
||||
public $rebootReleaseDate = '';
|
||||
public $rebootVersion = '';
|
||||
|
||||
public $previousReleaseDate = '';
|
||||
public $previousVersion = '';
|
||||
|
||||
/**
|
||||
* Constructs a new RebootDetails object and automatically detects the reboot type during initialization.
|
||||
@@ -40,66 +50,119 @@ class RebootDetails
|
||||
{
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
|
||||
$rebootReadme = @file_get_contents("$docroot/plugins/unRAIDServer/README.md", false, null, 0, 20) ?: '';
|
||||
/**
|
||||
* Read the reboot readme, and see if it says "REBOOT REQUIRED" or "DOWNGRADE"
|
||||
* only relying on the README.md file to save reads from the flash drive.
|
||||
* because we started allowing downgrades from the account.unraid.net Update OS page, we can't
|
||||
* fully rely on the README.md value of being accurate.
|
||||
* For instance if on 6.13.0-beta.2.1 then chose to "Downgrade" to 6.13.0-beta.1.10 from the account app
|
||||
* the README.md file would still say "REBOOT REQUIRED".
|
||||
*/
|
||||
$rebootReadme = @file_get_contents("$docroot/" . self::CURRENT_README_RELATIVE_PATH, false, null, 0, 20) ?: '';
|
||||
$rebootDetected = preg_match("/^\*\*(REBOOT REQUIRED|DOWNGRADE)/", $rebootReadme);
|
||||
if (!$rebootDetected) {
|
||||
return;
|
||||
}
|
||||
/**
|
||||
* if a reboot is required, then:
|
||||
* get current Unraid version from /etc/unraid-version
|
||||
* then get the version of the last update from self::CURRENT_CHANGES_TXT_PATH
|
||||
* if they're different, then a reboot is required
|
||||
* if the version in self::CURRENT_CHANGES_TXT_PATH is less than the current version, then a downgrade is required
|
||||
* if the version in self::CURRENT_CHANGES_TXT_PATH is greater than the current version, then an update is required
|
||||
*/
|
||||
$this->setCurrentVersion();
|
||||
$this->setRebootDetails();
|
||||
if ($this->currentVersion == '' || $this->rebootVersion == '') {
|
||||
return; // return to prevent potential incorrect outcome
|
||||
}
|
||||
|
||||
$rebootForDowngrade = $rebootDetected && strpos($rebootReadme, 'DOWNGRADE') !== false;
|
||||
$rebootForUpdate = $rebootDetected && strpos($rebootReadme, 'REBOOT REQUIRED') !== false;
|
||||
|
||||
$this->rebootType = $rebootForDowngrade ? 'downgrade' : ($rebootForUpdate ? 'update' : '');
|
||||
$compareVersions = version_compare($this->rebootVersion, $this->currentVersion);
|
||||
switch ($compareVersions) {
|
||||
case -1:
|
||||
$this->setRebootType('downgrade');
|
||||
break;
|
||||
case 0:
|
||||
// we should never get here, but if we do, then no reboot is required and just return
|
||||
return;
|
||||
case 1:
|
||||
$this->setRebootType('update');
|
||||
break;
|
||||
}
|
||||
|
||||
// Detect if third-party drivers were part of the update process
|
||||
$processWaitingThirdPartyDrivers = "inotifywait -q /boot/changes.txt -e move_self,delete_self";
|
||||
$processWaitingThirdPartyDrivers = "inotifywait -q " . self::CURRENT_CHANGES_TXT_PATH . " -e move_self,delete_self";
|
||||
// Run the ps command to list processes and check if the process is running
|
||||
$ps_command = "ps aux | grep -E \"$processWaitingThirdPartyDrivers\" | grep -v \"grep -E\"";
|
||||
$output = shell_exec($ps_command) ?? '';
|
||||
if ($this->rebootType != '' && strpos($output, $processWaitingThirdPartyDrivers) !== false) {
|
||||
$this->rebootType = 'thirdPartyDriversDownloading';
|
||||
$this->setRebootType('thirdPartyDriversDownloading');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the type of reboot required, which can be 'update', 'downgrade', or 'thirdPartyDriversDownloading'.
|
||||
*
|
||||
* @return string The type of reboot required.
|
||||
*/
|
||||
public function getRebootType()
|
||||
{
|
||||
return $this->rebootType;
|
||||
}
|
||||
|
||||
/**
|
||||
* Detects and retrieves the version information related to the system reboot based on the contents of the '/boot/changes.txt' file.
|
||||
*
|
||||
* @return string The system version information or 'Not found' if not found, or 'File not found' if the file is not present.
|
||||
*/
|
||||
public function getRebootVersion()
|
||||
private function readChangesTxt(string $file_path = self::CURRENT_CHANGES_TXT_PATH)
|
||||
{
|
||||
$file_path = '/boot/changes.txt';
|
||||
|
||||
// Check if the file exists
|
||||
if (file_exists($file_path)) {
|
||||
// Open the file for reading
|
||||
$file = fopen($file_path, 'r');
|
||||
|
||||
// Read the file line by line until we find a line that starts with '# Version'
|
||||
while (($line = fgets($file)) !== false) {
|
||||
if (strpos($line, '# Version') === 0) {
|
||||
// Use a regular expression to extract the full version string
|
||||
if (preg_match('/# Version\s+(\S+)/', $line, $matches)) {
|
||||
$fullVersion = $matches[1];
|
||||
return $fullVersion;
|
||||
} else {
|
||||
return 'Not found';
|
||||
}
|
||||
exec("head -n4 $file_path", $rows);
|
||||
foreach ($rows as $row) {
|
||||
$i = stripos($row,'version');
|
||||
if ($i !== false) {
|
||||
[$version, $releaseDate] = explode(' ', trim(substr($row, $i+7)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Close the file
|
||||
fclose($file);
|
||||
return [
|
||||
'releaseDate' => $releaseDate ?? 'Not found',
|
||||
'version' => $version ?? 'Not found',
|
||||
];
|
||||
} else {
|
||||
return 'File not found';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the current version of the Unraid server for comparison with the reboot version.
|
||||
*/
|
||||
private function setCurrentVersion() {
|
||||
// output ex: version="6.13.0-beta.2.1"
|
||||
$raw = @file_get_contents(self::CURRENT_VERSION_PATH) ?: '';
|
||||
// Regular expression to match the version between the quotes
|
||||
$pattern = '/version="([^"]+)"/';
|
||||
if (preg_match($pattern, $raw, $matches)) {
|
||||
$this->currentVersion = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
private function setRebootDetails()
|
||||
{
|
||||
$rebootDetails = $this->readChangesTxt();
|
||||
$this->rebootReleaseDate = $rebootDetails['releaseDate'];
|
||||
$this->rebootVersion = $rebootDetails['version'];
|
||||
}
|
||||
|
||||
private function setRebootType($rebootType)
|
||||
{
|
||||
$this->rebootType = $rebootType;
|
||||
}
|
||||
|
||||
/**
|
||||
* If self::PREVIOUS_BZ_ROOT_PATH exists, then the user has the option to downgrade to the previous version.
|
||||
* Parse the text file /boot/previous/changes.txt to get the version number of the previous version.
|
||||
* Then we move some files around and reboot.
|
||||
*/
|
||||
public function setPrevious()
|
||||
{
|
||||
if (@file_exists(self::PREVIOUS_BZ_ROOT_PATH) && @file_exists(self::PREVIOUS_CHANGES_TXT_PATH)) {
|
||||
$parseOutput = $this->readChangesTxt(self::PREVIOUS_CHANGES_TXT_PATH);
|
||||
$this->previousVersion = $parseOutput['version'];
|
||||
$this->previousReleaseDate = $parseOutput['releaseDate'];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -47,6 +47,7 @@ class ServerState
|
||||
private $connectPluginVersion;
|
||||
private $configErrorEnum = [
|
||||
"error" => 'UNKNOWN_ERROR',
|
||||
"ineligible" => 'INELIGIBLE',
|
||||
"invalid" => 'INVALID',
|
||||
"nokeyserver" => 'NO_KEY_SERVER',
|
||||
"withdrawn" => 'WITHDRAWN',
|
||||
@@ -92,7 +93,7 @@ class ServerState
|
||||
$this->osVersionBranch = trim(@exec('plugin category /var/log/plugins/unRAIDServer.plg') ?? 'stable');
|
||||
|
||||
$caseModelFile = '/boot/config/plugins/dynamix/case-model.cfg';
|
||||
$this->caseModel = file_exists($caseModelFile) ? file_get_contents($caseModelFile) : '';
|
||||
$this->caseModel = file_exists($caseModelFile) ? htmlspecialchars(@file_get_contents($caseModelFile), ENT_HTML5, 'UTF-8') : '';
|
||||
|
||||
$this->rebootDetails = new RebootDetails();
|
||||
|
||||
@@ -235,13 +236,17 @@ class ServerState
|
||||
public function getServerState()
|
||||
{
|
||||
$serverState = [
|
||||
"array" => [
|
||||
"state" => @$this->getWebguiGlobal('var', 'fsState'),
|
||||
"progress" => @$this->getWebguiGlobal('var', 'fsProgress'),
|
||||
],
|
||||
"apiKey" => $this->apiKey,
|
||||
"apiVersion" => $this->apiVersion,
|
||||
"avatar" => $this->avatar,
|
||||
"caseModel" => $this->caseModel,
|
||||
"config" => [
|
||||
'valid' => ($this->var['configValid'] === 'yes'),
|
||||
'error' => isset($this->configErrorEnum[$this->var['configValid']]) ? $this->configErrorEnum[$this->var['configValid']] : 'UNKNOWN_ERROR',
|
||||
'error' => isset($this->configErrorEnum[$this->var['configValid']]) ? $this->configErrorEnum[$this->var['configValid']] : null,
|
||||
],
|
||||
"connectPluginInstalled" => $this->connectPluginInstalled,
|
||||
"connectPluginVersion" => $this->connectPluginVersion,
|
||||
@@ -269,8 +274,9 @@ class ServerState
|
||||
"osVersion" => $this->osVersion,
|
||||
"osVersionBranch" => $this->osVersionBranch,
|
||||
"protocol" => _var($_SERVER, 'REQUEST_SCHEME'),
|
||||
"rebootType" => $this->rebootDetails->getRebootType(),
|
||||
"regDev" => @(int)$this->var['regDev'] ?? 0,
|
||||
"rebootType" => $this->rebootDetails->rebootType,
|
||||
"rebootVersion" => $this->rebootDetails->rebootVersion,
|
||||
"regDevs" => @(int)$this->var['regDevs'] ?? 0,
|
||||
"regGen" => @(int)$this->var['regGen'],
|
||||
"regGuid" => @$this->var['regGUID'] ?? '',
|
||||
"regTo" => @htmlspecialchars($this->var['regTo'], ENT_HTML5, 'UTF-8') ?? '',
|
||||
|
||||
@@ -72,13 +72,13 @@ class WebComponentTranslations
|
||||
'<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>' => '<p>' . _('To continue using Unraid OS you may purchase a license key.') . ' ' . _('Alternately, you may request a Trial extension.') . '</p>',
|
||||
'<p>To support more storage devices as your server grows, click Upgrade Key.</p>' => '<p>' . _('To support more storage devices as your server grows, click Upgrade Key.') . '</p>',
|
||||
'<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>' => '<p>' . _('You have used all your Trial extensions.') . ' ' . _('To continue using Unraid OS you may purchase a license key.') . '</p>',
|
||||
'<p>Your <em>Trial</em> key includes all the functionality and device support of a <em>Pro</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>' => '<p>' . _('Your **Trial** key includes all the functionality and device support of a **Pro** key') . '</p><p>' . _('After your **Trial** has reached expiration, your server *still functions normally* until the next time you Stop the array or reboot your server') . '</p><p>' . _('At that point you may either purchase a license key or request a *Trial* extension.') . '</p>',
|
||||
'<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>' => '<p>' . _('Your **Trial** key includes all the functionality and device support of an **Unleashed** key') . '</p><p>' . _('After your **Trial** has reached expiration, your server *still functions normally* until the next time you Stop the array or reboot your server') . '</p><p>' . _('At that point you may either purchase a license key or request a *Trial* extension.') . '</p>',
|
||||
'<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>' => '<p>' . _('Your license key file is corrupted or missing.') . ' ' . _('The key file should be located in the /config directory on your USB Flash boot device') . '</p><p>' . _('If you do not have a backup copy of your license key file you may attempt to recover your key with your Unraid.net account') . '</p><p>' . _('If this was an expired Trial installation, you may purchase a license key.') . '</p>',
|
||||
'<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>' => '<p>' . _('Your license key file is corrupted or missing.') . ' ' . _('The key file should be located in the /config directory on your USB Flash boot device') . '</p><p>' . _('If you do not have a backup copy of your license key file you may attempt to recover your key with your Unraid.net account') . '</p><p>' . _('If this was an expired Trial installation, you may purchase a license key.') . '</p>',
|
||||
'<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of a Pro Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class="list-disc pl-16px"><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>' => '<p>' . _('Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key.') . ' ' . _('A <em>Trial</em> key provides all the functionality of a Pro Registration key.') . '</p><p>' . _('Registration keys are bound to your USB Flash boot device serial number (GUID).') . ' ' . _('Please use a high quality name brand device at least 1GB in size.') . '</p><p>' . _('Note: USB memory card readers are generally not supported because most do not present unique serial numbers.') . '</p><p>' . _('*Important:*') . '</p><ul class="list-disc pl-16px"><li>' . _('Please make sure your server time is accurate to within 5 minutes') . '</li><li>' . _('Please make sure there is a DNS server specified') . '</li>>' . '</ul>',
|
||||
'<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class="list-disc pl-16px"><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>' => '<p>' . _('Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key.') . ' ' . _('A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.') . '</p><p>' . _('Registration keys are bound to your USB Flash boot device serial number (GUID).') . ' ' . _('Please use a high quality name brand device at least 1GB in size.') . '</p><p>' . _('Note: USB memory card readers are generally not supported because most do not present unique serial numbers.') . '</p><p>' . _('*Important:*') . '</p><ul class="list-disc pl-16px"><li>' . _('Please make sure your server time is accurate to within 5 minutes') . '</li><li>' . _('Please make sure there is a DNS server specified') . '</li>>' . '</ul>',
|
||||
'<p>Your Trial key requires an internet connection.</p><p><a href="/Settings/NetworkSettings" class="underline">Please check Settings > Network</a></p>' => '<p>' . _('Your Trial key requires an internet connection') . '</p><p><a href="/Settings/NetworkSettings" class="underline">' . _('Please check Settings > Network') . '</a></p>',
|
||||
'<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>' => '<p>' . _('Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.') . '</p>',
|
||||
'A Trial key provides all the functionality of a Pro Registration key' => _('A Trial key provides all the functionality of a Pro Registration key'),
|
||||
'A Trial key provides all the functionality of an Unleashed Registration key' => _('A Trial key provides all the functionality of an Unleashed Registration key'),
|
||||
'Acklowledge that you have made a Flash Backup to enable this action' => _('Acklowledge that you have made a Flash Backup to enable this action'),
|
||||
'ago' => _('ago'),
|
||||
'All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.' => _('All you need is an active internet connection, an Unraid.net account, and the Connect plugin.') . ' ' . _('Get started by installing the plugin.'),
|
||||
@@ -89,8 +89,10 @@ class WebComponentTranslations
|
||||
'Beta' => _('Beta'),
|
||||
'Blacklisted USB Flash GUID' => _('Blacklisted USB Flash GUID'),
|
||||
'BLACKLISTED' => _('BLACKLISTED'),
|
||||
'Calculating OS Update Eligibility…' => _('Calculating OS Update Eligibility…'),
|
||||
'Calculating trial expiration…' => _('Calculating trial expiration…'),
|
||||
'Callback redirect type not present or incorrect' => _('Callback redirect type not present or incorrect'),
|
||||
'Cancel {0}' => sprintf(_('Cancel %s'), '{0}'),
|
||||
'Cancel' => _('Cancel'),
|
||||
'Cannot access your USB Flash boot device' => _('Cannot access your USB Flash boot device'),
|
||||
'Cannot validate Unraid Trial key' => _('Cannot validate Unraid Trial key'),
|
||||
@@ -107,8 +109,10 @@ class WebComponentTranslations
|
||||
'Close' => _('Close'),
|
||||
'Configure Connect Features' => _('Configure Connect Features'),
|
||||
'Confirm and start update' => _('Confirm and start update'),
|
||||
'Confirm to Install Unraid OS {0}' => sprintf(_('Confirm to Install Unraid OS %s'), '{0}'),
|
||||
'Connected' => _('Connected'),
|
||||
'Contact Support' => _('Contact Support'),
|
||||
'Continue' => _('Continue'),
|
||||
'Copied' => _('Copied'),
|
||||
'Copy Key URL' => _('Copy Key URL'),
|
||||
'Copy your Key URL: {0}' => sprintf(_('Copy your Key URL: %s'), '{0}'),
|
||||
@@ -122,24 +126,30 @@ class WebComponentTranslations
|
||||
'Downgrade Unraid OS to {0}' => sprintf(_('Downgrade Unraid OS to %s'), '{0}'),
|
||||
'Downgrade Unraid OS' => _('Downgrade Unraid OS'),
|
||||
'Downgrades are only recommended if you\'re unable to solve a critical issue.' => _('Downgrades are only recommended if you\'re unable to solve a critical issue.'),
|
||||
'Download Diagnostics' => _('Download Diagnostics'),
|
||||
'Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.' => _('Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.'),
|
||||
'Download unraid-api Logs' => _('Download unraid-api Logs'),
|
||||
'Dynamic Remote Access' => _('Dynamic Remote Access'),
|
||||
'Enable update notifications' => _('Enable update notifications'),
|
||||
'Enhance your experience with Unraid Connect' => _('Enhance your experience with Unraid Connect'),
|
||||
'Enhance your Unraid experience with Connect' => _('Enhance your Unraid experience with Connect'),
|
||||
'Enhance your Unraid experience' => _('Enhance your Unraid experience'),
|
||||
'Error creatiing a trial key. Please try again later.' => _('Error creatiing a trial key. Please try again later.'),
|
||||
'Error creating a trial key. Please try again later.' => _('Error creating a trial key. Please try again later.'),
|
||||
'Error Parsing Changelog • {0}' => sprintf(_('Error Parsing Changelog • %s'), '{0}'),
|
||||
'Error' => _('Error'),
|
||||
'Expired {0}' => sprintf(_('Expired %s'), '{0}'),
|
||||
'Expired' => _('Expired'),
|
||||
'Expires at {0}' => sprintf(_('Expires at %s'), '{0}'),
|
||||
'Expires in {0}' => sprintf(_('Expires in %s'), '{0}'),
|
||||
'Extend License to Update' => _('Extend License to Update'),
|
||||
'Extend License' => _('Extend License'),
|
||||
'Extend Trial' => _('Extend Trial'),
|
||||
'Extending your free trial by 15 days' => _('Extending your free trial by 15 days'),
|
||||
'Extension Installed' => _('Extension Installed'),
|
||||
'Failed to {0} {1} Key' => sprintf(_('Failed to %1s %2s Key'), '{0}', '{1}'),
|
||||
'Failed to install key' => _('Failed to install key'),
|
||||
'Failed to update Connect account configuration' => _('Failed to update Connect account configuration'),
|
||||
'Fetching & parsing changelog…' => _('Fetching & parsing changelog…'),
|
||||
'Fix Error' => _('Fix Error'),
|
||||
'Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.' => sprintf(_('Flash Backup is not available. Navigate to %s/Main/Settings/Flash to try again then come back to this page.'), '{0}'),
|
||||
'Flash GUID Error' => _('Flash GUID Error'),
|
||||
@@ -152,18 +162,24 @@ class WebComponentTranslations
|
||||
'Go to Connect plugin settings' => _('Go to Connect plugin settings'),
|
||||
'Go to Connect' => _('Go to Connect'),
|
||||
'Go to Management Access Now' => _('Go to Management Access Now'),
|
||||
'Go to Settings > Notifications to enable automatic OS update notifications for future releases.' => _('Go to Settings > Notifications to enable automatic OS update notifications for future releases.'),
|
||||
'Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.' => _('Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.'),
|
||||
'Go to Tools > Management Access to ensure your backup is up-to-date.' => _('Go to Tools > Management Access to ensure your backup is up-to-date.'),
|
||||
'Go to Tools > Registration to fix' => _('Go to Tools > Registration to fix'),
|
||||
'Go to Tools > Registration to Learn More' => _('Go to Tools > Registration to Learn More'),
|
||||
'Go to Tools > Update OS for more options.' => _('Go to Tools > Update OS for more options.'),
|
||||
'Go to Tools > Update' => _('Go to Tools > Update'),
|
||||
'hour' => sprintf(_('%s hour'), '{n}') . ' | ' . sprintf(_('%s hours'), '{n}'),
|
||||
'I have made a Flash Backup' => _('I have made a Flash Backup'),
|
||||
'If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.' => _('If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.'),
|
||||
'Ignore this message if you are currently connected via Remote Access or VPN.' => _('Ignore this message if you are currently connected via Remote Access or VPN.'),
|
||||
'Ignore this release until next reboot' => _('Ignore this release until next reboot'),
|
||||
'Ignored Releases' => _('Ignored Releases'),
|
||||
'In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.' => _('In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.'),
|
||||
'Install Connect' => _('Install Connect'),
|
||||
'Install Recovered' => _('Install Recovered'),
|
||||
'Install Replaced' => _('Install Replaced'),
|
||||
'Install Unraid OS {0}' => sprintf(_('Install Unraid OS %s'), '{0}'),
|
||||
'Install' => _('Install'),
|
||||
'Installed' => _('Installed'),
|
||||
'Installing Extended Trial' => _('Installing Extended Trial'),
|
||||
@@ -175,6 +191,9 @@ class WebComponentTranslations
|
||||
'Invalid API Key Format' => _('Invalid API Key Format'),
|
||||
'Invalid API Key' => _('Invalid API Key'),
|
||||
'Invalid installation' => _('Invalid installation'),
|
||||
'It\s highly recommended to review the changelog before continuing your update.' => _('It\'s highly recommended to review the changelog before continuing your update.'),
|
||||
'Key ineligible for {0}' => sprintf(_('Key ineligible for %s'), '{0}'),
|
||||
'Key ineligible for future releases' => _('Key ineligible for future releases'),
|
||||
'Keyfile required to check replacement status' => _('Keyfile required to check replacement status'),
|
||||
'LAN IP {0}' => sprintf(_('LAN IP %s'), '{0}'),
|
||||
'LAN IP Copied' => _('LAN IP Copied'),
|
||||
@@ -182,12 +201,15 @@ class WebComponentTranslations
|
||||
'Last checked: {0}' => sprintf(_('Last checked: %s'), '{0}'),
|
||||
'Learn more about the error' => _('Learn more about the error'),
|
||||
'Learn more and fix' => _('Learn more and fix'),
|
||||
'Learn more and link your key to your account' => _('Learn more and link your key to your account'),
|
||||
'Learn More' => _('Learn More'),
|
||||
'Learn more' => _('Learn more'),
|
||||
'Let\'s Unleash your Hardware!' => _('Let\'s Unleash your Hardware!'),
|
||||
'License key actions' => _('License key actions'),
|
||||
'License key type' => _('License key type'),
|
||||
'License Management' => _('License Management'),
|
||||
'Link Key' => _('Link Key'),
|
||||
'Linked to Unraid.net account' => _('Linked to Unraidnet account'),
|
||||
'Loading' => _('Loading'),
|
||||
'Manage Unraid.net Account in new tab' => _('Manage Unraid.net Account in new tab'),
|
||||
'Manage Unraid.net Account' => _('Manage Unraid.net Account'),
|
||||
@@ -196,6 +218,7 @@ class WebComponentTranslations
|
||||
'minute' => sprintf(_('%s minute'), '{n}') . ' | ' . sprintf(_('%s minutes'), '{n}'),
|
||||
'Missing key file' => _('Missing key file'),
|
||||
'month' => sprintf(_('%s month'), '{n}') . ' | ' . sprintf(_('%s months'), '{n}'),
|
||||
'More options' => _('More options'),
|
||||
'Multiple License Keys Present' => _('Multiple License Keys Present'),
|
||||
'Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.' => _('Never ever be left without a backup of your config.') . ' ' . _('If you need to change flash drives, generate a backup from Connect and be up and running in minutes.'),
|
||||
'New Version: {0}' => sprintf(_('New Version: %s'), '{0}'),
|
||||
@@ -204,14 +227,18 @@ class WebComponentTranslations
|
||||
'No Keyfile' => _('No Keyfile'),
|
||||
'No thanks' => _('No thanks'),
|
||||
'No USB flash configuration data' => _('No USB flash configuration data'),
|
||||
'Not Linked' => _('Not Linked'),
|
||||
'On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.' => _('On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.'),
|
||||
'Online Flash Backup' => _('Online Flash Backup'),
|
||||
'Open a bug report' => _('Open a bug report'),
|
||||
'Open Dropdown' => _('Open Dropdown'),
|
||||
'Opens Connect in new tab' => _('Opens Connect in new tab'),
|
||||
'Original release date {0}' => sprintf(_('Original release date %s'), '{0}'),
|
||||
'OS Update Eligibility Expired' => _('OS Update Eligibility Expired'),
|
||||
'Performing actions' => _('Performing actions'),
|
||||
'Please confirm the update details below' => _('Please confirm the update details below'),
|
||||
'Please finish the initiated downgrade to enable updates.' => _('Please finish the initiated downgrade to enable updates.'),
|
||||
'Please finish the initiated update to enable a downgrade.' => _('Please finish the initiated update to enable a downgrade.'),
|
||||
'Please fix any errors and try again.' => _('Please fix any errors and try again.'),
|
||||
'Please keep this window open while we perform some actions' => _('Please keep this window open while we perform some actions'),
|
||||
'Please keep this window open' => _('Please keep this window open'),
|
||||
@@ -238,14 +265,20 @@ class WebComponentTranslations
|
||||
'Recover Key' => _('Recover Key'),
|
||||
'Recovered' => _('Recovered'),
|
||||
'Redeem Activation Code' => _('Redeem Activation Code'),
|
||||
'Refresh' => _('Refresh'),
|
||||
'Registered on' => _('Registered on'),
|
||||
'Registered to' => _('Registered to'),
|
||||
'Registration key / USB Flash GUID mismatch' => _('Registration key / USB Flash GUID mismatch'),
|
||||
'Release date {0}' => sprintf(_('Release date %s'), '{0}'),
|
||||
'Release requires verification to update' => _('Release requires verification to update'),
|
||||
'Reload' => _('Reload'),
|
||||
'Remark: Unraid\'s WAN IPv4 {0} does not match your client\'s WAN IPv4 {1}.' => sprintf(_('Remark: Unraid\'s WAN IPv4 %1s does not match your client\'s WAN IPv4 %2s.'), '{0}', '{1}'),
|
||||
'Remark: your WAN IPv4 is {0}' => sprintf(_('Remark: your WAN IPv4 is %s'), '{0}'),
|
||||
'Remove from ignore list' => _('Remove from ignore list'),
|
||||
'Remove' => _('Remove'),
|
||||
'Replace Key' => _('Replace Key'),
|
||||
'Replaced' => _('Replaced'),
|
||||
'Requires the local unraid-api to be running successfully' => _('Requires the local unraid-api to be running successfully'),
|
||||
'Restarting unraid-api…' => _('Restarting unraid-api…'),
|
||||
'second' => sprintf(_('%s second'), '{n}') . ' | ' . sprintf(_('%s seconds'), '{n}'),
|
||||
'Server Up Since {0}' => sprintf(_('Server Up Since %s'), '{0}'),
|
||||
@@ -257,6 +290,7 @@ class WebComponentTranslations
|
||||
'Sign In to utilize Unraid Connect' => _('Sign In to utilize Unraid Connect'),
|
||||
'Sign In to your Unraid.net account to get started' => _('Sign In to your Unraid.net account to get started'),
|
||||
'Sign In with Unraid.net Account' => _('Sign In with Unraid.net Account'),
|
||||
'Sign In' => _('Sign In'),
|
||||
'Sign Out Failed' => _('Sign Out Failed'),
|
||||
'Sign Out of Unraid.net' => _('Sign Out of Unraid.net'),
|
||||
'Sign Out requires the local unraid-api to be running' => _('Sign Out requires the local unraid-api to be running'),
|
||||
@@ -298,6 +332,7 @@ class WebComponentTranslations
|
||||
'Unable to fetch client WAN IPv4' => _('Unable to fetch client WAN IPv4'),
|
||||
'Unable to open release notes' => _('Unable to open release notes'),
|
||||
'Unknown error' => _('Unknown error'),
|
||||
'Unknown' => _('Unknown'),
|
||||
'unlimited' => _('unlimited'),
|
||||
'Unraid {0} Available' => sprintf(_('Unraid %s Available'), '{0}'),
|
||||
'Unraid {0} Update Available' => sprintf(_('Unraid %s Update Available'), '{0}'),
|
||||
@@ -310,10 +345,13 @@ class WebComponentTranslations
|
||||
'Unraid logo animating with a wave like effect' => _('Unraid logo animating with a wave like effect'),
|
||||
'Unraid OS {0} Released' => sprintf(_('Unraid OS %s Released'), '{0}'),
|
||||
'Unraid OS {0} Update Available' => sprintf(_('Unraid OS %s Update Available'), '{0}'),
|
||||
'Unraid OS is up-to-date' => _('Unraid OS is up-to-date'),
|
||||
'Unraid OS Update Available' => _('Unraid OS Update Available'),
|
||||
'unraid-api is offline' => _('unraid-api is offline'),
|
||||
'Up-to-date with eligible releases' => _('Up-to-date with eligible releases'),
|
||||
'Up-to-date' => _('Up-to-date'),
|
||||
'Update Available' => _('Update Available'),
|
||||
'Update Released' => _('Update Released'),
|
||||
'Update Unraid OS confirmation required' => _('Update Unraid OS confirmation required'),
|
||||
'Update Unraid OS' => _('Update Unraid OS'),
|
||||
'Updating 3rd party drivers' => _('Updating 3rd party drivers'),
|
||||
@@ -322,15 +360,20 @@ class WebComponentTranslations
|
||||
'Uptime {0}' => sprintf(_('Uptime %s'), '{0}'),
|
||||
'USB Flash device error' => _('USB Flash device error'),
|
||||
'USB Flash has no serial number' => _('USB Flash has no serial number'),
|
||||
'Verify to Update' => _('Verify to Update'),
|
||||
'Version available for restore {0}' => sprintf(_('Version available for restore %s'), '{0}'),
|
||||
'Version: {0}' => sprintf(_('Version: %s'), '{0}'),
|
||||
'View Available Updates' => _('View Available Updates'),
|
||||
'View Changelog & Update' => _('View Changelog & Update'),
|
||||
'View Changelog for {0}' => sprintf(_('View Changelog for %s'), '{0}'),
|
||||
'View Changelog on Docs' => _('View Changelog on Docs'),
|
||||
'View Changelog to Start Update' => _('View Changelog to Start Update'),
|
||||
'View Changelog' => _('View Changelog'),
|
||||
'View on Docs' => _('View on Docs'),
|
||||
'View release notes' => _('View release notes'),
|
||||
'We recommend backing up your USB Flash Boot Device before starting the update.' => _('We recommend backing up your USB Flash Boot Device before starting the update.'),
|
||||
'year' => sprintf(_('%s year'), '{n}') . ' | ' . sprintf(_('%s years'), '{n}'),
|
||||
'You are still eligible to access OS updates that were published on or before {1}.' => sprintf(_('You are still eligible to access OS updates that were published on or before %s.'), '{1}'),
|
||||
'You can also manually create a new backup by clicking the Create Flash Backup button.' => _('You can also manually create a new backup by clicking the Create Flash Backup button.'),
|
||||
'You can manually create a backup by clicking the Create Flash Backup button.' => _('You can manually create a backup by clicking the Create Flash Backup button.'),
|
||||
'You have already activated the Flash Backup feature via the Unraid Connect plugin.' => _('You have already activated the Flash Backup feature via the Unraid Connect plugin.'),
|
||||
@@ -339,7 +382,10 @@ class WebComponentTranslations
|
||||
'You may still update to releases dated prior to your update expiration date.' => _('You may still update to releases dated prior to your update expiration date.'),
|
||||
'You\'re one step closer to enhancing your Unraid experience' => _('You\'re one step closer to enhancing your Unraid experience'),
|
||||
'Your {0} Key has been replaced!' => sprintf(_('Your %s Key has been replaced!'), '{0}'),
|
||||
'Your free Trial key provides all the functionality of a Pro Registration key' => _('Your free Trial key provides all the functionality of a Pro Registration key'),
|
||||
'Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.' => sprintf(_('Your %s license included one year of free updates at the time of purchase.'), '{0}') . ' ' . _('You are now eligible to extend your license and access the latest OS updates.') . ' ' . sprintf(_('You are still eligible to access OS updates that were published on or before %s.'), '{1}'),
|
||||
'Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates.' => sprintf(_('Your %s license included one year of free updates at the time of purchase.'), '{0}') . ' ' . _('You are now eligible to extend your license and access the latest OS updates.'),
|
||||
'Your free Trial key provides all the functionality of an Unleashed Registration key' => _('Your free Trial key provides all the functionality of an Unleashed Registration key'),
|
||||
'Your license key is not eligible for Unraid OS {0}' => sprintf(_('Your license key is not eligible for Unraid OS %s'), '{0}'),
|
||||
'Your Trial has expired' => _('Your Trial has expired'),
|
||||
'Your Trial key has been extended!' => _('Your Trial key has been extended!'),
|
||||
];
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
$cli = php_sapi_name() == 'cli';
|
||||
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
|
||||
/**
|
||||
* @name response_complete
|
||||
@@ -85,7 +86,7 @@ switch ($command) {
|
||||
response_complete(200, array('result' => $output), $output);
|
||||
break;
|
||||
case 'wanip':
|
||||
$wanip = trim(@file_get_contents("https://wanip4.unraid.net/"));
|
||||
$wanip = trim(http_get_contents("https://wanip4.unraid.net/"));
|
||||
response_complete(200, array('result' => $wanip), $wanip);
|
||||
break;
|
||||
case 'none':
|
||||
|
||||
@@ -13,37 +13,17 @@ Tag="upload"
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
/**
|
||||
* @note icon-update is rotated via CSS in myservers1.php
|
||||
*/
|
||||
|
||||
require_once "$docroot/plugins/dynamix.my.servers/include/reboot-details.php";
|
||||
// Create an instance of the RebootDetails class
|
||||
$rebootDetails = new RebootDetails();
|
||||
/**
|
||||
* @note icon-update is rotated via CSS in myservers1.php
|
||||
*
|
||||
* If /boot/previous/bzroot exists, then the user has the option to downgrade to the previous version.
|
||||
* Parse the text file /boot/previous/changes.txt to get the version number of the previous version.
|
||||
* Then we move some files around and reboot.
|
||||
*/
|
||||
$restoreVersion = $restoreBranch = $restoreVersionReleaseDate = 'unknown';
|
||||
$restoreExists = file_exists('/boot/previous/bzroot');
|
||||
$restoreChangelogPath = '/boot/previous/changes.txt';
|
||||
// Get the current reboot details if there are any
|
||||
$rebootDetails->setPrevious();
|
||||
|
||||
$serverNameEscaped = htmlspecialchars(str_replace(' ', '_', strtolower($var['NAME'])));
|
||||
|
||||
if (file_exists($restoreChangelogPath)) {
|
||||
exec("head -n4 $restoreChangelogPath", $rows);
|
||||
foreach ($rows as $row) {
|
||||
$i = stripos($row,'version');
|
||||
if ($i !== false) {
|
||||
[$restoreVersion, $restoreVersionReleaseDate] = explode(' ', trim(substr($row, $i+7)));
|
||||
break;
|
||||
}
|
||||
}
|
||||
$restoreBranch = strpos($restoreVersion, 'rc') !== false
|
||||
? _('Next')
|
||||
: (strpos($restoreVersion, 'beta') !== false
|
||||
? _('Beta')
|
||||
: _('Stable'));
|
||||
}
|
||||
?>
|
||||
|
||||
<script>
|
||||
@@ -139,7 +119,7 @@ function startDowngrade() {
|
||||
$.get(
|
||||
'/plugins/dynamix.plugin.manager/include/Downgrade.php',
|
||||
{
|
||||
version: '<?=$restoreVersion?>',
|
||||
version: '<?= $rebootDetails->previousVersion ?>',
|
||||
},
|
||||
function() {
|
||||
refresh();
|
||||
@@ -150,7 +130,7 @@ function startDowngrade() {
|
||||
function confirmDowngrade() {
|
||||
swal({
|
||||
title: "_(Confirm Downgrade)_",
|
||||
text: "<?= $restoreVersion ?><br>_(A reboot will be required)_",
|
||||
text: "<?= $rebootDetails->previousVersion ?><br>_(A reboot will be required)_",
|
||||
html: true,
|
||||
type: 'warning',
|
||||
showCancelButton: true,
|
||||
@@ -167,7 +147,7 @@ function confirmDowngrade() {
|
||||
|
||||
<unraid-i18n-host>
|
||||
<unraid-downgrade-os
|
||||
reboot-version="<?= $rebootDetails->getRebootVersion() ?>"
|
||||
restore-version="<?= $restoreExists && $restoreVersion != 'unknown' ? $restoreVersion : '' ?>"
|
||||
restore-release-date="<?= $restoreExists && $restoreVersionReleaseDate != 'unknown' ? $restoreVersionReleaseDate : '' ?>"></unraid-downgrade-os>
|
||||
reboot-version="<?= $rebootDetails->rebootVersion ?>"
|
||||
restore-version="<?= $rebootDetails->previousVersion ?>"
|
||||
restore-release-date="<?= $rebootDetails->previousReleaseDate ?>"></unraid-downgrade-os>
|
||||
</unraid-i18n-host>
|
||||
|
||||
@@ -46,5 +46,5 @@ function flashBackup() {
|
||||
</script>
|
||||
|
||||
<unraid-i18n-host>
|
||||
<unraid-update-os reboot-version="<?= $rebootDetails->getRebootVersion() ?>"></unraid-update-os>
|
||||
<unraid-update-os reboot-version="<?= $rebootDetails->rebootVersion ?>"></unraid-update-os>
|
||||
</unraid-i18n-host>
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?php
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, Bergware International.
|
||||
/* Copyright 2005-2024, Lime Technology
|
||||
* Copyright 2012-2024, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
@@ -37,7 +37,7 @@ class UnraidOsCheck
|
||||
private const JSON_FILE_IGNORED = '/tmp/unraidcheck/ignored.json';
|
||||
private const JSON_FILE_IGNORED_KEY = 'updateOsIgnoredReleases';
|
||||
private const JSON_FILE_RESULT = '/tmp/unraidcheck/result.json';
|
||||
private const PLG_PATH = '/var/log/plugins/unRAIDServer.plg';
|
||||
private const PLG_PATH = '/usr/local/emhttp/plugins/unRAIDServer/unRAIDServer.plg';
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
@@ -108,6 +108,7 @@ class UnraidOsCheck
|
||||
function _($text) {return $text;}
|
||||
}
|
||||
|
||||
// this command will set the $notify array
|
||||
extract(parse_plugin_cfg('dynamix', true));
|
||||
|
||||
$var = (array)@parse_ini_file('/var/local/emhttp/var.ini');
|
||||
@@ -124,20 +125,11 @@ class UnraidOsCheck
|
||||
|
||||
$urlbase = $parsedAltUrl ?? $defaultUrl;
|
||||
$url = $urlbase.'?'.http_build_query($params);
|
||||
|
||||
$response = "";
|
||||
// use error handler to convert warnings from file_get_contents to errors so they can be captured
|
||||
function warning_as_error($severity, $message, $filename, $lineno) {
|
||||
throw new ErrorException($message, 0, $severity, $filename, $lineno);
|
||||
$curlinfo = [];
|
||||
$response = http_get_contents($url,[],$curlinfo);
|
||||
if (array_key_exists('error', $curlinfo)) {
|
||||
$response = json_encode(array('error' => $curlinfo['error']), JSON_PRETTY_PRINT);
|
||||
}
|
||||
set_error_handler("warning_as_error");
|
||||
try {
|
||||
$response = file_get_contents($url);
|
||||
} catch (Exception $e) {
|
||||
$response = json_encode(array('error' => $e->getMessage()), JSON_PRETTY_PRINT);
|
||||
}
|
||||
restore_error_handler();
|
||||
|
||||
$responseMutated = json_decode($response, true);
|
||||
if (!$responseMutated) {
|
||||
$response = json_encode(array('error' => 'Invalid response from '.$urlbase), JSON_PRETTY_PRINT);
|
||||
@@ -159,14 +151,17 @@ class UnraidOsCheck
|
||||
|
||||
// send notification if a newer version is available and not ignored
|
||||
$isNewerVersion = array_key_exists('isNewer',$responseMutated) ? $responseMutated['isNewer'] : false;
|
||||
$isReleaseIgnored = in_array($responseMutated['version'], $this->getIgnoredReleases());
|
||||
$isReleaseIgnored = array_key_exists('version',$responseMutated) ? in_array($responseMutated['version'], $this->getIgnoredReleases()) : false;
|
||||
|
||||
if ($responseMutated && $isNewerVersion && !$isReleaseIgnored) {
|
||||
$output = _var($notify,'plugin');
|
||||
$server = strtoupper(_var($var,'NAME','server'));
|
||||
$newver = (array_key_exists('version',$responseMutated) && $responseMutated['version']) ? $responseMutated['version'] : 'unknown';
|
||||
$script = '/usr/local/emhttp/webGui/scripts/notify';
|
||||
exec("$script -e ".escapeshellarg("System - Unraid [$newver]")." -s ".escapeshellarg("Notice [$server] - Version update $newver")." -d ".escapeshellarg("A new version of Unraid is available")." -i ".escapeshellarg("normal $output")." -l '/Tools/Update' -x");
|
||||
$event = "System - Unraid [$newver]";
|
||||
$subject = "Notice [$server] - Version update $newver";
|
||||
$description = "A new version of Unraid is available";
|
||||
exec("$script -e ".escapeshellarg($event)." -s ".escapeshellarg($subject)." -d ".escapeshellarg($description)." -i ".escapeshellarg("normal $output")." -l '/Tools/Update' -x");
|
||||
}
|
||||
|
||||
exit(0);
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
<?php
|
||||
|
||||
class UnraidUpdateCancel
|
||||
{
|
||||
private $PLG_FILENAME;
|
||||
private $PLG_BOOT;
|
||||
private $PLG_VAR;
|
||||
private $USR_LOCAL_PLUGIN_UNRAID_PATH;
|
||||
|
||||
public function __construct() {
|
||||
$this->PLG_FILENAME = "unRAIDServer.plg";
|
||||
$this->PLG_BOOT = "/boot/config/plugins/{$this->PLG_FILENAME}";
|
||||
$this->PLG_VAR = "/var/log/plugins/{$this->PLG_FILENAME}";
|
||||
$this->USR_LOCAL_PLUGIN_UNRAID_PATH = "/usr/local/emhttp/plugins/unRAIDServer";
|
||||
|
||||
// Handle the cancellation
|
||||
$revertResult = $this->revertFiles();
|
||||
// Return JSON response for front-end client
|
||||
$statusCode = $revertResult['success'] ? 200 : 500;
|
||||
http_response_code($statusCode);
|
||||
header('Content-Type: application/json');
|
||||
echo json_encode($revertResult);
|
||||
}
|
||||
|
||||
public function revertFiles() {
|
||||
try {
|
||||
$command = '/sbin/mount | grep -q "/boot/previous/bz"';
|
||||
exec($command, $output, $returnCode);
|
||||
|
||||
if ($returnCode !== 0) {
|
||||
return ['success' => true]; // Nothing to revert
|
||||
}
|
||||
|
||||
// Clear the results of previous unraidcheck run
|
||||
@unlink("/tmp/unraidcheck/result.json");
|
||||
|
||||
// Revert changes made by unRAIDServer.plg
|
||||
shell_exec("mv -f /boot/previous/* /boot");
|
||||
unlink($this->PLG_BOOT);
|
||||
unlink($this->PLG_VAR);
|
||||
symlink("{$this->USR_LOCAL_PLUGIN_UNRAID_PATH}/{$this->PLG_FILENAME}", $this->PLG_VAR);
|
||||
|
||||
// Restore README.md by echoing the content into the file
|
||||
$readmeFile = "{$this->USR_LOCAL_PLUGIN_UNRAID_PATH}/README.md";
|
||||
$readmeContent = "**Unraid OS**\n\n";
|
||||
$readmeContent .= "Unraid OS by [Lime Technology, Inc.](https://lime-technology.com).\n";
|
||||
file_put_contents($readmeFile, $readmeContent);
|
||||
|
||||
return ['success' => true]; // Upgrade handled successfully
|
||||
} catch (\Throwable $th) {
|
||||
return [
|
||||
'success' => false,
|
||||
'message' => $th->getMessage(),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Self instantiate the class and handle the cancellation
|
||||
new UnraidUpdateCancel();
|
||||
@@ -1,6 +1,6 @@
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
* Copyright 2012-2023, Bergware International.
|
||||
/* Copyright 2005-2024, Lime Technology
|
||||
* Copyright 2012-2024, Bergware International.
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or
|
||||
* modify it under the terms of the GNU General Public License version 2,
|
||||
@@ -11,429 +11,12 @@
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
// add translations
|
||||
$_SERVER['REQUEST_URI'] = 'settings';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
|
||||
function host_lookup_ip($host) {
|
||||
$result = @dns_get_record($host, DNS_A);
|
||||
$ip = ($result) ? $result[0]['ip']??'' : '';
|
||||
return($ip);
|
||||
}
|
||||
function rebindDisabled() {
|
||||
global $isLegacyCert;
|
||||
$rebindtesturl = $isLegacyCert ? "rebindtest.unraid.net" : "rebindtest.myunraid.net";
|
||||
// DNS Rebind Protection - this checks the server but clients could still have issues
|
||||
$validResponse = array("192.168.42.42", "fd42");
|
||||
$response = host_lookup_ip($rebindtesturl);
|
||||
return in_array(explode('::',$response)[0], $validResponse);
|
||||
}
|
||||
function format_port($port) {
|
||||
return ($port != 80 && $port != 443) ? ':'.$port : '';
|
||||
}
|
||||
function anonymize_host($host) {
|
||||
global $anon;
|
||||
if ($anon) {
|
||||
$host = preg_replace('/.*\.myunraid\.net/', '*.hash.myunraid.net', $host);
|
||||
$host = preg_replace('/.*\.unraid\.net/', 'hash.unraid.net', $host);
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
function anonymize_ip($ip) {
|
||||
global $anon;
|
||||
if ($anon && filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
|
||||
$ip = "[redacted]";
|
||||
}
|
||||
return $ip;
|
||||
}
|
||||
function generate_internal_host($host, $ip) {
|
||||
if (strpos($host,'.myunraid.net') !== false) {
|
||||
$host = str_replace('*', str_replace('.', '-', $ip), $host);
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
function generate_external_host($host, $ip) {
|
||||
if (strpos($host,'.myunraid.net') !== false) {
|
||||
$host = str_replace('*', str_replace('.', '-', $ip), $host);
|
||||
} elseif (strpos($host,'.unraid.net') !== false) {
|
||||
$host = "www.".$host;
|
||||
}
|
||||
return $host;
|
||||
}
|
||||
function verbose_output($httpcode, $result) {
|
||||
global $cli, $verbose, $anon, $plgversion, $post, $var, $isRegistered, $myservers, $reloadNginx, $nginx, $isLegacyCert;
|
||||
global $remoteaccess;
|
||||
global $icon_warn, $icon_ok;
|
||||
if (!$cli || !$verbose) return;
|
||||
|
||||
if ($anon) echo "(Output is anonymized, use '-vv' to see full details)".PHP_EOL;
|
||||
echo "Unraid OS {$var['version']}".((strpos($plgversion, "base-") === false) ? " with My Servers plugin version {$plgversion}" : '').PHP_EOL;
|
||||
echo ($isRegistered) ? "{$icon_ok}Signed in to Unraid.net as {$myservers['remote']['username']}".PHP_EOL : "{$icon_warn}Not signed in to Unraid.net".PHP_EOL ;
|
||||
echo "Use SSL is {$nginx['NGINX_USESSL']}".PHP_EOL;
|
||||
echo (rebindDisabled()) ? "{$icon_ok}Rebind protection is disabled" : "{$icon_warn}Rebind protection is enabled";
|
||||
echo " for ".($isLegacyCert ? "unraid.net" : "myunraid.net").PHP_EOL;
|
||||
if ($post) {
|
||||
$wanip = trim(@file_get_contents("https://wanip4.unraid.net/"));
|
||||
// check the data
|
||||
$certhostname = $nginx['NGINX_CERTNAME'];
|
||||
if ($certhostname) {
|
||||
// $certhostname is $nginx['NGINX_CERTNAME'] (certificate_bundle.pem)
|
||||
$certhostip = host_lookup_ip(generate_internal_host($certhostname, $post['internalip']));
|
||||
$certhosterr = ($certhostip != $post['internalip']);
|
||||
}
|
||||
if ($post['internalhostname'] != $certhostname) {
|
||||
// $post['internalhostname'] is $nginx['NGINX_LANMDNS'] (no cert, or Server_unraid_bundle.pem) || $nginx['NGINX_CERTNAME'] (certificate_bundle.pem)
|
||||
$internalhostip = host_lookup_ip(generate_internal_host($post['internalhostname'], $post['internalip']));
|
||||
$internalhosterr = ($internalhostip != $post['internalip']);
|
||||
}
|
||||
if (!empty($post['externalhostname'])) {
|
||||
// $post['externalhostname'] is $nginx['NGINX_CERTNAME'] (certificate_bundle.pem)
|
||||
$externalhostip = host_lookup_ip(generate_external_host($post['externalhostname'], $wanip));
|
||||
$externalhosterr = ($externalhostip != $wanip);
|
||||
}
|
||||
// anonymize data. no caclulations can be done with this data beyond this point.
|
||||
if ($anon) {
|
||||
if (!empty($certhostip)) $certhostip = anonymize_ip($certhostip);
|
||||
if (!empty($certhostname)) $certhostname = anonymize_host($certhostname);
|
||||
if (!empty($internalhostip)) $internalhostip = anonymize_ip($internalhostip);
|
||||
if (!empty($externalhostip)) $externalhostip = anonymize_ip($externalhostip);
|
||||
if (!empty($wanip)) $wanip = anonymize_ip($wanip);
|
||||
if (!empty($post['internalip'])) $post['internalip'] = anonymize_ip($post['internalip']);
|
||||
if (!empty($post['internalhostname'])) $post['internalhostname'] = anonymize_host($post['internalhostname']);
|
||||
if (!empty($post['externalhostname'])) $post['externalhostname'] = anonymize_host($post['externalhostname']);
|
||||
if (!empty($post['externalport'])) $post['externalport'] = "[redacted]";
|
||||
}
|
||||
// always anonymize the keyfile
|
||||
if (!empty($post['keyfile'])) $post['keyfile'] = "[redacted]";
|
||||
// output notes
|
||||
if (!empty($post['internalprotocol']) && !empty($post['internalhostname']) && !empty($post['internalport'])) {
|
||||
$localurl = $post['internalprotocol']."://".generate_internal_host($post['internalhostname'], $post['internalip']).format_port($post['internalport']);
|
||||
echo 'Local Access url: '.$localurl.PHP_EOL;
|
||||
if ($internalhostip) {
|
||||
// $internalhostip will not be defined for .local domains, ok to skip
|
||||
echo ($internalhosterr) ? $icon_warn : $icon_ok;
|
||||
echo generate_internal_host($post['internalhostname'], $post['internalip'])." resolves to {$internalhostip}";
|
||||
echo ($internalhosterr) ? ", it should resolve to {$post['internalip']}" : "";
|
||||
echo PHP_EOL;
|
||||
}
|
||||
if ($certhostname) {
|
||||
echo ($certhosterr) ? $icon_warn : $icon_ok;
|
||||
echo generate_internal_host($certhostname, $post['internalip']).' ';
|
||||
echo ($certhostip) ? "resolves to {$certhostip}" : "does not resolve to an IP address";
|
||||
echo ($certhosterr) ? ", it should resolve to {$post['internalip']}" : "";
|
||||
echo PHP_EOL;
|
||||
}
|
||||
if ($remoteaccess == 'yes' && !empty($post['externalprotocol']) && !empty($post['externalhostname']) && !empty($post['externalport'])) {
|
||||
$remoteurl = $post['externalprotocol']."://".generate_external_host($post['externalhostname'], $wanip).format_port($post['externalport']);
|
||||
echo 'Remote Access url: '.$remoteurl.PHP_EOL;
|
||||
echo ($externalhosterr) ? $icon_warn : $icon_ok;
|
||||
echo generate_external_host($post['externalhostname'], $wanip).' ';
|
||||
echo ($externalhosterr) ? "does not resolve to an IP address" : "resolves to {$externalhostip}";
|
||||
echo PHP_EOL;
|
||||
}
|
||||
if ($reloadNginx) {
|
||||
echo "IP address changes were detected, nginx was reloaded".PHP_EOL;
|
||||
}
|
||||
}
|
||||
// output post data
|
||||
echo PHP_EOL.'Request:'.PHP_EOL;
|
||||
echo @json_encode($post, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
|
||||
}
|
||||
if ($result) {
|
||||
echo "Response (HTTP $httpcode):".PHP_EOL;
|
||||
$mutatedResult = is_array($result) ? json_encode($result) : $result;
|
||||
echo @json_encode(@json_decode($mutatedResult, true), JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES | JSON_PRETTY_PRINT) . PHP_EOL;
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @name response_complete
|
||||
* @param {HTTP Response Status Code} $httpcode https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
|
||||
* @param {String|Array} $result - strings are assumed to be encoded JSON. Arrays will be encoded to JSON.
|
||||
* @param {String} $cli_success_msg
|
||||
*/
|
||||
function response_complete($httpcode, $result, $cli_success_msg='') {
|
||||
global $cli, $verbose;
|
||||
$mutatedResult = is_array($result) ? json_encode($result) : $result;
|
||||
if ($cli) {
|
||||
if ($verbose) verbose_output($httpcode, $result);
|
||||
$json = @json_decode($mutatedResult,true);
|
||||
if (!empty($json['error'])) {
|
||||
echo 'Error: '.$json['error'].PHP_EOL;
|
||||
exit(1);
|
||||
}
|
||||
exit($cli_success_msg.PHP_EOL);
|
||||
}
|
||||
header('Content-Type: application/json');
|
||||
http_response_code($httpcode);
|
||||
exit((string)$mutatedResult);
|
||||
}
|
||||
|
||||
// This is a stub, does nothing but return success
|
||||
$cli = php_sapi_name()=='cli';
|
||||
$verbose = $anon = false;
|
||||
if ($cli && ($argc > 1) && $argv[1] == "-v") {
|
||||
$verbose = true;
|
||||
$anon = true;
|
||||
}
|
||||
if ($cli && ($argc > 1) && $argv[1] == "-vv") {
|
||||
$verbose = true;
|
||||
}
|
||||
$var = parse_ini_file('/var/local/emhttp/var.ini');
|
||||
$nginx = parse_ini_file('/var/local/emhttp/nginx.ini');
|
||||
$is69 = version_compare($var['version'],"6.9.9","<");
|
||||
$reloadNginx = false;
|
||||
$dnserr = false;
|
||||
$icon_warn = "⚠️ ";
|
||||
$icon_ok = "✅ ";
|
||||
|
||||
$myservers_flash_cfg_path='/boot/config/plugins/dynamix.my.servers/myservers.cfg';
|
||||
$myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers_flash_cfg_path,true) : [];
|
||||
// ensure some vars are defined here so we don't have to test them later
|
||||
if (empty($myservers['remote']['apikey'])) {
|
||||
$myservers['remote']['apikey'] = "";
|
||||
}
|
||||
if (empty($myservers['remote']['wanaccess'])) {
|
||||
$myservers['remote']['wanaccess'] = "no";
|
||||
}
|
||||
if (empty($myservers['remote']['wanport'])) {
|
||||
$myservers['remote']['wanport'] = 443;
|
||||
}
|
||||
// remoteaccess, externalport
|
||||
if ($cli) {
|
||||
$remoteaccess = (empty($nginx['NGINX_WANFQDN'])) ? 'no' : 'yes';
|
||||
$externalport = $myservers['remote']['wanport'];
|
||||
} else {
|
||||
$remoteaccess = $_POST['remoteaccess']??'no';
|
||||
$externalport = intval($_POST['externalport']??443);
|
||||
|
||||
if ($remoteaccess != 'yes') {
|
||||
$remoteaccess = 'no';
|
||||
}
|
||||
|
||||
if ($externalport < 1 || $externalport > 65535) {
|
||||
$externalport = 443;
|
||||
}
|
||||
|
||||
if ($myservers['remote']['wanaccess'] != $remoteaccess) {
|
||||
// update the wanaccess ini value
|
||||
$orig = file_exists($myservers_flash_cfg_path) ? parse_ini_file($myservers_flash_cfg_path,true) : [];
|
||||
if (!$orig) {
|
||||
$orig = ['remote' => $myservers['remote']];
|
||||
}
|
||||
$orig['remote']['wanaccess'] = $remoteaccess;
|
||||
$text = '';
|
||||
foreach ($orig as $section => $block) {
|
||||
$pairs = "";
|
||||
foreach ($block as $key => $value) if (strlen($value)) $pairs .= "$key=\"$value\"\n";
|
||||
if ($pairs) $text .= "[$section]\n".$pairs;
|
||||
}
|
||||
if ($text) file_put_contents($myservers_flash_cfg_path, $text);
|
||||
// need nginx reload
|
||||
$reloadNginx = true;
|
||||
}
|
||||
exit("success".PHP_EOL);
|
||||
}
|
||||
$isRegistered = !empty($myservers['remote']['username']);
|
||||
|
||||
// protocols, hostnames, ports
|
||||
$internalprotocol = 'http';
|
||||
$internalport = $nginx['NGINX_PORT'];
|
||||
$internalhostname = $nginx['NGINX_LANMDNS'];
|
||||
$externalprotocol = 'https';
|
||||
// keyserver will expand *.hash.myunraid.net or add www to hash.unraid.net as needed
|
||||
$externalhostname = $nginx['NGINX_CERTNAME'];
|
||||
$isLegacyCert = preg_match('/.*\.unraid\.net$/', $nginx['NGINX_CERTNAME']);
|
||||
$isWildcardCert = preg_match('/.*\.myunraid\.net$/', $nginx['NGINX_CERTNAME']);
|
||||
$internalip = $nginx['NGINX_LANIP'];
|
||||
|
||||
if ($nginx['NGINX_USESSL']=='yes') {
|
||||
// When NGINX_USESSL is 'yes' in 6.9, it could be using either Server_unraid_bundle.pem or certificate_bundle.pem
|
||||
// When NGINX_USESSL is 'yes' in 6.10, it is is using Server_unraid_bundle.pem
|
||||
$internalprotocol = 'https';
|
||||
$internalport = $nginx['NGINX_PORTSSL'];
|
||||
if ($is69 && $nginx['NGINX_CERTNAME']) {
|
||||
// this is from certificate_bundle.pem
|
||||
$internalhostname = $nginx['NGINX_CERTNAME'];
|
||||
}
|
||||
}
|
||||
if ($nginx['NGINX_USESSL']=='auto') {
|
||||
// NGINX_USESSL cannot be 'auto' in 6.9, it is either 'yes' or 'no'
|
||||
// When NGINX_USESSL is 'auto' in 6.10, it is using certificate_bundle.pem
|
||||
$internalprotocol = 'https';
|
||||
$internalport = $nginx['NGINX_PORTSSL'];
|
||||
// keyserver will expand *.hash.myunraid.net as needed
|
||||
$internalhostname = $nginx['NGINX_CERTNAME'];
|
||||
}
|
||||
|
||||
// My Servers version
|
||||
$plgversion = file_exists("/var/log/plugins/dynamix.unraid.net.plg") ? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.plg 2>/dev/null'))
|
||||
: ( file_exists("/var/log/plugins/dynamix.unraid.net.staging.plg") ? trim(@exec('/usr/local/sbin/plugin version /var/log/plugins/dynamix.unraid.net.staging.plg 2>/dev/null'))
|
||||
: 'base-'.$var['version'] );
|
||||
|
||||
// only proceed when when signed in or when legacy unraid.net SSL certificate exists
|
||||
if (!$isRegistered && !$isLegacyCert) {
|
||||
response_complete(406, array('error' => _('Nothing to do')));
|
||||
}
|
||||
|
||||
// keyfile
|
||||
$keyfile = empty($var['regFILE']) ? false : @file_get_contents($var['regFILE']);
|
||||
if ($keyfile === false) {
|
||||
response_complete(406, array('error' => _('Registration key required')));
|
||||
}
|
||||
$keyfile = @base64_encode($keyfile);
|
||||
|
||||
// build post array
|
||||
$post = [
|
||||
'keyfile' => $keyfile,
|
||||
'plgversion' => $plgversion
|
||||
];
|
||||
if ($isLegacyCert) {
|
||||
// sign in not required to maintain local ddns for unraid.net cert
|
||||
// enable local ddns regardless of use_ssl value
|
||||
$post['internalip'] = $internalip;
|
||||
// if host.unraid.net does not resolve to the internalip and DNS Rebind Protection is disabled, disable caching
|
||||
if (host_lookup_ip(generate_internal_host($nginx['NGINX_CERTNAME'], $post['internalip'])) != $post['internalip'] && rebindDisabled()) $dnserr = true;
|
||||
}
|
||||
if ($isRegistered) {
|
||||
// if signed in, send data needed to maintain My Servers Dashboard
|
||||
$post['internalhostname'] = $internalhostname;
|
||||
$post['internalport'] = $internalport;
|
||||
$post['internalprotocol'] = $internalprotocol;
|
||||
$post['remoteaccess'] = $remoteaccess;
|
||||
$post['servercomment'] = $var['COMMENT'];
|
||||
$post['servername'] = $var['NAME'];
|
||||
if ($isWildcardCert) {
|
||||
// keyserver needs the internalip to generate the local access url
|
||||
$post['internalip'] = $internalip;
|
||||
}
|
||||
if ($remoteaccess == 'yes') {
|
||||
// include wanip in the cache file so we can track if it changes
|
||||
$post['_wanip'] = trim(@file_get_contents("https://wanip4.unraid.net/"));
|
||||
$post['externalhostname'] = $externalhostname;
|
||||
$post['externalport'] = $externalport;
|
||||
$post['externalprotocol'] = $externalprotocol;
|
||||
// if wanip.hash.myunraid.net or www.hash.unraid.net does not resolve to the wanip, disable caching
|
||||
if (host_lookup_ip(generate_external_host($post['externalhostname'], $post['_wanip'])) != $post['_wanip']) $dnserr = true;
|
||||
}
|
||||
}
|
||||
|
||||
// Include unraid-api report
|
||||
$unraidreport = [];
|
||||
if (file_exists('/usr/local/sbin/unraid-api')) {
|
||||
$jsonString = trim(@exec("/usr/local/sbin/unraid-api report --json 2>/dev/null"));
|
||||
$unraidreport = @json_decode($jsonString, true);
|
||||
if ($unraidreport === false) {
|
||||
$post['unraidreport'] = $jsonString;
|
||||
} else {
|
||||
// remove fields we don't need to submit
|
||||
unset($unraidreport['servers']);
|
||||
}
|
||||
} elseif (strpos($plgversion, "base-") === false) {
|
||||
// The plugin is installed but the api doesn't exist. This is a failed install. Generate basic troubleshooting data.
|
||||
if (file_exists('/boot/config/plugins/dynamix.my.servers/env')) {
|
||||
@extract(parse_ini_file('/boot/config/plugins/dynamix.my.servers/env',true));
|
||||
}
|
||||
if (empty($env)) {
|
||||
$env = "production";
|
||||
}
|
||||
$unraidreport['os']['version'] = $var['version'];
|
||||
$unraidreport['api']['version'] = "failed install";
|
||||
$unraidreport['api']['status'] = "missing";
|
||||
$unraidreport['api']['environment'] = $env;
|
||||
$unraidreport['relay']['status'] = "disconnected";
|
||||
$unraidreport['minigraph']['status'] = "disconnected";
|
||||
if ($isRegistered) {
|
||||
$unraidreport['myServers']['status'] = "authenticated";
|
||||
$unraidreport['myServers']['myServersUsername'] = $myservers['remote']['username'];
|
||||
} else {
|
||||
$unraidreport['myServers']['status'] = "signed out";
|
||||
}
|
||||
$unraidreport['apiKey'] = (empty($myservers['remote']['apikey'])) ? "invalid" : "exists";
|
||||
}
|
||||
|
||||
if (!empty($unraidreport)) {
|
||||
// include unraid-api crash logs
|
||||
$crashLog = '/var/log/unraid-api/crash.json';
|
||||
$crashAge = 0;
|
||||
if (file_exists($crashLog)) {
|
||||
$crashTime = filemtime($crashLog);
|
||||
$crashAge = time() - $crashTime; // age of crashLog in seconds
|
||||
$crashDetails = @json_decode(@file_get_contents($crashLog), true);
|
||||
if (empty($crashDetails['apiVersion']) && $crashAge < 30*60) {
|
||||
// found a recent crash log without an apiVersion, assume was created by current version of api
|
||||
$crashDetails['apiVersion'] = $unraidreport['api']['version'];
|
||||
// overwrite the crash log so it will always have the apiVersion
|
||||
file_put_contents($crashLog, json_encode($crashDetails, JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES));
|
||||
// reset to original timestamp so crashAge remains accurate
|
||||
touch($crashLog, $crashTime);
|
||||
}
|
||||
$unraidreport['crashAge'] = $crashAge;
|
||||
$unraidreport['crashLogs'] = $crashDetails;
|
||||
}
|
||||
|
||||
// add flash backup status
|
||||
$flashbackup_ini = '/var/local/emhttp/flashbackup.ini';
|
||||
$flashbackup_status = (file_exists($flashbackup_ini)) ? @parse_ini_file($flashbackup_ini) : [];
|
||||
if (empty($flashbackup_status['activated'])) {
|
||||
$flashbackup_status['activated'] = "";
|
||||
}
|
||||
if (empty($flashbackup_status['error'])) {
|
||||
$flashbackup_status['error'] = "";
|
||||
}
|
||||
$unraidreport['flashbackup']['activated'] = ($flashbackup_status['activated']) ? "yes" : "no";
|
||||
$unraidreport['flashbackup']['error'] = ($flashbackup_status['error']) ? $flashbackup_status['error'] : "no";
|
||||
|
||||
// add unraidreport to payload
|
||||
$post['unraidreport'] = json_encode($unraidreport);
|
||||
|
||||
// if the api is stopped and there are no crashLogs, or any crashLogs are more than maxCrashAge, start the api
|
||||
$maxCrashAge = 1*60*60; // 1 hour
|
||||
if ($unraidreport['api']['status'] == 'stopped' && (empty($unraidreport['crashLogs']) || $crashAge > $maxCrashAge)) {
|
||||
exec("echo \"/usr/local/sbin/unraid-api start\" | at -M now >/dev/null 2>&1");
|
||||
}
|
||||
}
|
||||
|
||||
// if remoteaccess is enabled in 6.10.0-rc3+ and WANIP has changed since nginx started, reload nginx
|
||||
if (isset($post['_wanip']) && ($post['_wanip'] != $nginx['NGINX_WANIP']) && version_compare($var['version'],"6.10.0-rc2",">")) $reloadNginx = true;
|
||||
// if remoteaccess is currently disabled (perhaps because a wanip was not available when nginx was started)
|
||||
// BUT the system is configured to have it enabled AND a wanip is now available
|
||||
// then reload nginx
|
||||
if ($remoteaccess == 'no' && $nginx['NGINX_WANACCESS'] == 'yes' && !empty(trim(@file_get_contents("https://wanip4.unraid.net/")))) $reloadNginx = true;
|
||||
if ($reloadNginx) {
|
||||
exec("/etc/rc.d/rc.nginx reload &>/dev/null");
|
||||
}
|
||||
|
||||
// maxage is 36 hours
|
||||
$maxage = 36*60*60;
|
||||
if ($dnserr || $verbose) $maxage = 0;
|
||||
$datafile = "/tmp/UpdateDNS.txt";
|
||||
$datafiletmp = "/tmp/UpdateDNS.txt.new";
|
||||
$dataprev = @file_get_contents($datafile) ?: '';
|
||||
$datanew = implode("\n",$post)."\n";
|
||||
if ($datanew == $dataprev && (time()-filemtime($datafile) < $maxage)) {
|
||||
response_complete(204, null, _('No change to report'));
|
||||
}
|
||||
file_put_contents($datafiletmp,$datanew);
|
||||
rename($datafiletmp, $datafile);
|
||||
|
||||
// do not submit the wanip, it will be captured from the submission if needed for remote access
|
||||
unset($post['_wanip']);
|
||||
|
||||
// report necessary server details to limetech for DNS updates
|
||||
$ch = curl_init('https://keys.lime-technology.com/account/server/register');
|
||||
curl_setopt($ch, CURLOPT_POST, 1);
|
||||
curl_setopt($ch, CURLOPT_POSTFIELDS, $post);
|
||||
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
|
||||
$result = curl_exec($ch);
|
||||
$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||
$error = curl_error($ch);
|
||||
curl_close($ch);
|
||||
|
||||
if ( ($result === false) || ($httpcode != "200") ) {
|
||||
// delete cache file to retry submission on next run
|
||||
@unlink($datafile);
|
||||
response_complete($httpcode ?? "500", array('error' => $error));
|
||||
}
|
||||
|
||||
response_complete($httpcode, $result, _('success'));
|
||||
header('Content-Type: application/json');
|
||||
http_response_code(204);
|
||||
exit(0);
|
||||
?>
|
||||
|
||||
@@ -2,4 +2,5 @@ VITE_ACCOUNT=https://localhost:8008
|
||||
VITE_CONNECT=https://connect.myunraid.net
|
||||
VITE_UNRAID_NET=https://unraid.ddev.site
|
||||
VITE_CALLBACK_KEY=aNotSoSecretKeyUsedToObfuscateQueryParams
|
||||
VITE_ALLOW_CONSOLE_LOGS=false
|
||||
VITE_ALLOW_CONSOLE_LOGS=false
|
||||
VITE_WEBGUI=http://localhost
|
||||
@@ -1,26 +0,0 @@
|
||||
/* eslint-disable @typescript-eslint/no-var-requires */
|
||||
const { readFileSync } = require('fs');
|
||||
const { parse } = require('dotenv');
|
||||
|
||||
const envConfig = parse(readFileSync('.env'));
|
||||
for (const k in envConfig) {
|
||||
process.env[k] = envConfig[k];
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extends: ['@nuxtjs/eslint-config-typescript'],
|
||||
ignorePatterns: ['composables/gql/'],
|
||||
rules: {
|
||||
'comma-dangle': ['warn', 'only-multiline'],
|
||||
semi: ['error', 'always'],
|
||||
quotes: ['warn', 'single'],
|
||||
'no-console': (process.env.NODE_ENV === 'production' ? 'error' : 'off'),
|
||||
'no-debugger': (process.env.NODE_ENV === 'production' ? 'error' : 'off'),
|
||||
'@typescript-eslint/no-unused-vars': process.env.NODE_ENV === 'production' ? 'error' : 'warn',
|
||||
'max-len': 'off',
|
||||
'vue/multi-word-component-names': 'off',
|
||||
'vue/v-on-event-hyphenation': 'off',
|
||||
'vue/no-v-html': 'off',
|
||||
'no-fallthrough': 'off',
|
||||
}
|
||||
};
|
||||
@@ -1 +1 @@
|
||||
18.17.1
|
||||
18.19.1
|
||||
@@ -10,7 +10,7 @@ import type {
|
||||
Server,
|
||||
ServerState,
|
||||
// ServerUpdateOsResponse,
|
||||
} from '~/types/server';
|
||||
} from "~/types/server";
|
||||
|
||||
// dayjs plugins
|
||||
// extend(customParseFormat);
|
||||
@@ -24,8 +24,32 @@ import type {
|
||||
// return result;
|
||||
// }
|
||||
|
||||
// ENOKEYFILE
|
||||
// TRIAL
|
||||
// BASIC
|
||||
// PLUS
|
||||
// PRO
|
||||
// STARTER
|
||||
// UNLEASHED
|
||||
// LIFETIME
|
||||
// EEXPIRED
|
||||
// EGUID
|
||||
// EGUID1
|
||||
// ETRIAL
|
||||
// ENOKEYFILE2
|
||||
// ENOKEYFILE1
|
||||
// ENOFLASH
|
||||
// EBLACKLISTED
|
||||
// EBLACKLISTED1
|
||||
// EBLACKLISTED2
|
||||
// ENOCONN
|
||||
|
||||
// '1111-1111-5GDB-123412341234' Starter.key = TkJCrVyXMLWWGKZF6TCEvf0C86UYI9KfUDSOm7JoFP19tOMTMgLKcJ6QIOt9_9Psg_t0yF-ANmzSgZzCo94ljXoPm4BESFByR0K7nyY9KVvU8szLEUcBUT3xC2adxLrAXFNxiPeK-mZqt34n16uETKYvLKL_Sr5_JziG5L5lJFBqYZCPmfLMiguFo1vp0xL8pnBH7q8bYoBnePrAcAVb9mAGxFVPEInSPkMBfC67JLHz7XY1Y_K5bYIq3go9XPtLltJ53_U4BQiMHooXUBJCKXodpqoGxq0eV0IhNEYdauAhnTsG90qmGZig0hZalQ0soouc4JZEMiYEcZbn9mBxPg
|
||||
const staticGuid = '1111-1111-5GDB-123412341234';
|
||||
|
||||
const state: ServerState = "BASIC" as ServerState;
|
||||
const currentFlashGuid = "1111-1111-CFXF-TEST1234ZACK"; // this is the flash drive that's been booted from
|
||||
const regGuid = "1111-1111-CFXF-TEST1234ZACK"; // this guid is registered in key server
|
||||
const keyfileBase64 = "asdf"; // @todo raycast download key to base64
|
||||
|
||||
// const randomGuid = `1111-1111-${makeid(4)}-123412341234`; // this guid is registered in key server
|
||||
// const newGuid = `1234-1234-${makeid(4)}-123412341234`; // this is a new USB, not registered
|
||||
@@ -42,70 +66,51 @@ const oneHourFromNow = Date.now() + 60 * 60 * 1000; // 1 hour from now
|
||||
let expireTime = 0;
|
||||
let regExp: number | undefined;
|
||||
|
||||
// ENOKEYFILE
|
||||
// TRIAL
|
||||
// BASIC
|
||||
// PLUS
|
||||
// PRO
|
||||
// EEXPIRED
|
||||
// EGUID
|
||||
// EGUID1
|
||||
// ETRIAL
|
||||
// ENOKEYFILE2
|
||||
// ENOKEYFILE1
|
||||
// ENOFLASH
|
||||
// EBLACKLISTED
|
||||
// EBLACKLISTED1
|
||||
// EBLACKLISTED2
|
||||
// ENOCONN
|
||||
const state: ServerState = 'STARTER';
|
||||
let regDev = 0;
|
||||
let regTy = '';
|
||||
let regDevs = 0;
|
||||
let regTy = "";
|
||||
switch (state) {
|
||||
// @ts-ignore
|
||||
case 'EEXPIRED':
|
||||
case "EEXPIRED":
|
||||
expireTime = uptime; // 1 hour ago
|
||||
// @ts-ignore
|
||||
case 'ENOCONN':
|
||||
// @ts-ignore
|
||||
case 'TRIAL':
|
||||
break;
|
||||
case "ENOCONN":
|
||||
break;
|
||||
case "TRIAL":
|
||||
expireTime = oneHourFromNow; // in 1 hour
|
||||
regTy = 'Trial';
|
||||
// @ts-ignore
|
||||
case 'BASIC':
|
||||
regDev = 6;
|
||||
// @ts-ignore
|
||||
case 'PLUS':
|
||||
regDev = 12;
|
||||
// @ts-ignore
|
||||
case 'PRO':
|
||||
// @ts-ignore
|
||||
case 'STARTER':
|
||||
regDev = 4;
|
||||
// regExp = oneHourFromNow;
|
||||
// regExp = oneDayFromNow;
|
||||
regTy = "Trial";
|
||||
break;
|
||||
case "BASIC":
|
||||
regDevs = 6;
|
||||
regTy = "Basic";
|
||||
break;
|
||||
case "PLUS":
|
||||
regDevs = 12;
|
||||
regTy = "Plus";
|
||||
break;
|
||||
case "PRO":
|
||||
regDevs = -1;
|
||||
regTy = "Pro";
|
||||
break;
|
||||
case "STARTER":
|
||||
regDevs = 6;
|
||||
regExp = ninetyDaysAgo;
|
||||
// regExp = uptime;
|
||||
// regExp = 1696363920000; // nori.local's expiration
|
||||
// @ts-ignore
|
||||
case 'UNLEASHED':
|
||||
// regExp = oneHourFromNow;
|
||||
// regExp = oneDayFromNow;
|
||||
// regExp = oneDayAgo;
|
||||
// regExp = uptime;
|
||||
// regExp = 1696363920000; // nori.local's expiration
|
||||
// @ts-ignore
|
||||
case 'LIFETIME':
|
||||
if (regDev === 0) { regDev = 99999; }
|
||||
if (regTy === '') { regTy = state.charAt(0).toUpperCase() + state.substring(1).toLowerCase(); } // title case
|
||||
regTy = "Starter";
|
||||
break;
|
||||
case "UNLEASHED":
|
||||
regDevs = -1;
|
||||
regExp = ninetyDaysAgo;
|
||||
regTy = "Unleashed";
|
||||
break;
|
||||
case "LIFETIME":
|
||||
regDevs = -1;
|
||||
regTy = "Lifetime";
|
||||
break;
|
||||
}
|
||||
|
||||
const connectPluginInstalled = 'dynamix.unraid.net.staging.plg';
|
||||
// const connectPluginInstalled = '';
|
||||
// const connectPluginInstalled = 'dynamix.unraid.net.staging.plg';
|
||||
const connectPluginInstalled = "";
|
||||
|
||||
const osVersion = '6.12.5';
|
||||
const osVersionBranch = 'stable';
|
||||
const osVersion = "6.12.8";
|
||||
const osVersionBranch = "stable";
|
||||
// const parsedRegExp = regExp ? dayjs(regExp).format('YYYY-MM-DD') : undefined;
|
||||
|
||||
// const mimicWebguiUnraidCheck = async (): Promise<ServerUpdateOsResponse | undefined> => {
|
||||
@@ -130,59 +135,60 @@ const osVersionBranch = 'stable';
|
||||
// };
|
||||
|
||||
export const serverState: Server = {
|
||||
apiKey: 'unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810',
|
||||
avatar: 'https://source.unsplash.com/300x300/?portrait',
|
||||
apiKey: "unupc_fab6ff6ffe51040595c6d9ffb63a353ba16cc2ad7d93f813a2e80a5810",
|
||||
avatar: "https://source.unsplash.com/300x300/?portrait",
|
||||
config: {
|
||||
// error: 'INVALID',
|
||||
valid: true,
|
||||
error: null,
|
||||
valid: false,
|
||||
},
|
||||
connectPluginInstalled,
|
||||
description: 'DevServer9000',
|
||||
description: "DevServer9000",
|
||||
deviceCount: 3,
|
||||
expireTime,
|
||||
flashBackupActivated: !!connectPluginInstalled,
|
||||
flashProduct: 'SanDisk_3.2Gen1',
|
||||
flashVendor: 'USB',
|
||||
guid: staticGuid,
|
||||
flashProduct: "SanDisk_3.2Gen1",
|
||||
flashVendor: "USB",
|
||||
guid: currentFlashGuid,
|
||||
// "guid": "0781-5583-8355-81071A2B0211",
|
||||
inIframe: false,
|
||||
// keyfile: 'DUMMY_KEYFILE',
|
||||
keyfile: 'TkJCrVyXMLWWGKZF6TCEvf0C86UYI9KfUDSOm7JoFP19tOMTMgLKcJ6QIOt9_9Psg_t0yF-ANmzSgZzCo94ljXoPm4BESFByR0K7nyY9KVvU8szLEUcBUT3xC2adxLrAXFNxiPeK-mZqt34n16uETKYvLKL_Sr5_JziG5L5lJFBqYZCPmfLMiguFo1vp0xL8pnBH7q8bYoBnePrAcAVb9mAGxFVPEInSPkMBfC67JLHz7XY1Y_K5bYIq3go9XPtLltJ53_U4BQiMHooXUBJCKXodpqoGxq0eV0IhNEYdauAhnTsG90qmGZig0hZalQ0soouc4JZEMiYEcZbn9mBxPg',
|
||||
lanIp: '192.168.254.36',
|
||||
license: '',
|
||||
locale: 'en_US', // en_US, ja
|
||||
name: 'dev-static',
|
||||
keyfile: keyfileBase64,
|
||||
lanIp: "192.168.254.36",
|
||||
license: "",
|
||||
locale: "en_US", // en_US, ja
|
||||
name: "dev-static",
|
||||
osVersion,
|
||||
osVersionBranch,
|
||||
// registered: connectPluginInstalled ? true : false,
|
||||
registered: false,
|
||||
regGen: 0,
|
||||
regTm: twoDaysAgo,
|
||||
regTo: 'Zack Spear',
|
||||
regTo: "Zack Spear",
|
||||
regTy,
|
||||
regDevs,
|
||||
regExp,
|
||||
// "regGuid": "0781-5583-8355-81071A2B0211",
|
||||
site: 'http://localhost:4321',
|
||||
regGuid,
|
||||
site: "http://localhost:4321",
|
||||
state,
|
||||
theme: {
|
||||
banner: false,
|
||||
bannerGradient: false,
|
||||
bgColor: '',
|
||||
bgColor: "",
|
||||
descriptionShow: true,
|
||||
metaColor: '',
|
||||
name: 'white',
|
||||
textColor: ''
|
||||
},
|
||||
updateOsResponse: {
|
||||
version: '6.12.6',
|
||||
name: 'Unraid 6.12.6',
|
||||
date: '2023-12-13',
|
||||
isNewer: true,
|
||||
isEligible: false,
|
||||
changelog: 'https://docs.unraid.net/unraid-os/release-notes/6.12.6/',
|
||||
sha256: '2f5debaf80549029cf6dfab0db59180e7e3391c059e6521aace7971419c9c4bf',
|
||||
metaColor: "",
|
||||
name: "white",
|
||||
textColor: "",
|
||||
},
|
||||
// updateOsResponse: {
|
||||
// version: '6.12.6',
|
||||
// name: 'Unraid 6.12.6',
|
||||
// date: '2023-12-13',
|
||||
// isNewer: true,
|
||||
// isEligible: false,
|
||||
// changelog: 'https://docs.unraid.net/unraid-os/release-notes/6.12.6/',
|
||||
// sha256: '2f5debaf80549029cf6dfab0db59180e7e3391c059e6521aace7971419c9c4bf',
|
||||
// },
|
||||
uptime,
|
||||
username: 'zspearmint',
|
||||
wanFQDN: ''
|
||||
username: "zspearmint",
|
||||
wanFQDN: "",
|
||||
};
|
||||
|
||||
@@ -7,6 +7,8 @@ Tag="globe"
|
||||
/**
|
||||
* @todo create web component env switcher liker upcEnv(). If we utilize manifest.json then we'll be switching its path.
|
||||
*/
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
$myservers_flash_cfg_path='/boot/config/plugins/dynamix.my.servers/myservers.cfg';
|
||||
$myservers = file_exists($myservers_flash_cfg_path) ? @parse_ini_file($myservers_flash_cfg_path,true) : [];
|
||||
// print_r($mystatus);
|
||||
@@ -142,7 +144,7 @@ if ($display['theme'] === 'black' || $display['theme'] === 'azure') {
|
||||
<unraid-key-actions></connect-key-actions>
|
||||
</div>
|
||||
<div class="ComponentWrapper">
|
||||
<unraid-wan-ip-check php-wan-ip="<?=@file_get_contents('https://wanip4.unraid.net/')?>"></connect-wan-ip-check>
|
||||
<unraid-wan-ip-check php-wan-ip="<?=http_get_contents('https://wanip4.unraid.net/')?>"></connect-wan-ip-check>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
const nuxtApp = useNuxtApp();
|
||||
const { registerEntry } = useCustomElements();
|
||||
onBeforeMount(() => {
|
||||
// @ts-ignore
|
||||
nuxtApp.$customElements.registerEntry('UnraidComponents');
|
||||
registerEntry('UnraidComponents');
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
<script lang="ts" setup>
|
||||
// eslint-disable vue/no-v-html
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
@@ -25,7 +26,7 @@ const { authAction, stateData } = storeToRefs(serverStore);
|
||||
size="12px"
|
||||
:text="t(authAction.text)"
|
||||
:title="authAction?.title ? t(authAction?.title) : undefined"
|
||||
@click="authAction.click()"
|
||||
@click="authAction.click?.()"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
@@ -6,12 +6,14 @@ import type { ButtonProps } from '~/types/ui/button';
|
||||
const props = withDefaults(defineProps<ButtonProps>(), {
|
||||
btnStyle: 'fill',
|
||||
btnType: 'button',
|
||||
class: undefined,
|
||||
click: undefined,
|
||||
href: undefined,
|
||||
icon: undefined,
|
||||
iconRight: undefined,
|
||||
iconRightHoverDisplay: false,
|
||||
// iconRightHoverAnimate: true,
|
||||
noPadding: false,
|
||||
size: '16px',
|
||||
text: '',
|
||||
title: '',
|
||||
@@ -58,33 +60,35 @@ const classes = computed(() => {
|
||||
|
||||
switch (props.size) {
|
||||
case '12px':
|
||||
buttonSize = 'text-12px p-8px gap-4px';
|
||||
buttonSize = `text-12px ${props.noPadding ? 'p-0' : 'p-8px'} gap-4px`;
|
||||
iconSize = 'w-12px';
|
||||
break;
|
||||
case '14px':
|
||||
buttonSize = 'text-14px p-8px gap-8px';
|
||||
buttonSize = `text-14px ${props.noPadding ? 'p-0' : 'p-8px'} gap-8px`;
|
||||
iconSize = 'w-14px';
|
||||
break;
|
||||
case '16px':
|
||||
buttonSize = 'text-16px p-12px gap-8px';
|
||||
buttonSize = `text-16px ${props.noPadding ? 'p-0' : 'p-12px'} gap-8px`;
|
||||
iconSize = 'w-16px';
|
||||
break;
|
||||
case '18px':
|
||||
buttonSize = 'text-18px p-12px gap-8px';
|
||||
buttonSize = `text-18px ${props.noPadding ? 'p-0' : 'p-12px'} gap-8px`;
|
||||
iconSize = 'w-18px';
|
||||
break;
|
||||
case '20px':
|
||||
buttonSize = 'text-20px p-16px gap-8px';
|
||||
buttonSize = `text-20px ${props.noPadding ? 'p-0' : 'p-16px'} gap-8px`;
|
||||
iconSize = 'w-20px';
|
||||
break;
|
||||
case '24px':
|
||||
buttonSize = 'text-24px p-16px gap-8px';
|
||||
buttonSize = `text-24px ${props.noPadding ? 'p-0' : 'p-16px'} gap-8px`;
|
||||
iconSize = 'w-24px';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
button: `${buttonSize} ${buttonColors} ${buttonDefaults}`,
|
||||
button: props.btnStyle === 'none'
|
||||
? `${buttonSize} ${props.class}`
|
||||
: `${buttonSize} ${buttonColors} ${buttonDefaults} ${props.class}`,
|
||||
icon: `${iconSize} fill-current flex-shrink-0`,
|
||||
};
|
||||
});
|
||||
|
||||
47
web/components/ConnectSettings/AllowedOrigins.vue
Normal file
47
web/components/ConnectSettings/AllowedOrigins.vue
Normal file
@@ -0,0 +1,47 @@
|
||||
<script lang="ts" setup>
|
||||
import { useUnraidApiSettingsStore } from '~/store/unraidApiSettings';
|
||||
|
||||
const apiSettingsStore = useUnraidApiSettingsStore();
|
||||
|
||||
const originsText = ref<string>('');
|
||||
const errors = ref<string[]>([]);
|
||||
|
||||
onMounted(async () => {
|
||||
const allowedOriginsSettings = await apiSettingsStore.getAllowedOrigins();
|
||||
originsText.value = allowedOriginsSettings.join(', ');
|
||||
});
|
||||
|
||||
const origins = computed<string[]>(() => {
|
||||
console.log('originsText.value: ' + originsText.value);
|
||||
const newOrigins: string[] = [];
|
||||
if (originsText.value) {
|
||||
originsText.value.split(',').forEach((origin) => {
|
||||
try {
|
||||
const newUrl = new URL(origin.trim());
|
||||
newOrigins.push(newUrl.toString());
|
||||
} catch (e) {
|
||||
errors.value.push(`Invalid origin: ${origin}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return newOrigins;
|
||||
});
|
||||
|
||||
const setAllowedOrigins = () => {
|
||||
apiSettingsStore.setAllowedOrigins(origins.value);
|
||||
};
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<h2>Setup Allowed Origins</h2>
|
||||
<input v-model="originsText" type="text" placeholder="Input Comma Separated List of URLs">
|
||||
<button type="button" @click="setAllowedOrigins()">
|
||||
Set Allowed Origins
|
||||
</button>
|
||||
<div v-for="(error, index) of errors" :key="index">
|
||||
<p>{{ error }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
17
web/components/ConnectSettings/ConnectSettings.ce.vue
Normal file
17
web/components/ConnectSettings/ConnectSettings.ce.vue
Normal file
@@ -0,0 +1,17 @@
|
||||
<script lang="ts" setup>
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
// import { useI18n } from 'vue-i18n';
|
||||
|
||||
// const { t } = useI18n();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<AuthCe />
|
||||
<!-- @todo: flashback up -->
|
||||
<WanIpCheckCe />
|
||||
<ConnectSettingsRemoteAccess />
|
||||
<ConnectSettingsAllowedOrigins />
|
||||
<DownloadApiLogsCe />
|
||||
</template>
|
||||
60
web/components/ConnectSettings/RemoteAccess.vue
Normal file
60
web/components/ConnectSettings/RemoteAccess.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<script lang="ts" setup>
|
||||
import { WAN_ACCESS_TYPE, WAN_FORWARD_TYPE } from '~/composables/gql/graphql';
|
||||
import { useUnraidApiSettingsStore } from '~/store/unraidApiSettings';
|
||||
|
||||
const apiSettingsStore = useUnraidApiSettingsStore();
|
||||
|
||||
const accessType = ref<WAN_ACCESS_TYPE>(WAN_ACCESS_TYPE.Disabled);
|
||||
const forwardType = ref<WAN_FORWARD_TYPE | null>(null);
|
||||
const port = ref<number | null>(null);
|
||||
|
||||
onMounted(async () => {
|
||||
const remoteAccessSettings = await apiSettingsStore.getRemoteAccess();
|
||||
accessType.value =
|
||||
remoteAccessSettings?.accessType ?? WAN_ACCESS_TYPE.Disabled;
|
||||
forwardType.value = remoteAccessSettings?.forwardType ?? null;
|
||||
port.value = remoteAccessSettings?.port ?? null;
|
||||
});
|
||||
|
||||
const setRemoteAccess = () => {
|
||||
apiSettingsStore.setupRemoteAccess({
|
||||
accessType: accessType.value,
|
||||
...(forwardType.value ? { forwardType: forwardType.value } : {}),
|
||||
...(port.value ? { port: port.value } : {}),
|
||||
});
|
||||
};
|
||||
|
||||
watch(accessType, (newVal) => {
|
||||
if (newVal !== WAN_ACCESS_TYPE.Disabled) {
|
||||
forwardType.value = WAN_FORWARD_TYPE.Static;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-col">
|
||||
<h2>Setup Remote Access</h2>
|
||||
<label for="forwardType">Forward Type</label>
|
||||
<select id="forwardType" v-model="accessType">
|
||||
<option v-for="(val, index) in Object.values(WAN_ACCESS_TYPE)" :key="index" :value="val">
|
||||
{{ val }}
|
||||
</option>
|
||||
</select>
|
||||
<template v-if="accessType !== WAN_ACCESS_TYPE.Disabled">
|
||||
<label for="forwardType">Forward Type</label>
|
||||
<select id="forwardType" v-model="forwardType">
|
||||
<option v-for="(val, index) in Object.values(WAN_FORWARD_TYPE)" :key="index" :value="val">
|
||||
{{ val }}
|
||||
</option>
|
||||
</select>
|
||||
</template>
|
||||
<template v-if="forwardType === WAN_FORWARD_TYPE.Static && accessType !== WAN_ACCESS_TYPE.Disabled">
|
||||
<label for="port">Port</label>
|
||||
<input id="port" v-model="port" type="number">
|
||||
</template>
|
||||
|
||||
<button @click="setRemoteAccess">
|
||||
Save
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -39,7 +39,7 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const { rebootType } = storeToRefs(serverStore);
|
||||
const { rebootType, osVersionBranch } = storeToRefs(serverStore);
|
||||
|
||||
const subtitle = computed(() => {
|
||||
if (rebootType.value === 'update') {
|
||||
@@ -48,6 +48,8 @@ const subtitle = computed(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
const showExternalDowngrade = computed(() => osVersionBranch.value !== 'stable');
|
||||
|
||||
onBeforeMount(() => {
|
||||
serverStore.setRebootVersion(props.rebootVersion);
|
||||
});
|
||||
@@ -59,6 +61,7 @@ onBeforeMount(() => {
|
||||
:title="t('Downgrade Unraid OS')"
|
||||
:subtitle="subtitle"
|
||||
:downgrade-not-available="restoreVersion === '' && rebootType === ''"
|
||||
:show-external-downgrade="showExternalDowngrade"
|
||||
:t="t"
|
||||
/>
|
||||
<UpdateOsDowngrade
|
||||
|
||||
@@ -10,10 +10,12 @@ import { useI18n } from 'vue-i18n';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
|
||||
import { WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
|
||||
import { getReleaseNotesUrl, WEBGUI_TOOLS_DOWNGRADE, WEBGUI_TOOLS_UPDATE } from '~/helpers/urls';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
import type { UiBadgeProps, UiBadgePropsColor } from '~/types/ui/badge';
|
||||
|
||||
const { t } = useI18n();
|
||||
|
||||
@@ -25,6 +27,9 @@ const { osVersion, rebootType, stateDataError } = storeToRefs(serverStore);
|
||||
const { available, availableWithRenewal } = storeToRefs(updateOsStore);
|
||||
const { rebootTypeText } = storeToRefs(updateOsActionsStore);
|
||||
|
||||
export interface UpdateOsStatus extends UserProfileLink {
|
||||
badge: UiBadgeProps;
|
||||
}
|
||||
const updateOsStatus = computed(() => {
|
||||
if (stateDataError.value) { // only allowed to update when server is does not have a state error
|
||||
return null;
|
||||
@@ -32,8 +37,10 @@ const updateOsStatus = computed(() => {
|
||||
|
||||
if (rebootTypeText.value) {
|
||||
return {
|
||||
badgeColor: 'yellow',
|
||||
badgeIcon: ExclamationTriangleIcon,
|
||||
badge: {
|
||||
color: 'yellow' as UiBadgePropsColor,
|
||||
icon: ExclamationTriangleIcon,
|
||||
},
|
||||
href: rebootType.value === 'downgrade'
|
||||
? WEBGUI_TOOLS_DOWNGRADE.toString()
|
||||
: WEBGUI_TOOLS_UPDATE.toString(),
|
||||
@@ -43,6 +50,10 @@ const updateOsStatus = computed(() => {
|
||||
|
||||
if (availableWithRenewal.value || available.value) {
|
||||
return {
|
||||
badge: {
|
||||
color: 'orange' as UiBadgePropsColor,
|
||||
icon: BellAlertIcon,
|
||||
},
|
||||
click: () => { updateOsStore.setModalOpen(true); },
|
||||
text: availableWithRenewal.value
|
||||
? t('Update Released')
|
||||
@@ -59,37 +70,42 @@ const updateOsStatus = computed(() => {
|
||||
|
||||
<template>
|
||||
<div class="flex flex-row justify-start gap-x-4px">
|
||||
<button
|
||||
<a
|
||||
class="group leading-none"
|
||||
:title="t('View release notes')"
|
||||
@click="updateOsActionsStore.viewReleaseNotes(t('{0} Release Notes', [osVersion]))"
|
||||
:href="getReleaseNotesUrl(osVersion).toString()"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<UiBadge
|
||||
color="custom"
|
||||
:icon="InformationCircleIcon"
|
||||
icon-styles="text-gamma"
|
||||
icon-styles="text-header-text-secondary"
|
||||
size="14px"
|
||||
class="text-gamma group-hover:text-orange-dark group-focus:text-orange-dark group-hover:underline group-focus:underline"
|
||||
>
|
||||
{{ osVersion }}
|
||||
</UiBadge>
|
||||
</button>
|
||||
|
||||
</a>
|
||||
<component
|
||||
:is="updateOsStatus.href ? 'a' : 'button'"
|
||||
v-if="updateOsStatus"
|
||||
:href="updateOsStatus.href ?? undefined"
|
||||
:title="updateOsStatus.title ?? undefined"
|
||||
class="group"
|
||||
@click="updateOsStatus.click ? updateOsStatus.click() : undefined"
|
||||
@click="updateOsStatus.click?.()"
|
||||
>
|
||||
<UiBadge
|
||||
:color="updateOsStatus.badgeColor ?? 'orange'"
|
||||
:icon="updateOsStatus.badgeIcon ?? BellAlertIcon"
|
||||
v-if="updateOsStatus.badge"
|
||||
:color="updateOsStatus.badge.color"
|
||||
:icon="updateOsStatus.badge.icon"
|
||||
size="12px"
|
||||
>
|
||||
{{ updateOsStatus.text }}
|
||||
</UiBadge>
|
||||
<template v-else>
|
||||
{{ updateOsStatus.text }}
|
||||
</template>
|
||||
</component>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -2,10 +2,7 @@
|
||||
import { provide } from 'vue';
|
||||
import { createI18n, I18nInjectionKey } from 'vue-i18n';
|
||||
|
||||
import { disableProductionConsoleLogs } from '~/helpers/functions';
|
||||
|
||||
import en_US from '~/locales/en_US.json'; // eslint-disable-line camelcase
|
||||
disableProductionConsoleLogs();
|
||||
import en_US from '~/locales/en_US.json';
|
||||
// import ja from '~/locales/ja.json';
|
||||
|
||||
const defaultLocale = 'en_US'; // ja, en_US
|
||||
@@ -17,6 +14,7 @@ let nonDefaultLocale = false;
|
||||
* Unfortunately, this was the only way I could get the data from PHP to vue-i18n :(
|
||||
* I tried using i18n.setLocaleMessage() but it didn't work no matter what I tried.
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const windowLocaleData = (window as any).LOCALE_DATA || null;
|
||||
if (windowLocaleData) {
|
||||
try {
|
||||
@@ -33,7 +31,7 @@ const i18n = createI18n<false>({
|
||||
locale: nonDefaultLocale ? parsedLocale : defaultLocale,
|
||||
fallbackLocale: defaultLocale,
|
||||
messages: {
|
||||
en_US, // eslint-disable-line camelcase
|
||||
en_US,
|
||||
// ja,
|
||||
...(nonDefaultLocale ? parsedMessages : {}),
|
||||
}
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { ServerStateDataAction } from '~/types/server';
|
||||
@@ -10,7 +11,7 @@ const props = withDefaults(defineProps<{
|
||||
filterBy?: string[] | undefined;
|
||||
filterOut?: string[] | undefined;
|
||||
maxWidth?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}>(), {
|
||||
actions: undefined,
|
||||
filterBy: undefined,
|
||||
@@ -49,7 +50,7 @@ const filteredKeyActions = computed((): ServerStateDataAction[] | undefined => {
|
||||
:icon-right-hover-display="true"
|
||||
:text="t(action.text)"
|
||||
:title="action.title ? t(action.title) : undefined"
|
||||
@click="action.click()"
|
||||
@click="action.click?.()"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { TransitionChild, TransitionRoot } from '@headlessui/vue';
|
||||
import { XMarkIcon } from '@heroicons/vue/24/outline';
|
||||
import useFocusTrap from '~/composables/useFocusTrap';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
export interface Props {
|
||||
centerContent?: boolean;
|
||||
@@ -11,7 +11,7 @@ export interface Props {
|
||||
open?: boolean;
|
||||
showCloseX?: boolean;
|
||||
success?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
tallContent?: boolean;
|
||||
title?: string;
|
||||
}
|
||||
@@ -28,9 +28,11 @@ const props = withDefaults(defineProps<Props>(), {
|
||||
});
|
||||
watchEffect(() => {
|
||||
// toggle body scrollability
|
||||
return props.open
|
||||
? document.body.style.setProperty('overflow', 'hidden')
|
||||
: document.body.style.removeProperty('overflow');
|
||||
if (props.open) {
|
||||
document.body.style.setProperty('overflow', 'hidden')
|
||||
} else {
|
||||
document.body.style.removeProperty('overflow');
|
||||
}
|
||||
});
|
||||
|
||||
const emit = defineEmits(['close']);
|
||||
@@ -38,8 +40,6 @@ const closeModal = () => {
|
||||
emit('close');
|
||||
};
|
||||
|
||||
const { trapRef } = useFocusTrap();
|
||||
|
||||
const ariaLablledById = computed((): string|undefined => props.title ? `ModalTitle-${Math.random()}`.replace('0.', '') : undefined);
|
||||
|
||||
/**
|
||||
@@ -50,7 +50,6 @@ const ariaLablledById = computed((): string|undefined => props.title ? `ModalTit
|
||||
<template>
|
||||
<TransitionRoot appear :show="open" as="template">
|
||||
<div
|
||||
ref="trapRef"
|
||||
class="fixed inset-0 z-10 overflow-y-auto"
|
||||
role="dialog"
|
||||
aria-dialog="true"
|
||||
|
||||
@@ -25,12 +25,14 @@ import { storeToRefs } from 'pinia';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { RegistrationItemProps } from '~/types/registration';
|
||||
|
||||
import KeyActions from '~/components/KeyActions.vue';
|
||||
import RegistrationReplaceCheck from '~/components/Registration/ReplaceCheck.vue';
|
||||
import RegistrationKeyLinkedStatus from '~/components/Registration/KeyLinkedStatus.vue';
|
||||
import RegistrationUpdateExpirationAction from '~/components/Registration/UpdateExpirationAction.vue';
|
||||
import UserProfileUptimeExpire from '~/components/UserProfile/UptimeExpire.vue';
|
||||
|
||||
@@ -38,7 +40,10 @@ const { t } = useI18n();
|
||||
|
||||
const replaceRenewCheckStore = useReplaceRenewStore();
|
||||
const serverStore = useServerStore();
|
||||
|
||||
const {
|
||||
computedArray,
|
||||
arrayWarning,
|
||||
authAction,
|
||||
dateTimeFormat,
|
||||
deviceCount,
|
||||
@@ -47,18 +52,21 @@ const {
|
||||
flashProduct,
|
||||
keyActions,
|
||||
keyfile,
|
||||
computedRegDevs,
|
||||
regGuid,
|
||||
regTm,
|
||||
regTo,
|
||||
regTy,
|
||||
regExp,
|
||||
regUpdatesExpired,
|
||||
serverErrors,
|
||||
state,
|
||||
stateData,
|
||||
stateDataError,
|
||||
tooManyDevices,
|
||||
} = storeToRefs(serverStore);
|
||||
|
||||
const formattedRegTm = ref<any>();
|
||||
const formattedRegTm = ref<string>();
|
||||
/**
|
||||
* regTm may not have a value until we get a response from the refreshServerState action
|
||||
* So we need to watch for this value to be able to format it based on the user's date time preferences.
|
||||
@@ -74,35 +82,42 @@ watch(regTm, (_newV) => {
|
||||
});
|
||||
onBeforeMount(() => {
|
||||
setFormattedRegTm();
|
||||
/** automatically check for replacement and renewal eligibility…will prompt user if eligible for a renewal / key re-roll for legacy keys */
|
||||
if (guid.value && keyfile.value) {
|
||||
replaceRenewCheckStore.check();
|
||||
}
|
||||
});
|
||||
|
||||
const devicesAvailable = computed((): number => {
|
||||
switch (regTy.value) {
|
||||
case 'Starter':
|
||||
return 4;
|
||||
case 'Basic':
|
||||
return 6;
|
||||
case 'Plus':
|
||||
return 12;
|
||||
case 'Unleashed':
|
||||
case 'Lifetime':
|
||||
case 'Pro':
|
||||
case 'Trial':
|
||||
return 9999;
|
||||
default:
|
||||
return 0;
|
||||
const headingIcon = computed(() => serverErrors.value.length ? ShieldExclamationIcon : ShieldCheckIcon);
|
||||
const heading = computed(() => {
|
||||
if (serverErrors.value.length) { // It's rare to have multiple errors but for the time being only show the first error
|
||||
return serverErrors.value[0]?.heading;
|
||||
}
|
||||
return stateData.value.heading;
|
||||
});
|
||||
const subheading = computed(() => {
|
||||
if (serverErrors.value.length) { // It's rare to have multiple errors but for the time being only show the first error
|
||||
return serverErrors.value[0]?.message;
|
||||
}
|
||||
return stateData.value.message;
|
||||
});
|
||||
|
||||
const showTrialExpiration = computed((): boolean => state.value === 'TRIAL' || state.value === 'EEXPIRED');
|
||||
const showUpdateEligibility = computed((): boolean => !!(regExp.value));
|
||||
const keyInstalled = computed((): boolean => !!(!stateDataError.value && state.value !== 'ENOKEYFILE'));
|
||||
const showTransferStatus = computed((): boolean => !!(keyInstalled.value && guid.value && !showTrialExpiration.value));
|
||||
const showLinkedAndTransferStatus = computed((): boolean => !!(keyInstalled.value && guid.value && !showTrialExpiration.value));
|
||||
// filter out renew action and only display other key actions…renew is displayed in RegistrationUpdateExpirationAction
|
||||
const showFilteredKeyActions = computed((): boolean => !!(keyActions.value && keyActions.value?.filter(action => !['renew'].includes(action.name)).length > 0));
|
||||
|
||||
const items = computed((): RegistrationItemProps[] => {
|
||||
return [
|
||||
...(computedArray.value
|
||||
? [{
|
||||
label: t('Array status'),
|
||||
text: computedArray.value,
|
||||
warning: arrayWarning.value,
|
||||
}]
|
||||
: []),
|
||||
...(regTy.value
|
||||
? [{
|
||||
label: t('License key type'),
|
||||
@@ -169,20 +184,28 @@ const items = computed((): RegistrationItemProps[] => {
|
||||
: []),
|
||||
...(keyInstalled.value
|
||||
? [{
|
||||
error: deviceCount.value > devicesAvailable.value,
|
||||
error: tooManyDevices.value,
|
||||
label: t('Attached Storage Devices'),
|
||||
text: deviceCount.value > devicesAvailable.value
|
||||
? t('{0} out of {1} allowed devices – upgrade your key to support more devices', [deviceCount.value, devicesAvailable.value > 12 ? t('unlimited') : devicesAvailable.value])
|
||||
: t('{0} out of {1} devices', [deviceCount.value, devicesAvailable.value > 12 ? t('unlimited') : devicesAvailable.value]),
|
||||
text: tooManyDevices.value
|
||||
? t('{0} out of {1} allowed devices – upgrade your key to support more devices', [deviceCount.value, computedRegDevs.value])
|
||||
: t('{0} out of {1} devices', [deviceCount.value, computedRegDevs.value === -1 ? t('unlimited') : computedRegDevs.value]),
|
||||
}]
|
||||
: []),
|
||||
...(showTransferStatus.value
|
||||
...(showLinkedAndTransferStatus.value
|
||||
? [{
|
||||
label: t('Transfer License to New Flash'),
|
||||
component: RegistrationReplaceCheck,
|
||||
componentProps: { t },
|
||||
}]
|
||||
: []),
|
||||
...(regTo.value && showLinkedAndTransferStatus.value
|
||||
? [{
|
||||
label: t('Linked to Unraid.net account'),
|
||||
component: RegistrationKeyLinkedStatus,
|
||||
componentProps: { t },
|
||||
}]
|
||||
: []),
|
||||
|
||||
...(showFilteredKeyActions.value
|
||||
? [{
|
||||
component: KeyActions,
|
||||
@@ -194,13 +217,6 @@ const items = computed((): RegistrationItemProps[] => {
|
||||
: []),
|
||||
];
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
/** automatically check for replacement and renewal eligibility…will prompt user if eligible for a renewal / key re-roll for legacy keys */
|
||||
if (guid.value && keyfile.value) {
|
||||
replaceRenewCheckStore.check();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@@ -210,17 +226,17 @@ onBeforeMount(() => {
|
||||
<header class="flex flex-col gap-y-16px">
|
||||
<h3
|
||||
class="text-20px md:text-24px font-semibold leading-normal flex flex-row items-center gap-8px"
|
||||
:class="stateDataError ? 'text-unraid-red' : 'text-green-500'"
|
||||
:class="serverErrors.length ? 'text-unraid-red' : 'text-green-500'"
|
||||
>
|
||||
<component :is="stateDataError ? ShieldExclamationIcon : ShieldCheckIcon" class="w-24px h-24px" />
|
||||
<component :is="headingIcon" class="w-24px h-24px" />
|
||||
<span>
|
||||
{{ stateData.heading }}
|
||||
{{ heading }}
|
||||
</span>
|
||||
</h3>
|
||||
<div
|
||||
v-if="stateData.message"
|
||||
v-if="subheading"
|
||||
class="prose text-16px leading-relaxed whitespace-normal opacity-75"
|
||||
v-html="stateData.message"
|
||||
v-html="subheading"
|
||||
/>
|
||||
<span v-if="authAction" class="grow-0">
|
||||
<BrandButton
|
||||
@@ -228,7 +244,7 @@ onBeforeMount(() => {
|
||||
:icon="authAction.icon"
|
||||
:text="t(authAction.text)"
|
||||
:title="authAction.title ? t(authAction.title) : undefined"
|
||||
@click="authAction.click()"
|
||||
@click="authAction.click?.()"
|
||||
/>
|
||||
</span>
|
||||
</header>
|
||||
|
||||
@@ -25,9 +25,9 @@ const evenBgColor = computed(() => {
|
||||
error && 'text-white bg-unraid-red',
|
||||
warning && 'text-black bg-yellow-100',
|
||||
]"
|
||||
class="text-16px p-12px grid grid-cols-1 gap-4px sm:px-20px sm:grid-cols-5 sm:gap-16px items-start rounded"
|
||||
class="text-16px p-12px grid grid-cols-1 gap-4px sm:px-20px sm:grid-cols-5 sm:gap-16px items-baseline rounded"
|
||||
>
|
||||
<dt v-if="label" class="font-semibold sm:col-span-2 flex flex-row sm:justify-end sm:text-right items-center gap-x-8px">
|
||||
<dt v-if="label" class="font-semibold leading-normal sm:col-span-2 flex flex-row sm:justify-end sm:text-right items-center gap-x-8px">
|
||||
<ShieldExclamationIcon v-if="error" class="w-16px h-16px fill-current" />
|
||||
<span v-html="label" />
|
||||
</dt>
|
||||
@@ -35,7 +35,13 @@ const evenBgColor = computed(() => {
|
||||
class="leading-normal sm:col-span-3"
|
||||
:class="!label && 'sm:col-start-2'"
|
||||
>
|
||||
<span v-if="text" class="select-all" :class="!error ? 'opacity-75' : ''">
|
||||
<span
|
||||
v-if="text"
|
||||
class="select-all"
|
||||
:class="{
|
||||
'opacity-75': !error,
|
||||
}"
|
||||
>
|
||||
{{ text }}
|
||||
</span>
|
||||
<template v-if="$slots['right']">
|
||||
|
||||
74
web/components/Registration/KeyLinkedStatus.vue
Normal file
74
web/components/Registration/KeyLinkedStatus.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<script setup lang="ts">
|
||||
import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
ArrowPathIcon,
|
||||
LinkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useAccountStore } from '~/store/account';
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const replaceRenewStore = useReplaceRenewStore();
|
||||
const { keyLinkedStatus, keyLinkedOutput } = storeToRefs(replaceRenewStore);
|
||||
|
||||
defineProps<{
|
||||
t: ComposerTranslation;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap items-center justify-between gap-8px">
|
||||
<BrandButton
|
||||
v-if="keyLinkedStatus !== 'linked' && keyLinkedStatus !== 'checking'"
|
||||
btn-style="none"
|
||||
:no-padding="true"
|
||||
:title="t('Refresh')"
|
||||
class="group"
|
||||
@click="replaceRenewStore.check(true)"
|
||||
>
|
||||
<UiBadge
|
||||
v-if="keyLinkedOutput"
|
||||
:color="keyLinkedOutput.color"
|
||||
:icon="keyLinkedOutput.icon"
|
||||
:icon-right="ArrowPathIcon"
|
||||
size="16px"
|
||||
>
|
||||
{{ t(keyLinkedOutput.text ?? 'Unknown') }}
|
||||
</UiBadge>
|
||||
</BrandButton>
|
||||
<UiBadge
|
||||
v-else
|
||||
:color="keyLinkedOutput.color"
|
||||
:icon="keyLinkedOutput.icon"
|
||||
size="16px"
|
||||
>
|
||||
{{ t(keyLinkedOutput.text ?? 'Unknown') }}
|
||||
</UiBadge>
|
||||
|
||||
<span class="inline-flex flex-wrap-items-start gap-8px">
|
||||
<BrandButton
|
||||
v-if="keyLinkedStatus === 'notLinked'"
|
||||
btn-style="underline"
|
||||
:external="true"
|
||||
:icon="LinkIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:text="t('Link Key')"
|
||||
:title="t('Learn more and link your key to your account')"
|
||||
class="text-14px"
|
||||
@click="accountStore.linkKey"
|
||||
/>
|
||||
<BrandButton
|
||||
v-else
|
||||
btn-style="underline"
|
||||
:external="true"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:text="t('Learn More')"
|
||||
class="text-14px"
|
||||
@click="accountStore.myKeys"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
KeyIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { DOCS_REGISTRATION_REPLACE_KEY } from '~/helpers/urls';
|
||||
import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
@@ -12,12 +13,12 @@ const replaceRenewStore = useReplaceRenewStore();
|
||||
const { replaceStatusOutput } = storeToRefs(replaceRenewStore);
|
||||
|
||||
defineProps<{
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}>();
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="flex flex-wrap items-start justify-between gap-8px">
|
||||
<div class="flex flex-wrap items-center justify-between gap-8px">
|
||||
<BrandButton
|
||||
v-if="!replaceStatusOutput"
|
||||
:icon="KeyIcon"
|
||||
@@ -32,16 +33,18 @@ defineProps<{
|
||||
:icon="replaceStatusOutput.icon"
|
||||
size="16px"
|
||||
>
|
||||
{{ t(replaceStatusOutput.text) }}
|
||||
{{ t(replaceStatusOutput.text ?? 'Unknown') }}
|
||||
</UiBadge>
|
||||
|
||||
<BrandButton
|
||||
btn-style="underline"
|
||||
:external="true"
|
||||
:href="DOCS_REGISTRATION_REPLACE_KEY.toString()"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:text="t('Learn More')"
|
||||
class="text-14px"
|
||||
/>
|
||||
<span class="inline-flex flex-wrap items-center justify-end gap-8px">
|
||||
<BrandButton
|
||||
btn-style="underline"
|
||||
:external="true"
|
||||
:href="DOCS_REGISTRATION_REPLACE_KEY.toString()"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:text="t('Learn More')"
|
||||
class="text-14px"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
export interface Props {
|
||||
componentIs?: string;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -27,11 +28,11 @@ const output = computed(() => {
|
||||
}
|
||||
return {
|
||||
text: regUpdatesExpired.value
|
||||
? props.t('Ineligible for updates released after {0}', [outputDateTimeFormatted.value])
|
||||
: props.t('Eligible for updates until {0}', [outputDateTimeFormatted.value]),
|
||||
? props.t('Ineligible for feature updates released after {0}', [outputDateTimeFormatted.value])
|
||||
: props.t('Eligible for free feature updates until {0}', [outputDateTimeFormatted.value]),
|
||||
title: regUpdatesExpired.value
|
||||
? props.t('Ineligible as of {0}', [outputDateTimeReadableDiff.value])
|
||||
: props.t('Eligible for updates for {0}', [outputDateTimeReadableDiff.value]),
|
||||
: props.t('Eligible for free feature updates for {0}', [outputDateTimeReadableDiff.value]),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { DOCS_REGISTRATION_LICENSING } from '~/helpers/urls';
|
||||
@@ -8,7 +9,7 @@ import { useReplaceRenewStore } from '~/store/replaceRenew';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
export interface Props {
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = defineProps<Props>();
|
||||
@@ -39,11 +40,11 @@ const output = computed(() => {
|
||||
}
|
||||
return {
|
||||
text: regUpdatesExpired.value
|
||||
? props.t('Ineligible for updates released after {0}', [formattedRegExp.value])
|
||||
: props.t('Eligible for updates until {0}', [formattedRegExp.value]),
|
||||
? props.t('Ineligible for feature updates released after {0}', [formattedRegExp.value])
|
||||
: props.t('Eligible for free feature updates until {0}', [formattedRegExp.value]),
|
||||
title: regUpdatesExpired.value
|
||||
? props.t('Ineligible as of {0}', [readableDiffRegExp.value])
|
||||
: props.t('Eligible for updates for {0}', [readableDiffRegExp.value]),
|
||||
: props.t('Eligible for free feature updates for {0}', [readableDiffRegExp.value]),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -75,7 +76,7 @@ const output = computed(() => {
|
||||
:text="t('Extend License')"
|
||||
:title="t('Pay your annual fee to continue receiving OS updates.')"
|
||||
class="flex-grow"
|
||||
@click="renewAction.click()"
|
||||
@click="renewAction.click?.()"
|
||||
/>
|
||||
|
||||
<BrandButton
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowPathIcon, ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useAccountStore } from '~/store/account';
|
||||
import type { ButtonStyle } from '~/types/ui/button';
|
||||
|
||||
defineProps<{
|
||||
t: any;
|
||||
btnStyle?: ButtonStyle;
|
||||
t: ComposerTranslation;
|
||||
}>();
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
@@ -13,6 +16,7 @@ const accountStore = useAccountStore();
|
||||
<template>
|
||||
<div class="flex flex-col sm:flex-shrink-0 sm:flex-grow-0 items-center">
|
||||
<BrandButton
|
||||
:btn-style="btnStyle"
|
||||
:icon="ArrowPathIcon"
|
||||
:icon-right="ArrowTopRightOnSquareIcon"
|
||||
:text="t('Check for OS Updates')"
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { computed } from 'vue';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { usePurchaseStore } from '~/store/purchase';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
@@ -17,7 +18,7 @@ import { useUpdateOsChangelogStore } from '~/store/updateOsChangelog';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useAccountStore } from '~/store/account';
|
||||
@@ -20,7 +21,7 @@ import type { ButtonProps } from '~/types/ui/button';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -49,9 +50,20 @@ const {
|
||||
checkForUpdatesLoading,
|
||||
} = storeToRefs(updateOsStore);
|
||||
|
||||
const {
|
||||
outputDateTimeFormatted: formattedRegExp,
|
||||
} = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
|
||||
/**
|
||||
* regExp may not have a value until we get a response from the refreshServerState action
|
||||
* So we need to watch for this value to be able to format it based on the user's date time preferences.
|
||||
*/
|
||||
const formattedRegExp = ref<string>();
|
||||
const setFormattedRegExp = () => { // ran in watch on regExp and onBeforeMount
|
||||
if (!regExp.value) { return; }
|
||||
|
||||
const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
|
||||
formattedRegExp.value = outputDateTimeFormatted.value;
|
||||
};
|
||||
watch(regExp, (_newV) => {
|
||||
setFormattedRegExp();
|
||||
});
|
||||
|
||||
const ignoreThisRelease = ref(false);
|
||||
// if we had a release ignored and now we don't set ignoreThisRelease to false
|
||||
@@ -87,8 +99,8 @@ const modalCopy = computed((): ModalCopy | null => {
|
||||
|
||||
if (availableWithRenewal.value) {
|
||||
const description = regUpdatesExpired.value
|
||||
? props.t('Ineligible for updates released after {0}', [formattedRegExp.value])
|
||||
: props.t('Eligible for updates until {0}', [formattedRegExp.value]);
|
||||
? props.t('Ineligible for feature updates released after {0}', [formattedRegExp.value])
|
||||
: props.t('Eligible for free feature updates until {0}', [formattedRegExp.value]);
|
||||
return {
|
||||
title: props.t('Unraid OS {0} Released', [availableWithRenewal.value]),
|
||||
description: `<p>${formattedReleaseDate}</p><p>${description}</p>`,
|
||||
@@ -136,7 +148,8 @@ const actionButtons = computed((): ButtonProps[] | null => {
|
||||
const buttons: ButtonProps[] = [];
|
||||
|
||||
// update available but not stable branch - should link out to account update callback
|
||||
if (availableRequiresAuth.value) {
|
||||
// if availableWithRenewal.value is true, then we need to renew the license before we can update so don't show the verify button
|
||||
if (availableRequiresAuth.value && !availableWithRenewal.value) {
|
||||
buttons.push({
|
||||
click: async () => await accountStore.updateOs(),
|
||||
icon: IdentificationIcon,
|
||||
@@ -147,7 +160,7 @@ const actionButtons = computed((): ButtonProps[] | null => {
|
||||
}
|
||||
|
||||
// update available - open changelog to commence update
|
||||
if (available.value) {
|
||||
if (available.value && updateOsResponse.value?.changelog) {
|
||||
buttons.push({
|
||||
btnStyle: availableWithRenewal.value
|
||||
? 'outline'
|
||||
@@ -187,10 +200,10 @@ const close = () => {
|
||||
};
|
||||
|
||||
const renderMainSlot = computed(() => {
|
||||
return checkForUpdatesLoading.value || available.value || availableWithRenewal.value || extraLinks.value?.length > 0 || updateOsIgnoredReleases.value.length > 0;
|
||||
return !!(checkForUpdatesLoading.value || available.value || availableWithRenewal.value || extraLinks.value?.length > 0 || updateOsIgnoredReleases.value.length > 0);
|
||||
});
|
||||
|
||||
const userFormattedReleaseDate = ref<any>();
|
||||
const userFormattedReleaseDate = ref<string>();
|
||||
/**
|
||||
* availableReleaseDate may not have a value until we get a release in the update os check response.
|
||||
* So we need to watch for this value to be able to format it based on the user's date time preferences.
|
||||
@@ -208,6 +221,7 @@ onBeforeMount(() => {
|
||||
if (availableReleaseDate.value) {
|
||||
setUserFormattedReleaseDate();
|
||||
}
|
||||
setFormattedRegExp();
|
||||
});
|
||||
|
||||
const modalWidth = computed(() => {
|
||||
@@ -240,9 +254,9 @@ const modalWidth = computed(() => {
|
||||
:icon="item.icon"
|
||||
:icon-right="item.iconRight"
|
||||
:icon-right-hover-display="item.iconRightHoverDisplay"
|
||||
:text="t(item.text)"
|
||||
:text="t(item.text ?? '')"
|
||||
:title="item.title ? t(item.title) : undefined"
|
||||
@click="item.click ? item.click() : undefined"
|
||||
@click="item.click?.()"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -310,9 +324,9 @@ const modalWidth = computed(() => {
|
||||
:icon="item.icon"
|
||||
:icon-right="item.iconRight"
|
||||
:icon-right-hover-display="item.iconRightHoverDisplay"
|
||||
:text="t(item.text)"
|
||||
:text="t(item.text ?? '')"
|
||||
:title="item.title ? t(item.title) : undefined"
|
||||
@click="item.click ? item.click() : undefined"
|
||||
@click="item.click?.()"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -9,6 +9,7 @@ import {
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref } from 'vue';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
@@ -20,7 +21,7 @@ import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
releaseDate: string;
|
||||
version: string;
|
||||
}>();
|
||||
@@ -35,7 +36,7 @@ const {
|
||||
|
||||
const diagnosticsButton = ref<UserProfileLink | undefined>({
|
||||
click: () => {
|
||||
// @ts-ignore – global function provided by the webgui on the update page
|
||||
// @ts-expect-error – global function provided by the webgui on the update page
|
||||
downloadDiagnostics();
|
||||
},
|
||||
icon: FolderArrowDownIcon,
|
||||
@@ -45,7 +46,7 @@ const diagnosticsButton = ref<UserProfileLink | undefined>({
|
||||
|
||||
const downgradeButton = ref<UserProfileLink>({
|
||||
click: () => {
|
||||
// @ts-ignore – global function provided by the webgui on the update page
|
||||
// @ts-expect-error – global function provided by the webgui on the update page
|
||||
confirmDowngrade();
|
||||
},
|
||||
name: 'downgrade',
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { XMarkIcon } from '@heroicons/vue/24/solid';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useThemeStore } from '~/store/theme';
|
||||
|
||||
export interface Props {
|
||||
label: string;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
<script setup lang="ts">
|
||||
import { useServerStore } from '~/store/server';
|
||||
const serverStore = useServerStore();
|
||||
const { updateOsIgnoredReleases } = storeToRefs(serverStore);
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<UpdateOsIgnoredRelease
|
||||
v-for="ignoredRelease in updateOsIgnoredReleases"
|
||||
:key="ignoredRelease"
|
||||
:label="ignoredRelease"
|
||||
/>
|
||||
</template>
|
||||
@@ -1,6 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
ArrowPathIcon,
|
||||
ArrowTopRightOnSquareIcon,
|
||||
BellAlertIcon,
|
||||
CheckCircleIcon,
|
||||
ExclamationTriangleIcon,
|
||||
@@ -11,28 +12,32 @@ import { storeToRefs } from 'pinia';
|
||||
|
||||
import { WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useAccountStore } from '~/store/account';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { ButtonProps } from '~/types/ui/button';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import BrandLoadingWhite from '~/components/Brand/LoadingWhite.vue';
|
||||
|
||||
export interface Props {
|
||||
downgradeNotAvailable?: boolean;
|
||||
restoreVersion?: string | undefined;
|
||||
showUpdateCheck?: boolean;
|
||||
t: any;
|
||||
showExternalDowngrade?: boolean;
|
||||
t: ComposerTranslation;
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
}
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
downgradeNotAvailable: false,
|
||||
restoreVersion: undefined,
|
||||
showUpdateCheck: false,
|
||||
showExternalDowngrade: false,
|
||||
title: undefined,
|
||||
subtitle: undefined,
|
||||
});
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const serverStore = useServerStore();
|
||||
const updateOsStore = useUpdateOsStore();
|
||||
const updateOsActionsStore = useUpdateOsActionsStore();
|
||||
@@ -41,6 +46,8 @@ const { dateTimeFormat, osVersion, rebootType, rebootVersion, regExp, regUpdates
|
||||
const { available, availableWithRenewal } = storeToRefs(updateOsStore);
|
||||
const { ineligibleText, rebootTypeText, status } = storeToRefs(updateOsActionsStore);
|
||||
|
||||
const updateAvailable = computed(() => available.value || availableWithRenewal.value);
|
||||
|
||||
const {
|
||||
outputDateTimeReadableDiff: readableDiffRegExp,
|
||||
outputDateTimeFormatted: formattedRegExp,
|
||||
@@ -52,11 +59,50 @@ const regExpOutput = computed(() => {
|
||||
}
|
||||
return {
|
||||
text: regUpdatesExpired.value
|
||||
? props.t('Ineligible for updates released after {0}', [formattedRegExp.value])
|
||||
: props.t('Eligible for updates until {0}', [formattedRegExp.value]),
|
||||
? props.t('Ineligible for feature updates released after {0}', [formattedRegExp.value])
|
||||
: props.t('Eligible for free feature updates until {0}', [formattedRegExp.value]),
|
||||
title: regUpdatesExpired.value
|
||||
? props.t('Ineligible as of {0}', [readableDiffRegExp.value])
|
||||
: props.t('Eligible for updates for {0}', [readableDiffRegExp.value]),
|
||||
: props.t('Eligible for free feature updates for {0}', [readableDiffRegExp.value]),
|
||||
};
|
||||
});
|
||||
|
||||
const showRebootButton = computed(() => rebootType.value === 'downgrade' || rebootType.value === 'update');
|
||||
|
||||
const checkButton = computed((): ButtonProps => {
|
||||
if (showRebootButton.value || props.showExternalDowngrade) {
|
||||
return {
|
||||
btnStyle: 'outline',
|
||||
click: () => {
|
||||
props.showExternalDowngrade
|
||||
? accountStore.downgradeOs()
|
||||
: accountStore.updateOs();
|
||||
},
|
||||
icon: ArrowTopRightOnSquareIcon,
|
||||
text: props.t('More options'),
|
||||
};
|
||||
}
|
||||
|
||||
if (!updateAvailable.value) {
|
||||
return {
|
||||
btnStyle: 'outline',
|
||||
click: () => {
|
||||
updateOsStore.localCheckForUpdate();
|
||||
},
|
||||
icon: ArrowPathIcon,
|
||||
text: props.t('Check for Update'),
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
btnStyle: 'fill',
|
||||
click: () => {
|
||||
updateOsStore.setModalOpen(true);
|
||||
},
|
||||
icon: BellAlertIcon,
|
||||
text: availableWithRenewal.value
|
||||
? props.t('Unraid OS {0} Released', [availableWithRenewal.value])
|
||||
: props.t('Unraid OS {0} Update Available', [available.value]),
|
||||
};
|
||||
});
|
||||
</script>
|
||||
@@ -117,8 +163,8 @@ const regExpOutput = computed(() => {
|
||||
<template v-else>
|
||||
<UiBadge
|
||||
v-if="rebootType === ''"
|
||||
:color="available || availableWithRenewal ? 'orange' : 'green'"
|
||||
:icon="available || availableWithRenewal ? BellAlertIcon : CheckCircleIcon"
|
||||
:color="updateAvailable ? 'orange' : 'green'"
|
||||
:icon="updateAvailable ? BellAlertIcon : CheckCircleIcon"
|
||||
>
|
||||
{{ (available
|
||||
? t('Unraid {0} Available', [available])
|
||||
@@ -145,17 +191,33 @@ const regExpOutput = computed(() => {
|
||||
</UiBadge>
|
||||
</div>
|
||||
|
||||
<div class="shrink-0">
|
||||
<UpdateOsCallbackButton
|
||||
v-if="showUpdateCheck && rebootType === ''"
|
||||
:t="t"
|
||||
/>
|
||||
<BrandButton
|
||||
v-else-if="rebootType === 'downgrade' || rebootType === 'update'"
|
||||
:icon="ArrowPathIcon"
|
||||
:text="rebootType === 'downgrade' ? t('Reboot Now to Downgrade to {0}', [rebootVersion]) : t('Reboot Now to Update to {0}', [rebootVersion])"
|
||||
@click="updateOsActionsStore.rebootServer()"
|
||||
/>
|
||||
<div class="inline-flex flex-col flex-shrink-0 gap-16px flex-grow items-center md:items-end">
|
||||
<span v-if="showRebootButton">
|
||||
<BrandButton
|
||||
btn-style="fill"
|
||||
:icon="ArrowPathIcon"
|
||||
:text="rebootType === 'downgrade' ? t('Reboot Now to Downgrade to {0}', [rebootVersion]) : t('Reboot Now to Update to {0}', [rebootVersion])"
|
||||
@click="updateOsActionsStore.rebootServer()"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span>
|
||||
<BrandButton
|
||||
:btn-style="checkButton.btnStyle"
|
||||
:icon="checkButton.icon"
|
||||
:text="checkButton.text"
|
||||
@click="checkButton.click"
|
||||
/>
|
||||
</span>
|
||||
|
||||
<span v-if="rebootType !== ''">
|
||||
<BrandButton
|
||||
btn-style="outline"
|
||||
:icon="XCircleIcon"
|
||||
:text="t('Cancel {0}', [rebootType === 'downgrade' ? t('Downgrade') : t('Update')])"
|
||||
@click="updateOsStore.cancelUpdate()"
|
||||
/>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { ExclamationTriangleIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
|
||||
defineProps<{
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}>();
|
||||
|
||||
const { rebootTypeText } = storeToRefs(useUpdateOsActionsStore());
|
||||
|
||||
@@ -15,6 +15,7 @@ import {
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
@@ -26,7 +27,7 @@ import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}>();
|
||||
|
||||
const serverStore = useServerStore();
|
||||
@@ -82,10 +83,10 @@ const flashBackupBasicStatus = ref<'complete' | 'ready' | 'started'>('ready');
|
||||
const flashBackupText = computed(() => props.t('Create Flash Backup'));
|
||||
const startFlashBackup = () => {
|
||||
console.debug('[startFlashBackup]', Date.now());
|
||||
// @ts-ignore – global function provided by the webgui on the update page
|
||||
// @ts-expect-error – global function provided by the webgui on the update page
|
||||
if (typeof flashBackup === 'function') {
|
||||
flashBackupBasicStatus.value = 'started';
|
||||
// @ts-ignore – global function provided by the webgui on the update page
|
||||
// @ts-expect-error – global function provided by the webgui on the update page
|
||||
flashBackup();
|
||||
checkFlashBackupStatus();
|
||||
} else {
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
import dayjs from 'dayjs';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { ref, watchEffect } from 'vue';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
@@ -18,7 +19,7 @@ import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}>();
|
||||
|
||||
const serverStore = useServerStore();
|
||||
@@ -94,7 +95,7 @@ watchEffect(() => {
|
||||
:text="t('Extend License')"
|
||||
:title="t('Pay your annual fee to continue receiving OS updates.')"
|
||||
class="flex-grow"
|
||||
@click="renewAction.click()"
|
||||
@click="renewAction.click?.()"
|
||||
/>
|
||||
<!-- <BrandButton
|
||||
btn-style="black"
|
||||
|
||||
@@ -11,6 +11,7 @@ import {
|
||||
XMarkIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
import 'tailwindcss/tailwind.css';
|
||||
import '~/assets/main.css';
|
||||
import { WEBGUI_CONNECT_SETTINGS, WEBGUI_TOOLS_REGISTRATION } from '~/helpers/urls';
|
||||
@@ -23,7 +24,7 @@ import { useUpdateOsActionsStore } from '~/store/updateOsActions';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -62,6 +63,7 @@ const {
|
||||
} = storeToRefs(serverStore);
|
||||
const {
|
||||
status: updateOsStatus,
|
||||
callbackTypeDowngrade,
|
||||
callbackUpdateRelease,
|
||||
} = storeToRefs(updateOsActionStore);
|
||||
/**
|
||||
@@ -78,7 +80,7 @@ const isSettingsPage = ref<boolean>(document.location.pathname === '/Settings/Ma
|
||||
|
||||
const heading = computed(() => {
|
||||
if (updateOsStatus.value === 'confirming') {
|
||||
return props.t('Update Unraid OS confirmation required');
|
||||
return callbackTypeDowngrade.value ? props.t('Downgrade Unraid OS confirmation required') : props.t('Update Unraid OS confirmation required');
|
||||
}
|
||||
switch (callbackStatus.value) {
|
||||
case 'error':
|
||||
@@ -88,10 +90,11 @@ const heading = computed(() => {
|
||||
case 'success':
|
||||
return props.t('Success!');
|
||||
}
|
||||
return '';
|
||||
});
|
||||
const subheading = computed(() => {
|
||||
if (updateOsStatus.value === 'confirming') {
|
||||
return props.t('Please confirm the update details below');
|
||||
return callbackTypeDowngrade.value ? props.t('Please confirm the downgrade details below') : props.t('Please confirm the update details below');
|
||||
}
|
||||
if (callbackStatus.value === 'error') {
|
||||
return props.t('Something went wrong'); /** @todo show actual error messages */
|
||||
@@ -102,7 +105,7 @@ const subheading = computed(() => {
|
||||
if (keyActionType.value === 'purchase') { return props.t('Thank you for purchasing an Unraid {0} Key!', [keyType.value]); }
|
||||
if (keyActionType.value === 'replace') { return props.t('Your {0} Key has been replaced!', [keyType.value]); }
|
||||
if (keyActionType.value === 'trialExtend') { return props.t('Your Trial key has been extended!'); }
|
||||
if (keyActionType.value === 'trialStart') { return props.t('Your free Trial key provides all the functionality of a Pro Registration key'); }
|
||||
if (keyActionType.value === 'trialStart') { return props.t('Your free Trial key provides all the functionality of an Unleashed Registration key'); }
|
||||
if (keyActionType.value === 'upgrade') { return props.t('Thank you for upgrading to an Unraid {0} Key!', [keyType.value]); }
|
||||
return '';
|
||||
}
|
||||
@@ -139,10 +142,6 @@ const keyInstallStatusCopy = computed((): { text: string; } => {
|
||||
let txt2 = props.t('Installed');
|
||||
let txt3 = props.t('Install');
|
||||
switch (keyInstallStatus.value) {
|
||||
case 'ready':
|
||||
return {
|
||||
text: props.t('Ready to Install Key'),
|
||||
};
|
||||
case 'installing':
|
||||
if (keyActionType.value === 'trialExtend') { txt1 = props.t('Installing Extended Trial'); }
|
||||
if (keyActionType.value === 'recover') { txt1 = props.t('Installing Recovered'); }
|
||||
@@ -165,15 +164,16 @@ const keyInstallStatusCopy = computed((): { text: string; } => {
|
||||
return {
|
||||
text: props.t('Failed to {0} {1} Key', [txt3, keyType.value]),
|
||||
};
|
||||
case 'ready':
|
||||
default:
|
||||
return {
|
||||
text: props.t('Ready to Install Key'),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
const accountActionStatusCopy = computed((): { text: string; } => {
|
||||
switch (accountActionStatus.value) {
|
||||
case 'ready':
|
||||
return {
|
||||
text: props.t('Ready to update Connect account configuration'),
|
||||
};
|
||||
case 'waiting':
|
||||
return {
|
||||
text: accountAction.value?.type === 'signIn'
|
||||
@@ -198,6 +198,11 @@ const accountActionStatusCopy = computed((): { text: string; } => {
|
||||
? props.t('Sign In Failed')
|
||||
: props.t('Sign Out Failed'),
|
||||
};
|
||||
case 'ready':
|
||||
default:
|
||||
return {
|
||||
text: props.t('Ready to update Connect account configuration'),
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
@@ -317,7 +322,7 @@ const showUpdateEligibility = computed(() => {
|
||||
</p>
|
||||
|
||||
<p class="text-14px italic opacity-75">
|
||||
{{ t('This update will require a reboot') }}
|
||||
{{ callbackTypeDowngrade ? t('This downgrade will require a reboot') : t('This update will require a reboot') }}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
@@ -367,7 +372,7 @@ const showUpdateEligibility = computed(() => {
|
||||
/>
|
||||
<BrandButton
|
||||
:icon="CheckIcon"
|
||||
:text="t('Confirm and start update')"
|
||||
:text="callbackTypeDowngrade ? t('Confirm and start downgrade') : t('Confirm and start update')"
|
||||
@click="confirmUpdateOs"
|
||||
/>
|
||||
</template>
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
import { TransitionRoot } from '@headlessui/vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useDropdownStore } from '~/store/dropdown';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
defineProps<{ t: any; }>();
|
||||
defineProps<{ t: ComposerTranslation; }>();
|
||||
|
||||
const dropdownStore = useDropdownStore();
|
||||
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
<script setup lang="ts">
|
||||
import { ExclamationTriangleIcon, CheckCircleIcon, UserCircleIcon } from '@heroicons/vue/24/solid';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import BrandLoading from '~/components/Brand/Loading.vue';
|
||||
import { useUnraidApiStore } from '~/store/unraidApi';
|
||||
import { useServerStore } from '~/store/server';
|
||||
|
||||
const props = defineProps<{ t: any; }>();
|
||||
const props = defineProps<{ t: ComposerTranslation; }>();
|
||||
|
||||
const { username } = storeToRefs(useServerStore());
|
||||
|
||||
|
||||
@@ -5,14 +5,18 @@ import {
|
||||
ArrowTopRightOnSquareIcon,
|
||||
BellAlertIcon,
|
||||
CogIcon,
|
||||
ExclamationTriangleIcon,
|
||||
KeyIcon,
|
||||
UserIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import {
|
||||
CONNECT_DASHBOARD,
|
||||
WEBGUI_CONNECT_SETTINGS,
|
||||
WEBGUI_TOOLS_REGISTRATION,
|
||||
WEBGUI_TOOLS_DOWNGRADE,
|
||||
WEBGUI_TOOLS_UPDATE,
|
||||
} from '~/helpers/urls';
|
||||
import { useAccountStore } from '~/store/account';
|
||||
import { useErrorsStore } from '~/store/errors';
|
||||
@@ -20,7 +24,7 @@ import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
const props = defineProps<{ t: any; }>();
|
||||
const props = defineProps<{ t: ComposerTranslation; }>();
|
||||
|
||||
const accountStore = useAccountStore();
|
||||
const errorsStore = useErrorsStore();
|
||||
@@ -31,7 +35,6 @@ const {
|
||||
keyActions,
|
||||
connectPluginInstalled,
|
||||
rebootType,
|
||||
rebootVersion,
|
||||
registered,
|
||||
regUpdatesExpired,
|
||||
stateData,
|
||||
@@ -50,7 +53,7 @@ const signOutAction = computed(() => stateData.value.actions?.filter((act: { nam
|
||||
*/
|
||||
const filteredKeyActions = computed(() => keyActions.value?.filter(action => !['renew'].includes(action.name)));
|
||||
|
||||
const manageUnraidNetAccount = computed(() => {
|
||||
const manageUnraidNetAccount = computed((): UserProfileLink => {
|
||||
return {
|
||||
external: true,
|
||||
click: () => { accountStore.manage(); },
|
||||
@@ -60,7 +63,7 @@ const manageUnraidNetAccount = computed(() => {
|
||||
};
|
||||
});
|
||||
|
||||
const updateOsCheckForUpdatesButton = computed(() => {
|
||||
const updateOsCheckForUpdatesButton = computed((): UserProfileLink => {
|
||||
return {
|
||||
click: () => {
|
||||
updateOsStore.localCheckForUpdate();
|
||||
@@ -69,7 +72,7 @@ const updateOsCheckForUpdatesButton = computed(() => {
|
||||
text: props.t('Check for Update'),
|
||||
};
|
||||
});
|
||||
const updateOsResponseModalOpenButton = computed(() => {
|
||||
const updateOsResponseModalOpenButton = computed((): UserProfileLink => {
|
||||
return {
|
||||
click: () => {
|
||||
updateOsStore.setModalOpen(true);
|
||||
@@ -81,25 +84,31 @@ const updateOsResponseModalOpenButton = computed(() => {
|
||||
: props.t('Unraid OS {0} Update Available', [osUpdateAvailable.value]),
|
||||
};
|
||||
});
|
||||
const updateOsToolsUpdatePageButton = computed(() => {
|
||||
const rebootDetectedButton = computed((): UserProfileLink => {
|
||||
return {
|
||||
external: true,
|
||||
href: WEBGUI_TOOLS_REGISTRATION.toString(),
|
||||
icon: KeyIcon,
|
||||
href: rebootType.value === 'downgrade'
|
||||
? WEBGUI_TOOLS_DOWNGRADE.toString()
|
||||
: WEBGUI_TOOLS_UPDATE.toString(),
|
||||
icon: ExclamationTriangleIcon,
|
||||
text: rebootType.value === 'downgrade'
|
||||
? props.t('Reboot Now to Downgrade to {0}', [rebootVersion.value])
|
||||
: props.t('Reboot Now to Update to {0}', [rebootVersion.value]),
|
||||
? props.t('Reboot Required for Downgrade')
|
||||
: props.t('Reboot Required for Update'),
|
||||
};
|
||||
});
|
||||
|
||||
const updateOsButton = computed(() => {
|
||||
const updateOsButton = computed((): UserProfileLink[] => {
|
||||
const btns = [];
|
||||
if (rebootType.value === 'downgrade' || rebootType.value === 'update') {
|
||||
return updateOsToolsUpdatePageButton.value;
|
||||
btns.push(rebootDetectedButton.value);
|
||||
return btns;
|
||||
}
|
||||
|
||||
if (osUpdateAvailable.value) {
|
||||
return updateOsResponseModalOpenButton.value;
|
||||
btns.push(updateOsResponseModalOpenButton.value);
|
||||
} else {
|
||||
btns.push(updateOsCheckForUpdatesButton.value);
|
||||
}
|
||||
return updateOsCheckForUpdatesButton.value;
|
||||
return btns;
|
||||
});
|
||||
|
||||
const links = computed(():UserProfileLink[] => {
|
||||
@@ -114,7 +123,7 @@ const links = computed(():UserProfileLink[] => {
|
||||
: []),
|
||||
|
||||
// ensure we only show the update button when we don't have an error
|
||||
...(!stateDataError.value ? [updateOsButton.value] : []),
|
||||
...(!stateDataError.value ? [...updateOsButton.value] : []),
|
||||
|
||||
// connect plugin links
|
||||
...(registered.value && connectPluginInstalled.value
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script setup lang="ts">
|
||||
// eslint-disable vue/no-v-html
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useErrorsStore } from '~/store/errors';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
defineProps<{ t: any; }>();
|
||||
defineProps<{ t: ComposerTranslation; }>();
|
||||
|
||||
const errorsStore = useErrorsStore();
|
||||
const { errors } = storeToRefs(errorsStore);
|
||||
@@ -19,7 +21,7 @@ const { errors } = storeToRefs(errorsStore);
|
||||
<div class="text-14px px-12px flex flex-col gap-y-8px" :class="{ 'pb-8px': !error.actions }" v-html="t(error.message)" />
|
||||
<nav v-if="error.actions">
|
||||
<li v-for="(link, idx) in error.actions" :key="`link_${idx}`">
|
||||
<UpcDropdownItem :item="link" :rounded="false" :t="t" />
|
||||
<UpcDropdownItem :item="link as UserProfileLink" :rounded="false" :t="t" />
|
||||
</li>
|
||||
</nav>
|
||||
</li>
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
<script setup lang="ts">
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import type { ServerStateDataAction } from '~/types/server';
|
||||
import type { UserProfileLink } from '~/types/userProfile';
|
||||
|
||||
export interface Props {
|
||||
item: ServerStateDataAction | UserProfileLink;
|
||||
rounded?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -32,11 +34,11 @@ const showExternalIconOnHover = computed(() => props.item?.external && props.ite
|
||||
'rounded-md': rounded,
|
||||
'disabled:opacity-50 disabled:hover:opacity-50 disabled:focus:opacity-50 disabled:cursor-not-allowed': item?.disabled,
|
||||
}"
|
||||
@click.stop="item?.click ? item?.click(item?.clickParams) : null"
|
||||
@click.stop="item?.click ? item?.click(item?.clickParams ?? []) : null"
|
||||
>
|
||||
<span class="leading-snug inline-flex flex-row items-center gap-x-8px">
|
||||
<component :is="item?.icon" class="flex-shrink-0 text-current w-16px h-16px" aria-hidden="true" />
|
||||
{{ t(item?.text, item?.textParams) }}
|
||||
{{ t(item?.text, item?.textParams ?? []) }}
|
||||
</span>
|
||||
<ArrowTopRightOnSquareIcon
|
||||
v-if="showExternalIconOnHover"
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUnraidApiStore } from '~/store/unraidApi';
|
||||
|
||||
@@ -8,7 +10,7 @@ import '~/assets/main.css';
|
||||
|
||||
import BrandLoadingWhite from '~/components/Brand/LoadingWhite.vue';
|
||||
|
||||
defineProps<{ t: any; }>();
|
||||
defineProps<{ t: ComposerTranslation; }>();
|
||||
|
||||
const { expireTime, connectPluginInstalled, state, stateData } = storeToRefs(useServerStore());
|
||||
const { unraidApiStatus, unraidApiRestartAction } = storeToRefs(useUnraidApiStore());
|
||||
@@ -20,7 +22,10 @@ const showExpireTime = computed(() => (state.value === 'TRIAL' || state.value ==
|
||||
<div class="flex flex-col gap-y-24px w-full min-w-300px md:min-w-[500px] max-w-xl p-16px">
|
||||
<header>
|
||||
<h2 class="text-24px text-center font-semibold" v-html="t(stateData.heading)" />
|
||||
<div class="flex flex-col gap-y-8px" v-html="t(stateData.message)" />
|
||||
<div
|
||||
class="text-center prose text-16px leading-relaxed whitespace-normal opacity-75 gap-y-8px"
|
||||
v-html="t(stateData.message)"
|
||||
/>
|
||||
<UpcUptimeExpire
|
||||
v-if="showExpireTime"
|
||||
class="text-center opacity-75 mt-12px"
|
||||
@@ -36,7 +41,7 @@ const showExpireTime = computed(() => (state.value === 'TRIAL' || state.value ==
|
||||
:icon="unraidApiStatus === 'restarting' ? BrandLoadingWhite : unraidApiRestartAction?.icon"
|
||||
:text="unraidApiStatus === 'restarting' ? t('Restarting unraid-api…') : t('Restart unraid-api')"
|
||||
:title="unraidApiStatus === 'restarting' ? t('Restarting unraid-api…') : t('Restart unraid-api')"
|
||||
@click="unraidApiRestartAction?.click()"
|
||||
@click="unraidApiRestartAction?.click?.()"
|
||||
/>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -8,13 +8,14 @@ import {
|
||||
InformationCircleIcon,
|
||||
ShieldExclamationIcon,
|
||||
} from '@heroicons/vue/24/solid';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useDropdownStore } from '~/store/dropdown';
|
||||
import { useErrorsStore } from '~/store/errors';
|
||||
import { useServerStore } from '~/store/server';
|
||||
import { useUpdateOsStore } from '~/store/updateOs';
|
||||
|
||||
const props = defineProps<{ t: any; }>();
|
||||
const props = defineProps<{ t: ComposerTranslation; }>();
|
||||
|
||||
const dropdownStore = useDropdownStore();
|
||||
const { dropdownVisible } = storeToRefs(dropdownStore);
|
||||
@@ -24,9 +25,10 @@ const { available: osUpdateAvailable } = storeToRefs(useUpdateOsStore());
|
||||
|
||||
const showErrorIcon = computed(() => errors.value.length || stateData.value.error);
|
||||
|
||||
const text = computed((): string | undefined => {
|
||||
const text = computed((): string => {
|
||||
if ((stateData.value.error) && state.value !== 'EEXPIRED') { return props.t('Fix Error'); }
|
||||
if (!registered.value && connectPluginInstalled.value) { return props.t('Sign In'); }
|
||||
return '';
|
||||
});
|
||||
|
||||
const title = computed((): string => {
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
*/
|
||||
import { Switch, SwitchGroup, SwitchLabel } from '@headlessui/vue';
|
||||
import { ArrowTopRightOnSquareIcon } from '@heroicons/vue/24/solid';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useInstallPlugin from '~/composables/installPlugin';
|
||||
import { CONNECT_DOCS } from '~/helpers/urls';
|
||||
@@ -14,7 +15,7 @@ import '~/assets/main.css';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useServerStore } from '~/store/server';
|
||||
import type { ServerStateDataAction } from '~/types/server';
|
||||
|
||||
defineProps<{ t: any; }>();
|
||||
defineProps<{ t: ComposerTranslation; }>();
|
||||
|
||||
const { state, stateData } = storeToRefs(useServerStore());
|
||||
|
||||
@@ -22,7 +23,7 @@ const upgradeAction = computed((): ServerStateDataAction | undefined => {
|
||||
<UpcServerStateBuy
|
||||
class="text-gamma"
|
||||
:title="t('Upgrade Key')"
|
||||
@click="upgradeAction.click()"
|
||||
@click="upgradeAction.click?.()"
|
||||
>
|
||||
<h5>Unraid OS <em><strong>{{ t(stateData.humanReadable) }}</strong></em></h5>
|
||||
</UpcServerStateBuy>
|
||||
@@ -35,7 +36,7 @@ const upgradeAction = computed((): ServerStateDataAction | undefined => {
|
||||
<UpcServerStateBuy
|
||||
class="text-orange-dark relative top-[1px] hidden sm:block"
|
||||
:title="t('Purchase Key')"
|
||||
@click="purchaseAction.click()"
|
||||
@click="purchaseAction.click?.()"
|
||||
>{{ t('Purchase') }}</UpcServerStateBuy>
|
||||
</template>
|
||||
</span>
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import { useTrialStore } from '~/store/trial';
|
||||
|
||||
export interface Props {
|
||||
open?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
@@ -41,6 +43,7 @@ const trialStatusCopy = computed((): TrialStatusCopy | null => {
|
||||
subheading: props.t('Please wait while the page reloads to install your trial key'),
|
||||
};
|
||||
case 'ready':
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script setup lang="ts">
|
||||
import { storeToRefs } from 'pinia';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import useDateTimeHelper from '~/composables/dateTime';
|
||||
import { useServerStore } from '~/store/server';
|
||||
@@ -7,7 +8,7 @@ import { useServerStore } from '~/store/server';
|
||||
export interface Props {
|
||||
forExpire?: boolean;
|
||||
shortText?: boolean;
|
||||
t: any;
|
||||
t: ComposerTranslation;
|
||||
}
|
||||
|
||||
const props = withDefaults(defineProps<Props>(), {
|
||||
|
||||
@@ -18,12 +18,13 @@ const { t } = useI18n();
|
||||
const { isRemoteAccess } = storeToRefs(useServerStore());
|
||||
|
||||
const wanIp = ref<string | null>();
|
||||
const fetchError = ref<any>();
|
||||
const fetchError = ref<string>('');
|
||||
const loading = ref(false);
|
||||
|
||||
const computedError = computed(() => {
|
||||
const computedError = computed((): string => {
|
||||
if (!props.phpWanIp) { return t('DNS issue, unable to resolve wanip4.unraid.net'); }
|
||||
if (fetchError.value) { return fetchError.value; }
|
||||
return '';
|
||||
});
|
||||
|
||||
onBeforeMount(() => {
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
import dayjs, { extend } from 'dayjs';
|
||||
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
||||
import type { ComposerTranslation } from 'vue-i18n';
|
||||
|
||||
import type { DateFormatOption, ServerDateTimeFormat, TimeFormatOption } from '~/types/server';
|
||||
|
||||
/** @see https://day.js.org/docs/en/display/format#localized-formats */
|
||||
@@ -33,6 +35,22 @@ const timeFormatOptions: TimeFormatOption[] = [
|
||||
];
|
||||
|
||||
/**
|
||||
* the provided ref may not have a value until we get a response from the refreshServerState action
|
||||
* So we need to watch for this value to be able to format it based on the user's date time preferences.
|
||||
* @example below is how to use this composable
|
||||
* const formattedRegExp = ref<any>();
|
||||
* const setFormattedRegExp = () => { // ran in watch on regExp and onBeforeMount
|
||||
* if (!regExp.value) { return; }
|
||||
* const { outputDateTimeFormatted } = useDateTimeHelper(dateTimeFormat.value, props.t, true, regExp.value);
|
||||
* formattedRegExp.value = outputDateTimeFormatted.value;
|
||||
* };
|
||||
* watch(regExp, (_newV) => {
|
||||
* setFormattedRegExp();
|
||||
* });
|
||||
* onBeforeMount(() => {
|
||||
* setFormattedRegExp();
|
||||
* });
|
||||
*
|
||||
* @param format provided by Unraid server's state.php and set in the server store
|
||||
* @param t translations
|
||||
* @param hideMinutesSeconds true will hide minutes and seconds from the output
|
||||
@@ -41,7 +59,7 @@ const timeFormatOptions: TimeFormatOption[] = [
|
||||
*/
|
||||
const useDateTimeHelper = (
|
||||
format: ServerDateTimeFormat | undefined,
|
||||
t: any,
|
||||
t: ComposerTranslation,
|
||||
hideMinutesSeconds?: boolean,
|
||||
providedDateTime?: number | undefined,
|
||||
diffCountUp?: boolean,
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -17,6 +17,10 @@ const documents = {
|
||||
"\n mutation SignOut {\n connectSignOut\n }\n": types.SignOutDocument,
|
||||
"\n fragment PartialCloud on Cloud {\n error\n apiKey {\n valid\n error\n }\n cloud {\n status\n error\n }\n minigraphql {\n status\n error\n }\n relay {\n status\n error\n }\n }\n": types.PartialCloudFragmentDoc,
|
||||
"\n query serverState {\n cloud {\n ...PartialCloud\n }\n config {\n error\n valid\n }\n info {\n os {\n hostname\n }\n }\n owner {\n avatar\n username\n }\n registration {\n state\n expiration\n keyFile {\n contents\n }\n updateExpiration\n }\n vars {\n regGen\n regState\n configError\n configValid\n }\n }\n": types.serverStateDocument,
|
||||
"\n query getExtraAllowedOrigins {\n extraAllowedOrigins\n }\n": types.getExtraAllowedOriginsDocument,
|
||||
"\n query getRemoteAccess {\n remoteAccess {\n accessType\n forwardType\n port\n }\n }\n": types.getRemoteAccessDocument,
|
||||
"\n mutation setAdditionalAllowedOrigins($input: AllowedOriginInput!) {\n setAdditionalAllowedOrigins(input: $input)\n }\n": types.setAdditionalAllowedOriginsDocument,
|
||||
"\n mutation setupRemoteAccess($input: SetupRemoteAccessInput!) {\n setupRemoteAccess(input: $input)\n }\n": types.setupRemoteAccessDocument,
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -49,6 +53,22 @@ export function graphql(source: "\n fragment PartialCloud on Cloud {\n error
|
||||
* The graphql function is used to parse GraphQL queries into a document that can be used by GraphQL clients.
|
||||
*/
|
||||
export function graphql(source: "\n query serverState {\n cloud {\n ...PartialCloud\n }\n config {\n error\n valid\n }\n info {\n os {\n hostname\n }\n }\n owner {\n avatar\n username\n }\n registration {\n state\n expiration\n keyFile {\n contents\n }\n updateExpiration\n }\n vars {\n regGen\n regState\n configError\n configValid\n }\n }\n"): (typeof documents)["\n query serverState {\n cloud {\n ...PartialCloud\n }\n config {\n error\n valid\n }\n info {\n os {\n hostname\n }\n }\n owner {\n avatar\n username\n }\n registration {\n state\n expiration\n keyFile {\n contents\n }\n updateExpiration\n }\n vars {\n regGen\n regState\n configError\n configValid\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 query getExtraAllowedOrigins {\n extraAllowedOrigins\n }\n"): (typeof documents)["\n query getExtraAllowedOrigins {\n extraAllowedOrigins\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 query getRemoteAccess {\n remoteAccess {\n accessType\n forwardType\n port\n }\n }\n"): (typeof documents)["\n query getRemoteAccess {\n remoteAccess {\n accessType\n forwardType\n port\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 mutation setAdditionalAllowedOrigins($input: AllowedOriginInput!) {\n setAdditionalAllowedOrigins(input: $input)\n }\n"): (typeof documents)["\n mutation setAdditionalAllowedOrigins($input: AllowedOriginInput!) {\n setAdditionalAllowedOrigins(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 mutation setupRemoteAccess($input: SetupRemoteAccessInput!) {\n setupRemoteAccess(input: $input)\n }\n"): (typeof documents)["\n mutation setupRemoteAccess($input: SetupRemoteAccessInput!) {\n setupRemoteAccess(input: $input)\n }\n"];
|
||||
|
||||
export function graphql(source: string) {
|
||||
return (documents as any)[source] ?? {};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/* eslint-disable */
|
||||
|
||||
import type { TypedDocumentNode as DocumentNode } from '@graphql-typed-document-node/core';
|
||||
export type Maybe<T> = T | null;
|
||||
export type InputMaybe<T> = Maybe<T>;
|
||||
@@ -242,6 +242,7 @@ export type Config = {
|
||||
};
|
||||
|
||||
export enum ConfigErrorState {
|
||||
Ineligible = 'INELIGIBLE',
|
||||
Invalid = 'INVALID',
|
||||
NoKeyServer = 'NO_KEY_SERVER',
|
||||
UnknownError = 'UNKNOWN_ERROR',
|
||||
@@ -877,6 +878,7 @@ export type Query = {
|
||||
dockerNetwork: DockerNetwork;
|
||||
/** All Docker networks */
|
||||
dockerNetworks: Array<Maybe<DockerNetwork>>;
|
||||
extraAllowedOrigins: Array<Scalars['String']['output']>;
|
||||
flash?: Maybe<Flash>;
|
||||
info?: Maybe<Info>;
|
||||
/** Current user account */
|
||||
@@ -886,6 +888,7 @@ export type Query = {
|
||||
owner?: Maybe<Owner>;
|
||||
parityHistory?: Maybe<Array<Maybe<ParityCheck>>>;
|
||||
registration?: Maybe<Registration>;
|
||||
remoteAccess: RemoteAccess;
|
||||
server?: Maybe<Server>;
|
||||
servers: Array<Server>;
|
||||
/** Network Shares */
|
||||
@@ -993,6 +996,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'];
|
||||
@@ -1522,7 +1532,35 @@ export type serverStateQuery = { __typename?: 'Query', cloud?: (
|
||||
& { ' $fragmentRefs'?: { 'PartialCloudFragment': PartialCloudFragment } }
|
||||
) | null, config: { __typename?: 'Config', error?: ConfigErrorState | null, valid?: boolean | null }, info?: { __typename?: 'Info', os?: { __typename?: 'Os', hostname?: string | null } | null } | null, owner?: { __typename?: 'Owner', avatar?: string | null, username?: string | null } | null, registration?: { __typename?: 'Registration', state?: RegistrationState | null, expiration?: string | null, updateExpiration?: string | null, keyFile?: { __typename?: 'KeyFile', contents?: string | null } | null } | null, vars?: { __typename?: 'Vars', regGen?: string | null, regState?: RegistrationState | null, configError?: ConfigErrorState | null, configValid?: boolean | null } | null };
|
||||
|
||||
export type getExtraAllowedOriginsQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type getExtraAllowedOriginsQuery = { __typename?: 'Query', extraAllowedOrigins: Array<string> };
|
||||
|
||||
export type getRemoteAccessQueryVariables = Exact<{ [key: string]: never; }>;
|
||||
|
||||
|
||||
export type getRemoteAccessQuery = { __typename?: 'Query', remoteAccess: { __typename?: 'RemoteAccess', accessType: WAN_ACCESS_TYPE, forwardType?: WAN_FORWARD_TYPE | null, port?: number | null } };
|
||||
|
||||
export type setAdditionalAllowedOriginsMutationVariables = Exact<{
|
||||
input: AllowedOriginInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type setAdditionalAllowedOriginsMutation = { __typename?: 'Mutation', setAdditionalAllowedOrigins: Array<string> };
|
||||
|
||||
export type setupRemoteAccessMutationVariables = Exact<{
|
||||
input: SetupRemoteAccessInput;
|
||||
}>;
|
||||
|
||||
|
||||
export type setupRemoteAccessMutation = { __typename?: 'Mutation', setupRemoteAccess: boolean };
|
||||
|
||||
export const PartialCloudFragmentDoc = {"kind":"Document","definitions":[{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialCloud"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"minigraphql"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode<PartialCloudFragment, unknown>;
|
||||
export const ConnectSignInDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"ConnectSignIn"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"ConnectSignInInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignIn"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<ConnectSignInMutation, ConnectSignInMutationVariables>;
|
||||
export const SignOutDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"SignOut"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"connectSignOut"}}]}}]} as unknown as DocumentNode<SignOutMutation, SignOutMutationVariables>;
|
||||
export const serverStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"serverState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PartialCloud"}}]}},{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"os"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hostname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updateExpiration"}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialCloud"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"minigraphql"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode<serverStateQuery, serverStateQueryVariables>;
|
||||
export const serverStateDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"serverState"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"FragmentSpread","name":{"kind":"Name","value":"PartialCloud"}}]}},{"kind":"Field","name":{"kind":"Name","value":"config"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"valid"}}]}},{"kind":"Field","name":{"kind":"Name","value":"info"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"os"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"hostname"}}]}}]}},{"kind":"Field","name":{"kind":"Name","value":"owner"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"avatar"}},{"kind":"Field","name":{"kind":"Name","value":"username"}}]}},{"kind":"Field","name":{"kind":"Name","value":"registration"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"state"}},{"kind":"Field","name":{"kind":"Name","value":"expiration"}},{"kind":"Field","name":{"kind":"Name","value":"keyFile"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"contents"}}]}},{"kind":"Field","name":{"kind":"Name","value":"updateExpiration"}}]}},{"kind":"Field","name":{"kind":"Name","value":"vars"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"regGen"}},{"kind":"Field","name":{"kind":"Name","value":"regState"}},{"kind":"Field","name":{"kind":"Name","value":"configError"}},{"kind":"Field","name":{"kind":"Name","value":"configValid"}}]}}]}},{"kind":"FragmentDefinition","name":{"kind":"Name","value":"PartialCloud"},"typeCondition":{"kind":"NamedType","name":{"kind":"Name","value":"Cloud"}},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"error"}},{"kind":"Field","name":{"kind":"Name","value":"apiKey"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"valid"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"cloud"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"minigraphql"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}},{"kind":"Field","name":{"kind":"Name","value":"relay"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"status"}},{"kind":"Field","name":{"kind":"Name","value":"error"}}]}}]}}]} as unknown as DocumentNode<serverStateQuery, serverStateQueryVariables>;
|
||||
export const getExtraAllowedOriginsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getExtraAllowedOrigins"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"extraAllowedOrigins"}}]}}]} as unknown as DocumentNode<getExtraAllowedOriginsQuery, getExtraAllowedOriginsQueryVariables>;
|
||||
export const getRemoteAccessDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"query","name":{"kind":"Name","value":"getRemoteAccess"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"remoteAccess"},"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"accessType"}},{"kind":"Field","name":{"kind":"Name","value":"forwardType"}},{"kind":"Field","name":{"kind":"Name","value":"port"}}]}}]}}]} as unknown as DocumentNode<getRemoteAccessQuery, getRemoteAccessQueryVariables>;
|
||||
export const setAdditionalAllowedOriginsDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"setAdditionalAllowedOrigins"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"AllowedOriginInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setAdditionalAllowedOrigins"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<setAdditionalAllowedOriginsMutation, setAdditionalAllowedOriginsMutationVariables>;
|
||||
export const setupRemoteAccessDocument = {"kind":"Document","definitions":[{"kind":"OperationDefinition","operation":"mutation","name":{"kind":"Name","value":"setupRemoteAccess"},"variableDefinitions":[{"kind":"VariableDefinition","variable":{"kind":"Variable","name":{"kind":"Name","value":"input"}},"type":{"kind":"NonNullType","type":{"kind":"NamedType","name":{"kind":"Name","value":"SetupRemoteAccessInput"}}}}],"selectionSet":{"kind":"SelectionSet","selections":[{"kind":"Field","name":{"kind":"Name","value":"setupRemoteAccess"},"arguments":[{"kind":"Argument","name":{"kind":"Name","value":"input"},"value":{"kind":"Variable","name":{"kind":"Name","value":"input"}}}]}]}}]} as unknown as DocumentNode<setupRemoteAccessMutation, setupRemoteAccessMutationVariables>;
|
||||
@@ -8,12 +8,12 @@ const useInstallPlugin = () => {
|
||||
const install = (payload: InstallPluginPayload) => {
|
||||
console.debug('[installPlugin]', payload);
|
||||
try {
|
||||
// @ts-ignore – `openPlugin` will be included in 6.10.4+ DefaultPageLayout
|
||||
// @ts-expect-error global function defined in the webgui's DefaultPageLayout.php
|
||||
if (typeof openPlugin === 'function') {
|
||||
const plgUrl = new URL(payload.pluginUrl);
|
||||
const installString = `${plgUrl.pathname.replace('.plg', '').substring(1)}:install`; // mimic what is done on the install plg page JS but without the regex that's hard to read
|
||||
console.debug('[installPlugin]', { installString, plgUrl });
|
||||
// @ts-ignore
|
||||
// @ts-expect-error global function defined in the webgui's DefaultPageLayout.php
|
||||
openPlugin(
|
||||
`plugin ${payload.update ? 'update' : 'install'} ${payload.pluginUrl}${payload.update ? '' : ' forced'}`, // command – `forced` is used to bypass the strcmp check in the plugin manager script being wrong for OS versions
|
||||
payload.modalTitle, // title
|
||||
@@ -23,8 +23,8 @@ const useInstallPlugin = () => {
|
||||
1, // hide close button
|
||||
);
|
||||
} else {
|
||||
// `openBox()` is defined in the webgui's DefaultPageLayout.php and used when openPlugin is not available
|
||||
// @ts-ignore
|
||||
//
|
||||
// @ts-expect-error openBox() is defined in the webgui's DefaultPageLayout.php and used when openPlugin is not available
|
||||
openBox(
|
||||
`/plugins/dynamix.plugin.manager/scripts/plugin&arg1=install&arg2=${payload.pluginUrl}`,
|
||||
payload.modalTitle,
|
||||
|
||||
@@ -18,6 +18,7 @@ export const startTrial = async (payload: StartTrialPayload): Promise<StartTrial
|
||||
|
||||
export interface ValidateGuidResponse {
|
||||
hasNewerKeyfile : boolean;
|
||||
linked: boolean;
|
||||
purchaseable: true;
|
||||
registered: false;
|
||||
replaceable: false;
|
||||
|
||||
@@ -21,16 +21,6 @@ export const WebguiInstallKey = request.url('/webGui/include/InstallKey.php');
|
||||
* @param {string} username
|
||||
*/
|
||||
export const WebguiUpdate = request.url('/update.php');
|
||||
/**
|
||||
* @name WebguiUpdateDns
|
||||
* @dataForm formUrl
|
||||
* @description Used after Sign In to ensure URLs will work correctly
|
||||
* @note this request is delayed by 500ms to allow server to process key install fully
|
||||
* @todo potentially remove delay
|
||||
* @param csrf_token
|
||||
* @type POST
|
||||
*/
|
||||
export const WebguiUpdateDns = request.url('/webGui/include/UpdateDNS.php');
|
||||
/**
|
||||
* @name WebguiState
|
||||
* @description used to get current state of server via PHP rather than unraid-api
|
||||
@@ -102,6 +92,10 @@ interface WebguiUnraidCheckPayload {
|
||||
version?: string;
|
||||
}
|
||||
|
||||
interface WebguiUnraidCheckIgnoreResponse {
|
||||
updateOsIgnoredReleases: string[];
|
||||
}
|
||||
|
||||
export const WebguiCheckForUpdate = async (): Promise<ServerUpdateOsResponse | unknown> => {
|
||||
console.debug('[WebguiCheckForUpdate]');
|
||||
try {
|
||||
@@ -131,7 +125,7 @@ export const WebguiCheckForUpdate = async (): Promise<ServerUpdateOsResponse | u
|
||||
}
|
||||
};
|
||||
|
||||
export const WebguiUpdateIgnore = async (payload: WebguiUnraidCheckPayload): Promise<any | void> => {
|
||||
export const WebguiUpdateIgnore = async (payload: WebguiUnraidCheckPayload): Promise<WebguiUnraidCheckIgnoreResponse> => {
|
||||
console.debug('[WebguiUpdateIgnore] payload', payload);
|
||||
try {
|
||||
const response = await request
|
||||
@@ -152,3 +146,25 @@ export const WebguiUpdateIgnore = async (payload: WebguiUnraidCheckPayload): Pro
|
||||
throw new Error('Error ignoring update');
|
||||
}
|
||||
};
|
||||
|
||||
export interface WebguiUpdateCancelResponse {
|
||||
message?: string;
|
||||
success?: boolean;
|
||||
}
|
||||
export const WebguiUpdateCancel = async (): Promise<WebguiUpdateCancelResponse> => {
|
||||
console.debug('[WebguiUpdateCancel]');
|
||||
try {
|
||||
const response = await request
|
||||
.url('/plugins/dynamix.plugin.manager/include/UnraidUpdateCancel.php')
|
||||
.get()
|
||||
.json(json => json as WebguiUpdateCancelResponse)
|
||||
.catch((error) => {
|
||||
console.error('[WebguiUpdateCancel] catch failed to execute UpdateUpdateCancel', error);
|
||||
throw new Error('Error attempting to revert OS files to cancel update');
|
||||
});
|
||||
return response as WebguiUpdateCancelResponse;
|
||||
} catch (error) {
|
||||
console.error('[WebguiUpdateCancel] catch failed to execute UpdateUpdateCancel', error);
|
||||
throw new Error('Error attempting to revert OS files to cancel update');
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2,7 +2,6 @@
|
||||
* @see https://www.telerik.com/blogs/how-to-trap-focus-modal-vue-3
|
||||
*/
|
||||
import { customRef } from 'vue';
|
||||
// eslint-disable-next-line import/named
|
||||
import { createFocusTrap } from 'focus-trap';
|
||||
|
||||
const useFocusTrap = (focusTrapArgs) => {
|
||||
|
||||
11
web/eslint.config.mjs
Normal file
11
web/eslint.config.mjs
Normal file
@@ -0,0 +1,11 @@
|
||||
// @ts-check
|
||||
import withNuxt from './.nuxt/eslint.config.mjs'
|
||||
|
||||
export default withNuxt(
|
||||
{
|
||||
rules: {
|
||||
'vue/multi-word-component-names': 'off', // turn off to allow web component parents to work and not trigger errors
|
||||
'vue/no-v-html': 'off',
|
||||
},
|
||||
},
|
||||
)
|
||||
@@ -1,10 +1,2 @@
|
||||
/** Output key + value as string for each item in the object. Adds new line after each item. */
|
||||
export const OBJ_TO_STR = (obj: object): string => Object.entries(obj).reduce((str, [p, val]) => `${str}${p}: ${val}\n`, '');
|
||||
/** Removes our dev logs from prod builds */
|
||||
export const disableProductionConsoleLogs = () => {
|
||||
if (import.meta.env.PROD && !import.meta.env.VITE_ALLOW_CONSOLE_LOGS) {
|
||||
console.log = () => {};
|
||||
console.debug = () => {};
|
||||
console.info = () => {};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -5,12 +5,13 @@ const UNRAID_NET = new URL(sessionStorage.getItem('unraidPurchaseUrl') ?? import
|
||||
|
||||
const ACCOUNT_CALLBACK = new URL('c', ACCOUNT);
|
||||
const FORUMS_BUG_REPORT = new URL('/bug-reports', FORUMS);
|
||||
const CONNECT_DOCS = new URL('category/unraid-connect', DOCS);
|
||||
const CONNECT_DOCS = new URL('/go/connect/', DOCS);
|
||||
const CONNECT_DASHBOARD = new URL(import.meta.env.VITE_CONNECT ?? 'https://connect.myunraid.net');
|
||||
const CONNECT_FORUMS = new URL('/forum/94-connect-plugin-support/', FORUMS);
|
||||
const CONTACT = new URL('/contact', UNRAID_NET);
|
||||
const DISCORD = new URL('https://discord.gg/unraid');
|
||||
const DISCORD = new URL('https://discord.unraid.net');
|
||||
const PURCHASE_CALLBACK = new URL('/c', UNRAID_NET);
|
||||
const UNRAID_NET_SUPPORT = new URL('/support', UNRAID_NET);
|
||||
|
||||
const WEBGUI = new URL(import.meta.env.VITE_WEBGUI ?? window.location.origin);
|
||||
const WEBGUI_GRAPHQL = new URL('/graphql', WEBGUI);
|
||||
@@ -22,9 +23,22 @@ const WEBGUI_TOOLS_UPDATE = new URL('/Tools/Update', WEBGUI);
|
||||
|
||||
const OS_RELEASES = new URL(import.meta.env.VITE_OS_RELEASES ?? 'https://releases.unraid.net/os');
|
||||
|
||||
const DOCS_RELEASE_NOTES = new URL('/unraid-os/release-notes/', DOCS);
|
||||
const DOCS_REGISTRATION_LICENSING = new URL('/unraid-os/faq/licensing-faq', DOCS);
|
||||
const DOCS_REGISTRATION_REPLACE_KEY = new URL('/unraid-os/manual/changing-the-flash-device', DOCS);
|
||||
const DOCS_RELEASE_NOTES = new URL('/go/release-notes/', DOCS);
|
||||
|
||||
/**
|
||||
* @param version - An Unraid OS version string (x.x.x-suffix).
|
||||
* Suffix indicates special releases, such as RCs or betas.
|
||||
* @returns A URL object pointing to the release notes for the specified Unraid OS version.
|
||||
*/
|
||||
const getReleaseNotesUrl = (version: string): URL => {
|
||||
const osVersion = version.split('-')[0];
|
||||
return new URL(`/unraid-os/release-notes/${osVersion}`, DOCS);
|
||||
}
|
||||
|
||||
const DOCS_REGISTRATION_LICENSING = new URL('/go/faq-licensing/', DOCS);
|
||||
const DOCS_REGISTRATION_REPLACE_KEY = new URL('/go/changing-the-flash-device/', DOCS);
|
||||
|
||||
const SUPPORT = new URL('https://unraid.net');
|
||||
|
||||
export {
|
||||
ACCOUNT,
|
||||
@@ -40,6 +54,7 @@ export {
|
||||
OS_RELEASES,
|
||||
DOCS,
|
||||
DOCS_RELEASE_NOTES,
|
||||
getReleaseNotesUrl,
|
||||
DOCS_REGISTRATION_LICENSING,
|
||||
DOCS_REGISTRATION_REPLACE_KEY,
|
||||
WEBGUI,
|
||||
@@ -49,4 +64,6 @@ export {
|
||||
WEBGUI_TOOLS_DOWNGRADE,
|
||||
WEBGUI_TOOLS_REGISTRATION,
|
||||
WEBGUI_TOOLS_UPDATE,
|
||||
SUPPORT,
|
||||
UNRAID_NET_SUPPORT,
|
||||
};
|
||||
|
||||
@@ -126,10 +126,10 @@
|
||||
"Learn More": "",
|
||||
"No Keyfile": "",
|
||||
"Let's Unleash your Hardware!": "",
|
||||
"<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of a Pro Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>": "",
|
||||
"<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>": "",
|
||||
"Trial": "",
|
||||
"Thank you for choosing Unraid OS!": "",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of a <em>Pro</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "",
|
||||
"Trial Expired": "",
|
||||
"Your Trial has expired": "",
|
||||
"<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>": "",
|
||||
@@ -186,7 +186,7 @@
|
||||
"Starting your free 30 day trial": "",
|
||||
"Trial Key Created": "",
|
||||
"Please wait while the page reloads to install your trial key": "",
|
||||
"A Trial key provides all the functionality of a Pro Registration key": "",
|
||||
"A Trial key provides all the functionality of an Unleashed Registration key": "",
|
||||
"Extension Installed": "",
|
||||
"Recovered": "",
|
||||
"Replaced": "",
|
||||
@@ -196,5 +196,5 @@
|
||||
"Install Extended": "",
|
||||
"Install Recovered": "",
|
||||
"Install Replaced": "",
|
||||
"Your free Trial key provides all the functionality of a Pro Registration key": ""
|
||||
"Your free Trial key provides all the functionality of an Unleashed Registration key": ""
|
||||
}
|
||||
|
||||
@@ -1,360 +1,372 @@
|
||||
{
|
||||
"LAN IP": "LAN IP",
|
||||
"LAN IP {0}": "LAN IP {0}",
|
||||
"LAN IP Copied": "LAN IP Copied",
|
||||
"Click to Copy LAN IP {0}": "Click to Copy LAN IP {0}",
|
||||
"Trial Key Expired at {0}": "Trial Key Expired at {0}",
|
||||
"Trial Key Expires at {0}": "Trial Key Expires at {0}",
|
||||
"Trial Key Expired {0}": "Trial Key Expired {0}",
|
||||
"Trial Key Expires in {0}": "Trial Key Expires in {0}",
|
||||
"Server Up Since {0}": "Server Up Since {0}",
|
||||
"Uptime {0}": "Uptime {0}",
|
||||
"year": "{n} year | {n} years",
|
||||
"month": "{n} month | {n} months",
|
||||
"day": "{n} day | {n} days",
|
||||
"hour": "{n} hour | {n} hours",
|
||||
"minute": "{n} minute | {n} minutes",
|
||||
"second": "{n} second | {n} seconds",
|
||||
"{0} {1} Key…": "{0} {1} Key…",
|
||||
"{0} devices": "{0} devices",
|
||||
"{0} out of {1} allowed devices – upgrade your key to support more devices": "{0} out of {1} allowed devices – upgrade your key to support more devices",
|
||||
"{0} out of {1} devices": "{0} out of {1} devices",
|
||||
"{0} Release Notes": "{0} Release Notes",
|
||||
"{0} Signed In Successfully": "{0} Signed In Successfully",
|
||||
"{0} Signed Out Successfully": "{0} Signed Out Successfully",
|
||||
"{0} Update Available": "{0} Update Available",
|
||||
"{1} Key {0} Successfully": "{1} Key {0} Successfully",
|
||||
"<p>It is not possible to use a Trial key with an existing Unraid OS installation.</p><p>You may purchase a license key corresponding to this USB Flash device to continue using this installation.</p>": "<p>It is not possible to use a Trial key with an existing Unraid OS installation.</p><p>You may purchase a license key corresponding to this USB Flash device to continue using this installation.</p>",
|
||||
"<p>Please refresh the page to ensure you load your latest configuration</p>": "<p>Please refresh the page to ensure you load your latest configuration</p>",
|
||||
"<p>Register for Connect by signing in to your Unraid.net account</p>": "<p>Register for Connect by signing in to your Unraid.net account</p>",
|
||||
"<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>": "<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>",
|
||||
"<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it is blacklisted.</p>": "<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it is blacklisted.</p>",
|
||||
"<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device.</p><p>You may also attempt to Purchase or Replace your key.</p>": "<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device.</p><p>You may also attempt to Purchase or Replace your key.</p>",
|
||||
"<p>There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device.</p><p>Alternately you may purchase a license key for this USB flash device.</p><p>If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.</p>": "<p>There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device.</p><p>Alternately you may purchase a license key for this USB flash device.</p><p>If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.</p>",
|
||||
"<p>There is a physical problem accessing your USB Flash boot device</p>": "<p>There is a physical problem accessing your USB Flash boot device</p>",
|
||||
"<p>There is a problem with your USB Flash device</p>": "<p>There is a problem with your USB Flash device</p>",
|
||||
"<p>This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.</p><p>A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers.</p>": "<p>This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.</p><p>A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers.</p>",
|
||||
"<p>This USB Flash device has an invalid GUID. Please try a different USB Flash device</p>": "<p>This USB Flash device has an invalid GUID. Please try a different USB Flash device</p>",
|
||||
"<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>": "<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>",
|
||||
"<p>To support more storage devices as your server grows, click Upgrade Key.</p>": "<p>To support more storage devices as your server grows, click Upgrade Key.</p>",
|
||||
"<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>": "<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>",
|
||||
"<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>": "<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>",
|
||||
"<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>": "<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>",
|
||||
"<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>": "<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>",
|
||||
"<p>Your Trial key requires an internet connection.</p><p><a href='/Settings/NetworkSettings class='underline'>Please check Settings > Network</a></p>": "<p>Your Trial key requires an internet connection.</p><p><a href='/Settings/NetworkSettings class='underline'>Please check Settings > Network</a></p>",
|
||||
"<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>": "<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>",
|
||||
"A Trial key provides all the functionality of an Unleashed Registration key": "A Trial key provides all the functionality of an Unleashed Registration key.",
|
||||
"A valid GUID is required to check for OS updates.": "A valid GUID is required to check for OS updates.",
|
||||
"A valid keyfile and USB Flash boot device are required to check for OS updates.": "A valid keyfile and USB Flash boot device are required to check for OS updates.",
|
||||
"A valid keyfile is required to check for OS updates.": "A valid keyfile is required to check for OS updates.",
|
||||
"A valid OS version is required to check for OS updates.": "A valid OS version is required to check for OS updates.",
|
||||
"Acklowledge that you have made a Flash Backup to enable this action": "Acklowledge that you have made a Flash Backup to enable this action",
|
||||
"ago": "ago",
|
||||
"Purchase": "Purchase",
|
||||
"Upgrade": "Upgrade",
|
||||
"Fix Error": "Fix Error",
|
||||
"Get Started": "Get Started",
|
||||
"Trial Expired, see options below": "Trial Expired, see options below",
|
||||
"Learn more about the error": "Learn more about the error",
|
||||
"Close Dropdown": "Close Dropdown",
|
||||
"Open Dropdown": "Open Dropdown",
|
||||
"Thank you for installing Connect!": "Thank you for installing Connect!",
|
||||
"Sign In to your Unraid.net account to get started": "Sign In to your Unraid.net account to get started",
|
||||
"Go to Connect": "Go to Connect",
|
||||
"Opens Connect in new tab": "Opens Connect in new tab",
|
||||
"Manage Unraid.net Account": "Manage Unraid.net Account",
|
||||
"Manage Unraid.net Account in new tab": "Manage Unraid.net Account in new tab",
|
||||
"Settings": "Settings",
|
||||
"Go to Connect plugin settings": "Go to Connect plugin settings",
|
||||
"Enhance your Unraid experience with Connect": "Enhance your Unraid experience with Connect",
|
||||
"Beta": "Beta",
|
||||
"Loading": "Loading",
|
||||
"Restarting unraid-api…": "Restarting unraid-api…",
|
||||
"unraid-api is offline": "unraid-api is offline",
|
||||
"Introducing Unraid Connect": "Introducing Unraid Connect",
|
||||
"Enhance your Unraid experience": "Enhance your Unraid experience",
|
||||
"Connected": "Connected",
|
||||
"Dynamic Remote Access": "Dynamic Remote Access",
|
||||
"Toggle on/off server accessibility with dynamic remote access. Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.": "Toggle on/off server accessibility with dynamic remote access. Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.",
|
||||
"Manage Your Server Within Connect": "Manage Your Server Within Connect",
|
||||
"Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.": "Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.",
|
||||
"Deep Linking": "Deep Linking",
|
||||
"The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.": "The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.",
|
||||
"Online Flash Backup": "Online Flash Backup",
|
||||
"Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.": "Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.",
|
||||
"Real-time Monitoring": "Real-time Monitoring",
|
||||
"Get an overview of your server's state, storage space, apps and VMs status, and more.": "Get an overview of your server's state, storage space, apps and VMs status, and more.",
|
||||
"Customizable Dashboard Tiles": "Customizable Dashboard Tiles",
|
||||
"Set custom server tiles how you like and automatically display your server's banner image on your Connect Dashboard.": "Set custom server tiles how you like and automatically display your server's banner image on your Connect Dashboard.",
|
||||
"License Management": "License Management",
|
||||
"Manage your license keys at any time via the My Keys section.": "Manage your license keys at any time via the My Keys section.",
|
||||
"Plus more on the way": "Plus more on the way",
|
||||
"All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.": "All you need is an active internet connection, an Unraid.net account, and the Connect plugin. Get started by installing the plugin.",
|
||||
"Attached Storage Devices": "Attached Storage Devices",
|
||||
"Backing up...this may take a few minutes": "Backing up...this may take a few minutes",
|
||||
"Basic": "Basic",
|
||||
"Begin downgrade to {0}": "Begin downgrade to {0}",
|
||||
"Beta": "Beta",
|
||||
"Blacklisted USB Flash GUID": "Blacklisted USB Flash GUID",
|
||||
"BLACKLISTED": "BLACKLISTED",
|
||||
"Calculating OS Update Eligibility…": "Calculating OS Update Eligibility…",
|
||||
"Calculating trial expiration…": "Calculating trial expiration…",
|
||||
"Callback redirect type not present or incorrect": "Callback redirect type not present or incorrect",
|
||||
"Cancel {0}": "Cancel {0}",
|
||||
"Cancel": "Cancel",
|
||||
"Cannot access your USB Flash boot device": "Cannot access your USB Flash boot device",
|
||||
"Cannot validate Unraid Trial key": "Cannot validate Unraid Trial key",
|
||||
"Check Eligibility": "Check Eligibility",
|
||||
"check for OS updates": "check for OS updates",
|
||||
"Check for OS Updates": "Check for OS Updates",
|
||||
"Check for Prereleases": "Check for Prereleases",
|
||||
"Check for Update": "Check for Update",
|
||||
"Checking WAN IPs…": "Checking WAN IPs…",
|
||||
"Checking...": "Checking...",
|
||||
"Checkout the Connect Documentation": "Checkout the Connect Documentation",
|
||||
"No thanks": "No thanks",
|
||||
"Learn more": "Learn more",
|
||||
"Install Connect": "Install Connect",
|
||||
"Installing Connect": "Installing Connect",
|
||||
"Click to close modal": "Click to close modal",
|
||||
"Click to Copy LAN IP {0}": "Click to Copy LAN IP {0}",
|
||||
"Close Dropdown": "Close Dropdown",
|
||||
"Close Modal": "Close Modal",
|
||||
"Close": "Close",
|
||||
"Reload": "Reload",
|
||||
"Reload Page": "Reload Page",
|
||||
"Unraid logo animating with a wave like effect": "Unraid logo animating with a wave like effect",
|
||||
"Click to close modal": "Click to close modal",
|
||||
"Error": "Error",
|
||||
"Performing actions": "Performing actions",
|
||||
"Success!": "Success!",
|
||||
"Something went wrong": "Something went wrong",
|
||||
"Please keep this window open while we perform some actions": "Please keep this window open while we perform some actions",
|
||||
"You're one step closer to enhancing your Unraid experience": "You're one step closer to enhancing your Unraid experience",
|
||||
"Thank you for purchasing an Unraid {0} Key!": "Thank you for purchasing an Unraid {0} Key!",
|
||||
"Thank you for upgrading to an Unraid {0} Key!": "Thank you for upgrading to an Unraid {0} Key!",
|
||||
"Your {0} Key has been replaced!": "Your {0} Key has been replaced!",
|
||||
"Your Trial key has been extended!": "Your Trial key has been extended!",
|
||||
"Configure Connect Features": "Configure Connect Features",
|
||||
"Confirm and start update": "Confirm and start update",
|
||||
"Confirm to Install Unraid OS {0}": "Confirm to Install Unraid OS {0}",
|
||||
"Connected": "Connected",
|
||||
"Contact Support": "Contact Support",
|
||||
"Continue": "Continue",
|
||||
"Copied": "Copied",
|
||||
"Copy Key URL": "Copy Key URL",
|
||||
"Copy your Key URL: {0}": "Copy your Key URL: {0}",
|
||||
"Then go to Tools > Registration to manually install it": "Then go to Tools > Registration to manually install it",
|
||||
"Enhance your experience with Unraid Connect": "Enhance your experience with Unraid Connect",
|
||||
"Sign In to utilize Unraid Connect": "Sign In to utilize Unraid Connect",
|
||||
"Configure Connect Features": "Configure Connect Features",
|
||||
"The primary method of support for Unraid Connect is through our forums and Discord.": "The primary method of support for Unraid Connect is through our forums and Discord.",
|
||||
"If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.": "If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.",
|
||||
"The logs may contain sensitive information so do not post them publicly.": "The logs may contain sensitive information so do not post them publicly.",
|
||||
"Download unraid-api Logs": "Download unraid-api Logs",
|
||||
"Unraid Connect Forums": "Unraid Connect Forums",
|
||||
"Unraid Discord": "Unraid Discord",
|
||||
"Unraid Contact Page": "Unraid Contact Page",
|
||||
"Create Flash Backup": "Create Flash Backup",
|
||||
"Current Version {0}": "Current Version {0}",
|
||||
"Current Version: Unraid {0}": "Current Version: Unraid {0}",
|
||||
"Customizable Dashboard Tiles": "Customizable Dashboard Tiles",
|
||||
"day": "{n} day | {n} days",
|
||||
"Deep Linking": "Deep Linking",
|
||||
"DNS issue, unable to resolve wanip4.unraid.net": "DNS issue, unable to resolve wanip4.unraid.net",
|
||||
"Unable to fetch client WAN IPv4": "Unable to fetch client WAN IPv4",
|
||||
"Checking WAN IPs…": "Checking WAN IPs…",
|
||||
"Remark: your WAN IPv4 is {0}": "Remark: your WAN IPv4 is {0}",
|
||||
"Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.": "Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.",
|
||||
"This may indicate a complex network that will not work with this Remote Access solution.": "This may indicate a complex network that will not work with this Remote Access solution.",
|
||||
"Ignore this message if you are currently connected via Remote Access or VPN.": "Ignore this message if you are currently connected via Remote Access or VPN.",
|
||||
"Ready to update Connect account configuration": "Ready to update Connect account configuration",
|
||||
"Signing in {0}…": "Signing in {0}…",
|
||||
"Signing out {0}…": "Signing out {0}…",
|
||||
"{0} Signed In Successfully": "{0} Signed In Successfully",
|
||||
"{0} Signed Out Successfully": "{0} Signed Out Successfully",
|
||||
"Sign In Failed": "Sign In Failed",
|
||||
"Sign Out Failed": "Sign Out Failed",
|
||||
"Failed to update Connect account configuration": "Failed to update Connect account configuration",
|
||||
"Callback redirect type not present or incorrect": "Callback redirect type not present or incorrect",
|
||||
"Failed to install key": "Failed to install key",
|
||||
"Ready to Install Key": "Ready to Install Key",
|
||||
"Installing Extended Trial": "Installing Extended Trial",
|
||||
"Installing Recovered": "Installing Recovered",
|
||||
"Installing Replaced": "Installing Replaced",
|
||||
"{0} {1} Key…": "{0} {1} Key…",
|
||||
"{1} Key {0} Successfully": "{1} Key {0} Successfully",
|
||||
"Failed to {0} {1} Key": "Failed to {0} {1} Key",
|
||||
"Purchase Key": "Purchase Key",
|
||||
"Upgrade Key": "Upgrade Key",
|
||||
"Recover Key": "Recover Key",
|
||||
"Redeem Activation Code": "Redeem Activation Code",
|
||||
"Replace Key": "Replace Key",
|
||||
"Sign In with Unraid.net Account": "Sign In with Unraid.net Account",
|
||||
"Sign Out of Unraid.net": "Sign Out of Unraid.net",
|
||||
"Downgrade Unraid OS to {0}": "Downgrade Unraid OS to {0}",
|
||||
"Downgrade Unraid OS": "Downgrade Unraid OS",
|
||||
"Downgrades are only recommended if you're unable to solve a critical issue.": "Downgrades are only recommended if you're unable to solve a critical issue.",
|
||||
"Download Diagnostics": "Download Diagnostics",
|
||||
"Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.": "Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.",
|
||||
"Download unraid-api Logs": "Download unraid-api Logs",
|
||||
"Dynamic Remote Access": "Dynamic Remote Access",
|
||||
"Eligible for free feature updates for {0}": "Eligible for free feature updates for {0}",
|
||||
"Eligible for free feature updates until {0}": "Eligible for free feature updates until {0}",
|
||||
"Eligible": "Eligible",
|
||||
"Enable update notifications": "Enable update notifications",
|
||||
"Enhance your experience with Unraid Connect": "Enhance your experience with Unraid Connect",
|
||||
"Enhance your Unraid experience with Connect": "Enhance your Unraid experience with Connect",
|
||||
"Enhance your Unraid experience": "Enhance your Unraid experience",
|
||||
"Error creating a trial key. Please try again later.": "Error creating a trial key. Please try again later.",
|
||||
"Error Parsing Changelog • {0}": "Error Parsing Changelog • {0}",
|
||||
"Error": "Error",
|
||||
"Expired {0}": "Expired {0}",
|
||||
"Expired": "Expired",
|
||||
"Expires at {0}": "Expires at {0}",
|
||||
"Expires in {0}": "Expires in {0}",
|
||||
"Extend License to Enable OS Updates": "Extend License to Enable OS Updates",
|
||||
"Extend License to Update": "Extend License to Update",
|
||||
"Extend License": "Extend License",
|
||||
"Extend Trial": "Extend Trial",
|
||||
"Start Free 30 Day Trial": "Start Free 30 Day Trial",
|
||||
"Go to Management Access Now": "Go to Management Access Now",
|
||||
"Contact Support": "Contact Support",
|
||||
"Learn More": "Learn More",
|
||||
"No Keyfile": "No Keyfile",
|
||||
"Let's Unleash your Hardware!": "Let's Unleash your Hardware!",
|
||||
"<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of a Pro Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>": "<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of a Pro Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>",
|
||||
"Trial": "Trial",
|
||||
"Thank you for choosing Unraid OS!": "Thank you for choosing Unraid OS!",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of a <em>Pro</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "<p>Your <em>Trial</em> key includes all the functionality and device support of a <em>Pro</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>",
|
||||
"Trial Expired": "Trial Expired",
|
||||
"Your Trial has expired": "Your Trial has expired",
|
||||
"<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>": "<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>",
|
||||
"<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>": "<p>You have used all your Trial extensions. To continue using Unraid OS you may purchase a license key.</p>",
|
||||
"Basic": "Basic",
|
||||
"<p>Register for Connect by signing in to your Unraid.net account</p>": "<p>Register for Connect by signing in to your Unraid.net account</p>",
|
||||
"<p>To support more storage devices as your server grows, click Upgrade Key.</p>": "<p>To support more storage devices as your server grows, click Upgrade Key.</p>",
|
||||
"Plus": "Plus",
|
||||
"Pro": "Pro",
|
||||
"Flash GUID Error": "Flash GUID Error",
|
||||
"Registration key / USB Flash GUID mismatch": "Registration key / USB Flash GUID mismatch",
|
||||
"<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>": "<p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>",
|
||||
"<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it is blacklisted.</p>": "<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it is blacklisted.</p>",
|
||||
"<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>": "<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device or choose Purchase Key.</p><p>Your Unraid registration key is ineligible for replacement as it has been replaced within the last 12 months.</p>",
|
||||
"<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device.</p><p>You may also attempt to Purchase or Replace your key.</p>": "<p>The license key file does not correspond to the USB Flash boot device. Please copy the correct key file to the /config directory on your USB Flash boot device.</p><p>You may also attempt to Purchase or Replace your key.</p>",
|
||||
"Multiple License Keys Present": "Multiple License Keys Present",
|
||||
"<p>There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device.</p><p>Alternately you may purchase a license key for this USB flash device.</p><p>If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.</p>": "<p>There are multiple license key files present on your USB flash device and none of them correspond to the USB Flash boot device. Please remove all key files, except the one you want to replace, from the /config directory on your USB Flash boot device.</p><p>Alternately you may purchase a license key for this USB flash device.</p><p>If you want to replace one of your license keys with a new key bound to this USB Flash device, please first remove all other key files first.</p>",
|
||||
"Missing key file": "Missing key file",
|
||||
"<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>": "<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>You may attempt to recover your key with your Unraid.net account.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>",
|
||||
"<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>": "<p>Your license key file is corrupted or missing. The key file should be located in the /config directory on your USB Flash boot device.</p><p>If you do not have a backup copy of your license key file you may attempt to recover your key.</p><p>If this was an expired Trial installation, you may purchase a license key.</p>",
|
||||
"Invalid installation": "Invalid installation",
|
||||
"<p>It is not possible to use a Trial key with an existing Unraid OS installation.</p><p>You may purchase a license key corresponding to this USB Flash device to continue using this installation.</p>": "<p>It is not possible to use a Trial key with an existing Unraid OS installation.</p><p>You may purchase a license key corresponding to this USB Flash device to continue using this installation.</p>",
|
||||
"No USB flash configuration data": "No USB flash configuration data",
|
||||
"<p>There is a problem with your USB Flash device</p>": "<p>There is a problem with your USB Flash device</p>",
|
||||
"No Flash": "No Flash",
|
||||
"Cannot access your USB Flash boot device": "Cannot access your USB Flash boot device",
|
||||
"<p>There is a physical problem accessing your USB Flash boot device</p>": "<p>There is a physical problem accessing your USB Flash boot device</p>",
|
||||
"BLACKLISTED": "BLACKLISTED",
|
||||
"Blacklisted USB Flash GUID": "Blacklisted USB Flash GUID",
|
||||
"<p>This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.</p><p>A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers.</p>": "<p>This USB Flash boot device has been blacklisted. This can occur as a result of transferring your license key to a replacement USB Flash device, and you are currently booted from your old USB Flash device.</p><p>A USB Flash device may also be blacklisted if we discover the serial number is not unique – this is common with USB card readers.</p>",
|
||||
"USB Flash device error": "USB Flash device error",
|
||||
"<p>This USB Flash device has an invalid GUID. Please try a different USB Flash device</p>": "<p>This USB Flash device has an invalid GUID. Please try a different USB Flash device</p>",
|
||||
"USB Flash has no serial number": "USB Flash has no serial number",
|
||||
"Trial Requires Internet Connection": "Trial Requires Internet Connection",
|
||||
"Cannot validate Unraid Trial key": "Cannot validate Unraid Trial key",
|
||||
"<p>Your Trial key requires an internet connection.</p><p><a href='/Settings/NetworkSettings class='underline'>Please check Settings > Network</a></p>": "<p>Your Trial key requires an internet connection.</p><p><a href='/Settings/NetworkSettings class='underline'>Please check Settings > Network</a></p>",
|
||||
"Stale": "Stale",
|
||||
"Stale Server": "Stale Server",
|
||||
"<p>Please refresh the page to ensure you load your latest configuration</p>": "<p>Please refresh the page to ensure you load your latest configuration</p>",
|
||||
"Invalid API Key": "Invalid API Key",
|
||||
"Please sign out then sign back in to refresh your API key.": "Please sign out then sign back in to refresh your API key.",
|
||||
"Invalid API Key Format": "Invalid API Key Format",
|
||||
"Too Many Devices": "Too Many Devices",
|
||||
"You have exceeded the number of devices allowed for your license. Please remove a device before adding another.": "You have exceeded the number of devices allowed for your license. Please remove a device before adding another.",
|
||||
"Unraid Connect Install Failed": "Unraid Connect Install Failed",
|
||||
"Rebooting will likely solve this.": "Rebooting will likely solve this.",
|
||||
"SSL certificates for unraid.net deprecated": "SSL certificates for unraid.net deprecated",
|
||||
"On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.": "On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.",
|
||||
"Unraid Connect Error": "Unraid Connect Error",
|
||||
"Trial Key Creation Failed": "Trial Key Creation Failed",
|
||||
"Error creatiing a trial key. Please try again later.": "Error creatiing a trial key. Please try again later.",
|
||||
"Extending your free trial by 15 days": "Extending your free trial by 15 days",
|
||||
"Please keep this window open": "Please keep this window open",
|
||||
"Starting your free 30 day trial": "Starting your free 30 day trial",
|
||||
"Trial Key Created": "Trial Key Created",
|
||||
"Please wait while the page reloads to install your trial key": "Please wait while the page reloads to install your trial key",
|
||||
"A Trial key provides all the functionality of a Pro Registration key": "A Trial key provides all the functionality of a Pro Registration key.",
|
||||
"Extension Installed": "Extension Installed",
|
||||
"Recovered": "Recovered",
|
||||
"Replaced": "Replaced",
|
||||
"Installing": "Installing",
|
||||
"Installed": "Installed",
|
||||
"Install": "Install",
|
||||
"Failed to {0} {1} Key": "Failed to {0} {1} Key",
|
||||
"Failed to install key": "Failed to install key",
|
||||
"Failed to update Connect account configuration": "Failed to update Connect account configuration",
|
||||
"Fetching & parsing changelog…": "Fetching & parsing changelog…",
|
||||
"Fix Error": "Fix Error",
|
||||
"Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.": "Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.",
|
||||
"Flash GUID Error": "Flash GUID Error",
|
||||
"Flash GUID required to check replacement status": "Flash GUID required to check replacement status",
|
||||
"Flash GUID": "Flash GUID",
|
||||
"Flash Product": "Flash Product",
|
||||
"Flash Vendor": "Flash Vendor",
|
||||
"Get a Lifetime Key" : "Get a Lifetime Key",
|
||||
"Get an overview of your server's state, storage space, apps and VMs status, and more.": "Get an overview of your server's state, storage space, apps and VMs status, and more.",
|
||||
"Get Started": "Get Started",
|
||||
"Go to Connect plugin settings": "Go to Connect plugin settings",
|
||||
"Go to Connect": "Go to Connect",
|
||||
"Go to Management Access Now": "Go to Management Access Now",
|
||||
"Go to Settings > Notifications to enable automatic OS update notifications for future releases.": "Go to Settings > Notifications to enable automatic OS update notifications for future releases.",
|
||||
"Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.": "Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.",
|
||||
"Go to Tools > Management Access to ensure your backup is up-to-date.": "Go to Tools > Management Access to ensure your backup is up-to-date.",
|
||||
"Go to Tools > Registration to fix": "Go to Tools > Registration to fix",
|
||||
"Go to Tools > Registration to Learn More": "Go to Tools > Registration to Learn More",
|
||||
"Go to Tools > Update OS for more options.": "Go to Tools > Update OS for more options.",
|
||||
"Go to Tools > Update": "Go to Tools > Update",
|
||||
"hour": "{n} hour | {n} hours",
|
||||
"I have made a Flash Backup": "I have made a Flash Backup",
|
||||
"If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.": "If you are asked to supply logs, please open a support request on our Contact Page and reply to the email message you receive with your logs attached.",
|
||||
"Ignore this message if you are currently connected via Remote Access or VPN.": "Ignore this message if you are currently connected via Remote Access or VPN.",
|
||||
"Ignore this release until next reboot": "Ignore this release until next reboot",
|
||||
"Ignored Releases": "Ignored Releases",
|
||||
"In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.": "In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.",
|
||||
"Ineligible as of {0}": "Ineligible as of {0}",
|
||||
"Ineligible for feature updates released after {0}": "Ineligible for feature updates released after {0}",
|
||||
"Ineligible for Unraid OS updates": "Ineligible for Unraid OS updates",
|
||||
"Ineligible": "Ineligible",
|
||||
"Ineligible for self-replacement": "Ineligible for self-replacement",
|
||||
"Install Connect": "Install Connect",
|
||||
"Install Extended": "Install Extended",
|
||||
"Install Recovered": "Install Recovered",
|
||||
"Install Replaced": "Install Replaced",
|
||||
"Your free Trial key provides all the functionality of a Pro Registration key": "Your free Trial key provides all the functionality of a Pro Registration key",
|
||||
"Calculating trial expiration…": "Calculating trial expiration…",
|
||||
"Signing In": "Signing In",
|
||||
"Signing Out": "Signing Out",
|
||||
"Sign In requires the local unraid-api to be running": "Sign In requires the local unraid-api to be running",
|
||||
"Sign Out requires the local unraid-api to be running": "Sign Out requires the local unraid-api to be running",
|
||||
"Unraid OS {0} Released": "Unraid OS {0} Released",
|
||||
"Unraid {0} Update Available": "Unraid {0} Update Available",
|
||||
"{0} Update Available": "{0} Update Available",
|
||||
"Unraid OS Update Available": "Unraid OS Update Available",
|
||||
"Update Unraid OS confirmation required": "Update Unraid OS confirmation required",
|
||||
"Please confirm the update details below": "Please confirm the update details below",
|
||||
"Current Version {0}": "Current Version {0}",
|
||||
"Current Version: Unraid {0}": "Current Version: Unraid {0}",
|
||||
"New Version: {0}": "New Version: {0}",
|
||||
"Version: {0}": "Version: {0}",
|
||||
"This update will require a reboot": "This update will require a reboot",
|
||||
"Confirm and start update": "Confirm and start update",
|
||||
"Update Unraid OS": "Update Unraid OS",
|
||||
"Install Unraid OS {0}": "Install Unraid OS {0}",
|
||||
"Install": "Install",
|
||||
"Installed": "Installed",
|
||||
"Installing Connect": "Installing Connect",
|
||||
"Installing Extended Trial": "Installing Extended Trial",
|
||||
"Installing Extended": "Installing Extended",
|
||||
"Installing Recovered": "Installing Recovered",
|
||||
"Installing Replaced": "Installing Replaced",
|
||||
"Installing": "Installing",
|
||||
"Introducing Unraid Connect": "Introducing Unraid Connect",
|
||||
"Invalid API Key Format": "Invalid API Key Format",
|
||||
"Invalid API Key": "Invalid API Key",
|
||||
"Invalid installation": "Invalid installation",
|
||||
"It's highly recommended to review the changelog before continuing your update": "It's highly recommended to review the changelog before continuing your update",
|
||||
"Key ineligible for {0}": "Key ineligible for {0}",
|
||||
"Key ineligible for future releases": "Key ineligible for future releases",
|
||||
"Key ineligible for new updates": "Key ineligible for new updates",
|
||||
"Keyfile required to check replacement status": "Keyfile required to check replacement status",
|
||||
"LAN IP {0}": "LAN IP {0}",
|
||||
"LAN IP Copied": "LAN IP Copied",
|
||||
"LAN IP": "LAN IP",
|
||||
"Last checked: {0}": "Last checked: {0}",
|
||||
"Downgrade Unraid OS": "Downgrade Unraid OS",
|
||||
"Downgrade Unraid OS to {0}": "Downgrade Unraid OS to {0}",
|
||||
"No downgrade available": "No downgrade available",
|
||||
"Begin downgrade to {0}": "Begin downgrade to {0}",
|
||||
"Version available for restore {0}": "Version available for restore {0}",
|
||||
"check for OS updates": "check for OS updates",
|
||||
"Check for Prereleases": "Check for Prereleases",
|
||||
"Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.": "Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.",
|
||||
"Check for OS Updates": "Check for OS Updates",
|
||||
"Checking...": "Checking...",
|
||||
"View release notes": "View release notes",
|
||||
"View Changelog for {0}": "View Changelog for {0}",
|
||||
"View Changelog & Update": "View Changelog & Update",
|
||||
"{0} Release Notes": "{0} Release Notes",
|
||||
"Unable to open release notes": "Unable to open release notes",
|
||||
"Downgrades are only recommended if you're unable to solve a critical issue.": "Downgrades are only recommended if you're unable to solve a critical issue.",
|
||||
"In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.": "In the rare event you need to downgrade we ask that you please provide us with Diagnostics so we can investigate your issue.",
|
||||
"Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.": "Download the Diagnostics zip then please open a bug report on our forums with a description of the issue along with your diagnostics.",
|
||||
"Reboot Now to Downgrade": "Reboot Now to Downgrade",
|
||||
"Reboot Now to Update": "Reboot Now to Update",
|
||||
"Reboot Now to Downgrade to {0}": "Reboot Now to Downgrade to {0}",
|
||||
"Reboot Now to Update to {0}": "Reboot Now to Update to {0}",
|
||||
"Reboot Required for Downgrade": "Reboot Required for Downgrade",
|
||||
"Reboot Required for Update": "Reboot Required for Update",
|
||||
"Reboot Required for Downgrade to {0}": "Reboot Required for Downgrade to {0}",
|
||||
"Reboot Required for Update to {0}": "Reboot Required for Update to {0}",
|
||||
"Updating 3rd party drivers": "Updating 3rd party drivers",
|
||||
"Update Available": "Update Available",
|
||||
"Up-to-date": "Up-to-date",
|
||||
"Open a bug report": "Open a bug report",
|
||||
"Go to Tools > Update": "Go to Tools > Update",
|
||||
"A valid keyfile and USB Flash boot device are required to check for OS updates.": "A valid keyfile and USB Flash boot device are required to check for OS updates.",
|
||||
"Please fix any errors and try again.": "Please fix any errors and try again.",
|
||||
"Go to Tools > Registration to fix": "Go to Tools > Registration to fix",
|
||||
"Original release date {0}": "Original release date {0}",
|
||||
"Registered to": "Registered to",
|
||||
"Registered on": "Registered on",
|
||||
"Updates Expire": "Updates Expire",
|
||||
"Flash GUID": "Flash GUID",
|
||||
"Flash Vendor": "Flash Vendor",
|
||||
"Flash Product": "Flash Product",
|
||||
"Attached Storage Devices": "Attached Storage Devices",
|
||||
"{0} out of {1} devices": "{0} out of {1} devices",
|
||||
"{0} out of {1} allowed devices – upgrade your key to support more devices": "{0} out of {1} allowed devices – upgrade your key to support more devices",
|
||||
"{0} devices": "{0} devices",
|
||||
"unlimited": "unlimited",
|
||||
"Unable to check for OS updates": "Unable to check for OS updates",
|
||||
"Learn more about the error": "Learn more about the error",
|
||||
"Learn more and fix": "Learn more and fix",
|
||||
"Learn more and link your key to your account": "Learn more and link your key to your account",
|
||||
"Learn more": "Learn more",
|
||||
"Learn More": "Learn More",
|
||||
"Let's Unleash your Hardware!": "Let's Unleash your Hardware!",
|
||||
"License key actions": "License key actions",
|
||||
"License key type": "License key type",
|
||||
"OS Update Eligibility Expiration": "OS Update Eligibility Expiration",
|
||||
"Ineligible for updates released after {0}": "Ineligible for updates released after {0}",
|
||||
"Eligible for updates until {0}": "Eligible for updates until {0}",
|
||||
"Ineligible as of {0}": "Ineligible as of {0}",
|
||||
"Eligible for updates for {0}": "Eligible for updates for {0}",
|
||||
"Renew your license key now": "Renew your license key now",
|
||||
"Extend License to Enable OS Updates": "Extend License to Enable OS Updates",
|
||||
"Check Eligibility": "Check Eligibility",
|
||||
"Eligible": "Eligible",
|
||||
"Ineligible": "Ineligible",
|
||||
"Flash GUID required to check replacement status": "Flash GUID required to check replacement status",
|
||||
"Keyfile required to check replacement status": "Keyfile required to check replacement status",
|
||||
"Unraid {0}": "Unraid {0}",
|
||||
"OS Update Eligibility": "OS Update Eligibility",
|
||||
"Transfer License to New Flash": "Transfer License to New Flash",
|
||||
"Starter": "Starter",
|
||||
"Unleashed": "Unleashed",
|
||||
"License Management": "License Management",
|
||||
"Lifetime": "Lifetime",
|
||||
"Link Key": "Link Key",
|
||||
"Linked": "Linked",
|
||||
"Linked to Unraid.net account": "Linked to Unraid.net account",
|
||||
"Loading": "Loading",
|
||||
"Manage Unraid.net Account in new tab": "Manage Unraid.net Account in new tab",
|
||||
"Manage Unraid.net Account": "Manage Unraid.net Account",
|
||||
"Manage your license keys at any time via the My Keys section.": "Manage your license keys at any time via the My Keys section.",
|
||||
"Manage Your Server Within Connect": "Manage Your Server Within Connect",
|
||||
"minute": "{n} minute | {n} minutes",
|
||||
"Missing key file": "Missing key file",
|
||||
"month": "{n} month | {n} months",
|
||||
"More options": "More options",
|
||||
"Multiple License Keys Present": "Multiple License Keys Present",
|
||||
"Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.": "Never ever be left without a backup of your config. If you need to change flash drives, generate a backup from Connect and be up and running in minutes.",
|
||||
"New Version: {0}": "New Version: {0}",
|
||||
"No downgrade available": "No downgrade available",
|
||||
"No Flash": "No Flash",
|
||||
"No Keyfile": "No Keyfile",
|
||||
"No thanks": "No thanks",
|
||||
"No USB flash configuration data": "No USB flash configuration data",
|
||||
"Not Linked": "Not Linked",
|
||||
"On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.": "On January 1st, 2023 SSL certificates for unraid.net were deprecated. You MUST provision a new SSL certificate to use our new myunraid.net domain. You can do this on the Settings > Management Access page.",
|
||||
"Online Flash Backup": "Online Flash Backup",
|
||||
"Open a bug report": "Open a bug report",
|
||||
"Open Dropdown": "Open Dropdown",
|
||||
"Opens Connect in new tab": "Opens Connect in new tab",
|
||||
"Original release date {0}": "Original release date {0}",
|
||||
"OS Update Eligibility Expiration": "OS Update Eligibility Expiration",
|
||||
"OS Update Eligibility Expired": "OS Update Eligibility Expired",
|
||||
"OS Update Eligibility": "OS Update Eligibility",
|
||||
"Pay your annual fee to continue receiving OS updates.": "Pay your annual fee to continue receiving OS updates.",
|
||||
"Renew Key": "Renew Key",
|
||||
"A valid GUID is required to check for OS updates.": "A valid GUID is required to check for OS updates.",
|
||||
"A valid keyfile is required to check for OS updates.": "A valid keyfile is required to check for OS updates.",
|
||||
"A valid OS version is required to check for OS updates.": "A valid OS version is required to check for OS updates.",
|
||||
"Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.": "Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.",
|
||||
"Key ineligible for new updates": "Key ineligible for new updates",
|
||||
"Ineligible for Unraid OS updates": "Ineligible for Unraid OS updates",
|
||||
"Learn more and fix": "Learn more and fix",
|
||||
"Expires at {0}": "Expires at {0}",
|
||||
"Expires in {0}": "Expires in {0}",
|
||||
"Expired": "Expired",
|
||||
"Expired {0}": "Expired {0}",
|
||||
"Create Flash Backup": "Create Flash Backup",
|
||||
"Get a Lifetime Key" : "Get a Lifetime Key",
|
||||
"We recommend backing up your USB Flash Boot Device before starting the update.": "We recommend backing up your USB Flash Boot Device before starting the update.",
|
||||
"You have already activated the Flash Backup feature via the Unraid Connect plugin.": "You have already activated the Flash Backup feature via the Unraid Connect plugin.",
|
||||
"Go to Tools > Management Access to ensure your backup is up-to-date.": "Go to Tools > Management Access to ensure your backup is up-to-date.",
|
||||
"You have not activated the Flash Backup feature via the Unraid Connect plugin.": "You have not activated the Flash Backup feature via the Unraid Connect plugin.",
|
||||
"Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.": "Go to Tools > Management Access to activate the Flash Backup feature and ensure your backup is up-to-date.",
|
||||
"Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.": "Flash Backup is not available. Navigate to {0}/Main/Settings/Flash to try again then come back to this page.",
|
||||
"Backing up...this may take a few minutes": "Backing up...this may take a few minutes",
|
||||
"Acklowledge that you have made a Flash Backup to enable this action": "Acklowledge that you have made a Flash Backup to enable this action",
|
||||
"You can also manually create a new backup by clicking the Create Flash Backup button.": "You can also manually create a new backup by clicking the Create Flash Backup button.",
|
||||
"You can manually create a backup by clicking the Create Flash Backup button.": "You can manually create a backup by clicking the Create Flash Backup button.",
|
||||
"I have made a Flash Backup": "I have made a Flash Backup",
|
||||
"You may still update to releases dated prior to your update expiration date.": "You may still update to releases dated prior to your update expiration date.",
|
||||
"View Available Updates": "View Available Updates",
|
||||
"Your license key is not eligible for Unraid OS {0}": "Your license key is not eligible for Unraid OS {0}",
|
||||
"Unraid {0} Available": "Unraid {0} Available",
|
||||
"Key ineligible for {0}": "Key ineligible for {0}",
|
||||
"Up-to-date with eligible releases": "Up-to-date with eligible releases",
|
||||
"Key ineligible for future releases": "Key ineligible for future releases",
|
||||
"View Changelog": "View Changelog",
|
||||
"You are still eligible to access OS updates that were published on or before {1}.": "You are still eligible to access OS updates that were published on or before {1}.",
|
||||
"Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates.": "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates.",
|
||||
"Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.": "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.",
|
||||
"Extend License": "Extend License",
|
||||
"Calculating OS Update Eligibility…": "Calculating OS Update Eligibility…",
|
||||
"Cancel": "Cancel",
|
||||
"Unknown error": "Unknown error",
|
||||
"Performing actions": "Performing actions",
|
||||
"Please confirm the update details below": "Please confirm the update details below",
|
||||
"Please finish the initiated downgrade to enable updates.": "Please finish the initiated downgrade to enable updates.",
|
||||
"Please finish the initiated update to enable a downgrade.": "Please finish the initiated update to enable a downgrade.",
|
||||
"Download Diagnostics": "Download Diagnostics",
|
||||
"Requires the local unraid-api to be running successfully": "Requires the local unraid-api to be running successfully",
|
||||
"Sign In": "Sign In",
|
||||
"OS Update Eligibility Expired": "OS Update Eligibility Expired",
|
||||
"Go to Tools > Registration to Learn More": "Go to Tools > Registration to Learn More",
|
||||
"Installing Extended": "Installing Extended",
|
||||
"Release requires verification to update": "Release requires verification to update",
|
||||
"Error Parsing Changelog • {0}": "Error Parsing Changelog • {0}",
|
||||
"It's highly recommended to review the changelog before continuing your update": "It's highly recommended to review the changelog before continuing your update",
|
||||
"View Changelog on Docs": "View Changelog on Docs",
|
||||
"Fetching & parsing changelog…": "Fetching & parsing changelog…",
|
||||
"View on Docs": "View on Docs",
|
||||
"Extend License to Update": "Extend License to Update",
|
||||
"Install Unraid OS {0}": "Install Unraid OS {0}",
|
||||
"View Changelog to Start Update": "View Changelog to Start Update",
|
||||
"Unraid OS {0} Update Available": "Unraid OS {0} Update Available",
|
||||
"Remove": "Remove",
|
||||
"Remove from ignore list": "Remove from ignore list",
|
||||
"Ignored Releases": "Ignored Releases",
|
||||
"Ignore this release until next reboot": "Ignore this release until next reboot",
|
||||
"Confirm to Install Unraid OS {0}": "Confirm to Install Unraid OS {0}",
|
||||
"Continue": "Continue",
|
||||
"Verify to Update": "Verify to Update",
|
||||
"Please fix any errors and try again.": "Please fix any errors and try again.",
|
||||
"Please keep this window open while we perform some actions": "Please keep this window open while we perform some actions",
|
||||
"Please keep this window open": "Please keep this window open",
|
||||
"Please sign out then sign back in to refresh your API key.": "Please sign out then sign back in to refresh your API key.",
|
||||
"Please wait while the page reloads to install your trial key": "Please wait while the page reloads to install your trial key",
|
||||
"Plus more on the way": "Plus more on the way",
|
||||
"Plus": "Plus",
|
||||
"Pro": "Pro",
|
||||
"Purchase Key": "Purchase Key",
|
||||
"Purchase": "Purchase",
|
||||
"Ready to Install Key": "Ready to Install Key",
|
||||
"Ready to update Connect account configuration": "Ready to update Connect account configuration",
|
||||
"Real-time Monitoring": "Real-time Monitoring",
|
||||
"Reboot Now to Downgrade to {0}": "Reboot Now to Downgrade to {0}",
|
||||
"Reboot Now to Downgrade": "Reboot Now to Downgrade",
|
||||
"Reboot Now to Update to {0}": "Reboot Now to Update to {0}",
|
||||
"Reboot Now to Update": "Reboot Now to Update",
|
||||
"Reboot Required for Downgrade to {0}": "Reboot Required for Downgrade to {0}",
|
||||
"Reboot Required for Downgrade": "Reboot Required for Downgrade",
|
||||
"Reboot Required for Update to {0}": "Reboot Required for Update to {0}",
|
||||
"Reboot Required for Update": "Reboot Required for Update",
|
||||
"Rebooting will likely solve this.": "Rebooting will likely solve this.",
|
||||
"Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.": "Receive the latest and greatest for Unraid OS. Whether it new features, security patches, or bug fixes – keeping your server up-to-date ensures the best experience that Unraid has to offer.",
|
||||
"Recover Key": "Recover Key",
|
||||
"Recovered": "Recovered",
|
||||
"Redeem Activation Code": "Redeem Activation Code",
|
||||
"Refresh": "Refresh",
|
||||
"Registered on": "Registered on",
|
||||
"Registered to": "Registered to",
|
||||
"Registration key / USB Flash GUID mismatch": "Registration key / USB Flash GUID mismatch",
|
||||
"Release date {0}": "Release date {0}",
|
||||
"Release requires verification to update": "Release requires verification to update",
|
||||
"Reload Page": "Reload Page",
|
||||
"Reload": "Reload",
|
||||
"Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.": "Remark: Unraid's WAN IPv4 {0} does not match your client's WAN IPv4 {1}.",
|
||||
"Remark: your WAN IPv4 is {0}": "Remark: your WAN IPv4 is {0}",
|
||||
"Remove from ignore list": "Remove from ignore list",
|
||||
"Remove": "Remove",
|
||||
"Renew Key": "Renew Key",
|
||||
"Renew your license key now": "Renew your license key now",
|
||||
"Replace Key": "Replace Key",
|
||||
"Replaced": "Replaced",
|
||||
"Requires the local unraid-api to be running successfully": "Requires the local unraid-api to be running successfully",
|
||||
"Restarting unraid-api…": "Restarting unraid-api…",
|
||||
"second": "{n} second | {n} seconds",
|
||||
"Server Up Since {0}": "Server Up Since {0}",
|
||||
"Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.": "Servers equipped with a myunraid.net certificate can be managed directly from within the Connect web UI. Manage multiple servers from your phone, tablet, laptop, or PC in the same browser window.",
|
||||
"Set custom server tiles how you like and automatically display your server's banner image on your Connect Dashboard.": "Set custom server tiles how you like and automatically display your server's banner image on your Connect Dashboard.",
|
||||
"Settings": "Settings",
|
||||
"Sign In Failed": "Sign In Failed",
|
||||
"Sign In requires the local unraid-api to be running": "Sign In requires the local unraid-api to be running",
|
||||
"Sign In to utilize Unraid Connect": "Sign In to utilize Unraid Connect",
|
||||
"Sign In to your Unraid.net account to get started": "Sign In to your Unraid.net account to get started",
|
||||
"Sign In with Unraid.net Account": "Sign In with Unraid.net Account",
|
||||
"Sign In": "Sign In",
|
||||
"Sign Out Failed": "Sign Out Failed",
|
||||
"Sign Out of Unraid.net": "Sign Out of Unraid.net",
|
||||
"Sign Out requires the local unraid-api to be running": "Sign Out requires the local unraid-api to be running",
|
||||
"Signing in {0}…": "Signing in {0}…",
|
||||
"Signing In": "Signing In",
|
||||
"Signing out {0}…": "Signing out {0}…",
|
||||
"Signing Out": "Signing Out",
|
||||
"Something went wrong": "Something went wrong",
|
||||
"SSL certificates for unraid.net deprecated": "SSL certificates for unraid.net deprecated",
|
||||
"Stale Server": "Stale Server",
|
||||
"Stale": "Stale",
|
||||
"Start Free 30 Day Trial": "Start Free 30 Day Trial",
|
||||
"Starter": "Starter",
|
||||
"Starting your free 30 day trial": "Starting your free 30 day trial",
|
||||
"Success!": "Success!",
|
||||
"Thank you for choosing Unraid OS!": "Thank you for choosing Unraid OS!",
|
||||
"Thank you for installing Connect!": "Thank you for installing Connect!",
|
||||
"Thank you for purchasing an Unraid {0} Key!": "Thank you for purchasing an Unraid {0} Key!",
|
||||
"Thank you for upgrading to an Unraid {0} Key!": "Thank you for upgrading to an Unraid {0} Key!",
|
||||
"The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.": "The Connect dashboard links to relevant sections of the webgui, allowing quick access to those settings and server sections.",
|
||||
"The logs may contain sensitive information so do not post them publicly.": "The logs may contain sensitive information so do not post them publicly.",
|
||||
"The primary method of support for Unraid Connect is through our forums and Discord.": "The primary method of support for Unraid Connect is through our forums and Discord.",
|
||||
"Then go to Tools > Registration to manually install it": "Then go to Tools > Registration to manually install it",
|
||||
"This may indicate a complex network that will not work with this Remote Access solution.": "This may indicate a complex network that will not work with this Remote Access solution.",
|
||||
"This update will require a reboot": "This update will require a reboot",
|
||||
"Toggle on/off server accessibility with dynamic remote access. Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.": "Toggle on/off server accessibility with dynamic remote access. Automatically turn on UPnP and open a random WAN port on your router at the click of a button and close off access in seconds.",
|
||||
"Too Many Devices": "Too Many Devices",
|
||||
"Transfer License to New Flash": "Transfer License to New Flash",
|
||||
"Trial Expired, see options below": "Trial Expired, see options below",
|
||||
"Trial Expired": "Trial Expired",
|
||||
"Trial Key Created": "Trial Key Created",
|
||||
"Trial Key Creation Failed": "Trial Key Creation Failed",
|
||||
"Trial Key Expired {0}": "Trial Key Expired {0}",
|
||||
"Trial Key Expired at {0}": "Trial Key Expired at {0}",
|
||||
"Trial Key Expires at {0}": "Trial Key Expires at {0}",
|
||||
"Trial Key Expires in {0}": "Trial Key Expires in {0}",
|
||||
"Trial Requires Internet Connection": "Trial Requires Internet Connection",
|
||||
"Trial": "Trial",
|
||||
"Unable to check for OS updates": "Unable to check for OS updates",
|
||||
"Unable to fetch client WAN IPv4": "Unable to fetch client WAN IPv4",
|
||||
"Unable to open release notes": "Unable to open release notes",
|
||||
"Unknown error": "Unknown error",
|
||||
"Unknown": "Unknown",
|
||||
"Unleashed": "Unleashed",
|
||||
"unlimited": "unlimited",
|
||||
"Unraid {0} Available": "Unraid {0} Available",
|
||||
"Unraid {0} Update Available": "Unraid {0} Update Available",
|
||||
"Unraid {0}": "Unraid {0}",
|
||||
"Unraid Connect Error": "Unraid Connect Error",
|
||||
"Unraid Connect Forums": "Unraid Connect Forums",
|
||||
"Unraid Connect Install Failed": "Unraid Connect Install Failed",
|
||||
"Unraid Contact Page": "Unraid Contact Page",
|
||||
"Unraid Discord": "Unraid Discord",
|
||||
"Unraid logo animating with a wave like effect": "Unraid logo animating with a wave like effect",
|
||||
"Unraid OS {0} Released": "Unraid OS {0} Released",
|
||||
"Unraid OS {0} Update Available": "Unraid OS {0} Update Available",
|
||||
"Unraid OS is up-to-date": "Unraid OS is up-to-date",
|
||||
"Unraid OS Update Available": "Unraid OS Update Available",
|
||||
"unraid-api is offline": "unraid-api is offline",
|
||||
"Up-to-date with eligible releases": "Up-to-date with eligible releases",
|
||||
"Up-to-date": "Up-to-date",
|
||||
"Update Available": "Update Available",
|
||||
"Update Released": "Update Released",
|
||||
"Go to Tools > Update OS for more options.": "Go to Tools > Update OS for more options.",
|
||||
"Go to Settings > Notifications to enable automatic OS update notifications for future releases.": "Go to Settings > Notifications to enable automatic OS update notifications for future releases.",
|
||||
"More options": "More options"
|
||||
"Update Unraid OS confirmation required": "Update Unraid OS confirmation required",
|
||||
"Update Unraid OS": "Update Unraid OS",
|
||||
"Updates Expire": "Updates Expire",
|
||||
"Updating 3rd party drivers": "Updating 3rd party drivers",
|
||||
"Upgrade Key": "Upgrade Key",
|
||||
"Upgrade": "Upgrade",
|
||||
"Uptime {0}": "Uptime {0}",
|
||||
"USB Flash device error": "USB Flash device error",
|
||||
"USB Flash has no serial number": "USB Flash has no serial number",
|
||||
"Verify to Update": "Verify to Update",
|
||||
"Version available for restore {0}": "Version available for restore {0}",
|
||||
"Version: {0}": "Version: {0}",
|
||||
"View Available Updates": "View Available Updates",
|
||||
"View Changelog & Update": "View Changelog & Update",
|
||||
"View Changelog for {0}": "View Changelog for {0}",
|
||||
"View Changelog on Docs": "View Changelog on Docs",
|
||||
"View Changelog to Start Update": "View Changelog to Start Update",
|
||||
"View Changelog": "View Changelog",
|
||||
"View on Docs": "View on Docs",
|
||||
"View release notes": "View release notes",
|
||||
"We recommend backing up your USB Flash Boot Device before starting the update.": "We recommend backing up your USB Flash Boot Device before starting the update.",
|
||||
"year": "{n} year | {n} years",
|
||||
"You are still eligible to access OS updates that were published on or before {1}.": "You are still eligible to access OS updates that were published on or before {1}.",
|
||||
"You can also manually create a new backup by clicking the Create Flash Backup button.": "You can also manually create a new backup by clicking the Create Flash Backup button.",
|
||||
"You can manually create a backup by clicking the Create Flash Backup button.": "You can manually create a backup by clicking the Create Flash Backup button.",
|
||||
"You have already activated the Flash Backup feature via the Unraid Connect plugin.": "You have already activated the Flash Backup feature via the Unraid Connect plugin.",
|
||||
"You have exceeded the number of devices allowed for your license. Please remove a device before adding another.": "You have exceeded the number of devices allowed for your license. Please remove a device before adding another.",
|
||||
"You have not activated the Flash Backup feature via the Unraid Connect plugin.": "You have not activated the Flash Backup feature via the Unraid Connect plugin.",
|
||||
"You may still update to releases dated prior to your update expiration date.": "You may still update to releases dated prior to your update expiration date.",
|
||||
"You're one step closer to enhancing your Unraid experience": "You're one step closer to enhancing your Unraid experience",
|
||||
"Your {0} Key has been replaced!": "Your {0} Key has been replaced!",
|
||||
"Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.": "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates. You are still eligible to access OS updates that were published on or before {1}.",
|
||||
"Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates.": "Your {0} license included one year of free updates at the time of purchase. You are now eligible to extend your license and access the latest OS updates.",
|
||||
"Your free Trial key provides all the functionality of an Unleashed Registration key": "Your free Trial key provides all the functionality of an Unleashed Registration key",
|
||||
"Your license key is not eligible for Unraid OS {0}": "Your license key is not eligible for Unraid OS {0}",
|
||||
"Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.": "Your license key's OS update eligibility has expired. Please renew your license key to enable updates released after your expiration date.",
|
||||
"Your Trial has expired": "Your Trial has expired",
|
||||
"Your Trial key has been extended!": "Your Trial key has been extended!"
|
||||
}
|
||||
|
||||
@@ -126,10 +126,10 @@
|
||||
"Learn More": "もっと詳しく知る",
|
||||
"No Keyfile": "キーファイルがありません",
|
||||
"Let's Unleash your Hardware!": "ハードウェアを解き放ちましょう!",
|
||||
"<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of a Pro Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>": "<p>登録キーを購入するか、30 日間の無料の<em>トライアル</em>キーをインストールするまで、サーバーは使用できません。 \n<em>トライアル</em> キーは、Pro 登録キーのすべての機能を提供します。</p><p>登録キーは、USB フラッシュ ブート デバイスのシリアル番号 (GUID) にバインドされています。\nサイズが少なくとも 1 GB の高品質の有名ブランドのデバイスを使用してください。</p><p>注: USB メモリ カード リーダーのほとんどは固有のシリアル番号を提示していないため、通常はサポートされていません。</p><p><strong>\n重要:</strong></p><ul class='list-disc pl-16px'><li>サーバー時間が 5 分以内であることを確認してください。</li><li>\n指定された DNS サーバー</li></ul>",
|
||||
"<p>Your server will not be usable until you purchase a Registration key or install a free 30 day <em>Trial</em> key. A <em>Trial</em> key provides all the functionality of an Unleashed Registration key.</p><p>Registration keys are bound to your USB Flash boot device serial number (GUID). Please use a high quality name brand device at least 1GB in size.</p><p>Note: USB memory card readers are generally not supported because most do not present unique serial numbers.</p><p><strong>Important:</strong></p><ul class='list-disc pl-16px'><li>Please make sure your server time is accurate to within 5 minutes</li><li>Please make sure there is a DNS server specified</li></ul>": "<p>登録キーを購入するか、30 日間の無料の<em>トライアル</em>キーをインストールするまで、サーバーは使用できません。 \n<em>トライアル</em> キーは、Pro 登録キーのすべての機能を提供します。</p><p>登録キーは、USB フラッシュ ブート デバイスのシリアル番号 (GUID) にバインドされています。\nサイズが少なくとも 1 GB の高品質の有名ブランドのデバイスを使用してください。</p><p>注: USB メモリ カード リーダーのほとんどは固有のシリアル番号を提示していないため、通常はサポートされていません。</p><p><strong>\n重要:</strong></p><ul class='list-disc pl-16px'><li>サーバー時間が 5 分以内であることを確認してください。</li><li>\n指定された DNS サーバー</li></ul>",
|
||||
"Trial": "トライアル",
|
||||
"Thank you for choosing Unraid OS!": "Unraid OS をお選びいただきありがとうございます。",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of a <em>Pro</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "<p><em>トライアル</em> キーには、<em>プロ</em> キーのすべての機能とデバイス サポートが含まれています。</p><p><em>トライアル</em> の終了後\n有効期限に達しても、次回アレイを停止するかサーバーを再起動するまで、 サーバーは<strong>通常どおり機能</strong>します。</p><p>その時点で、ライセンス キーを購入するか、<em>ライセンス キーをリクエストすることができます。 \n> トライアル</em>拡張機能。</p>",
|
||||
"<p>Your <em>Trial</em> key includes all the functionality and device support of an <em>Unleashed</em> key.</p><p>After your <em>Trial</em> has reached expiration, your server <strong>still functions normally</strong> until the next time you Stop the array or reboot your server.</p><p>At that point you may either purchase a license key or request a <em>Trial</em> extension.</p>": "<p><em>トライアル</em> キーには、<em>プロ</em> キーのすべての機能とデバイス サポートが含まれています。</p><p><em>トライアル</em> の終了後\n有効期限に達しても、次回アレイを停止するかサーバーを再起動するまで、 サーバーは<strong>通常どおり機能</strong>します。</p><p>その時点で、ライセンス キーを購入するか、<em>ライセンス キーをリクエストすることができます。 \n> トライアル</em>拡張機能。</p>",
|
||||
"Trial Expired": "トライアル期間が終了しました",
|
||||
"Your Trial has expired": "トライアル版の有効期限が切れました",
|
||||
"<p>To continue using Unraid OS you may purchase a license key. Alternately, you may request a Trial extension.</p>": "<p>Unraid OS を引き続き使用するには、ライセンス キーを購入することができます。\nあるいは、トライアルの延長をリクエストすることもできます。</p>",
|
||||
@@ -186,7 +186,7 @@
|
||||
"Starting your free 30 day trial": "30 日間の無料トライアルを開始する",
|
||||
"Trial Key Created": "トライアルキーが作成されました",
|
||||
"Please wait while the page reloads to install your trial key": "試用版キーをインストールするには、ページがリロードされるまでお待ちください。",
|
||||
"A Trial key provides all the functionality of a Pro Registration key": "トライアル キーは、Pro 登録キーのすべての機能を提供します。",
|
||||
"A Trial key provides all the functionality of an Unleashed Registration key": "トライアル キーは、Pro 登録キーのすべての機能を提供します。",
|
||||
"Extension Installed": "拡張機能がインストールされました",
|
||||
"Recovered": "回復しました",
|
||||
"Replaced": "交換されました",
|
||||
@@ -196,5 +196,5 @@
|
||||
"Install Extended": "拡張インストール",
|
||||
"Install Recovered": "インストールが回復しました",
|
||||
"Install Replaced": "インストールと置き換え",
|
||||
"Your free Trial key provides all the functionality of a Pro Registration key": "無料のトライアル キーは、プロ登録キーのすべての機能を提供します"
|
||||
"Your free Trial key provides all the functionality of an Unleashed Registration key": "無料のトライアル キーは、プロ登録キーのすべての機能を提供します"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user