Compare commits
556 Commits
proxy_twea
...
tailscale-
Author | SHA1 | Date | |
---|---|---|---|
a7ac0986a1 | |||
57edfd6d7b | |||
3a5d5f07ef | |||
13413b5d1f | |||
47a04e10c0 | |||
44e09d534c | |||
1b65b07110 | |||
da01e24ff8 | |||
a080ec364d | |||
|
95c6913c62 | ||
|
b783d4b207 | ||
|
a866de833a | ||
|
ee31e35849 | ||
|
50e7389c8a | ||
|
d536ef285b | ||
|
2dc82b61de | ||
|
57ec7909e5 | ||
|
01c6f64b52 | ||
|
80d567dfde | ||
|
99d60fa08a | ||
|
8bbf176b8b | ||
|
ca51a3799b | ||
|
e4bb758b05 | ||
|
3c007fa1d0 | ||
|
c062e4dd9c | ||
|
06b1c9a20f | ||
|
bf6d5982be | ||
|
d9bd5b56c8 | ||
|
bc7c66fec9 | ||
|
dc50e7d2c2 | ||
|
0061c66dfe | ||
|
91caf869f5 | ||
|
97c3a4621b | ||
|
1ffb22bddf | ||
|
1d9e14f07c | ||
6f7b97e37a | |||
|
ed7219d9c7 | ||
|
082d7d842b | ||
|
9a502776a1 | ||
|
03346f4709 | ||
|
1cc84832ee | ||
|
959df7e46c | ||
|
3fb6c2147b | ||
|
3db6fa9a1d | ||
|
0c5987fab0 | ||
|
fcbc8f700e | ||
|
6fd88575b2 | ||
|
bff0238f88 | ||
|
ee7f1f4a5b | ||
|
c3dd62f1d5 | ||
|
7c0fb18e3c | ||
|
fe2e2ff897 | ||
|
74530129ae | ||
|
968e3b1d72 | ||
|
6bdcb38c47 | ||
|
8c7cdca4aa | ||
|
08024a0464 | ||
|
828cd7b747 | ||
|
0ce3960de6 | ||
|
8b91d22796 | ||
b716920800 | |||
6d749a8b1a | |||
69b95ae27d | |||
|
20e29ab5af | ||
a75bc3d4d7 | |||
|
e8e5ccdf18 | ||
020ed9a07f | |||
|
8f656e87b1 | ||
|
54b1e81b38 | ||
|
4c6be23467 | ||
|
4a4983f7c5 | ||
|
dcfaa1afa0 | ||
|
e52813b626 | ||
|
760aac71df | ||
|
20ef176665 | ||
|
66d7193dab | ||
|
9c9c79b1b3 | ||
|
8aac4ee119 | ||
|
8120959c2f | ||
|
72abe50721 | ||
|
18e37ed045 | ||
|
6845c007a7 | ||
|
b844f941d0 | ||
|
5883e767aa | ||
|
efc4fa2673 | ||
|
0810fc5bd8 | ||
|
897365a5de | ||
|
8d628aad4f | ||
e3c4ff280d | |||
19de7c1979 | |||
5ec695921a | |||
171a77feec | |||
3e29f0b8b8 | |||
b62c8f5a12 | |||
e9faee0d27 | |||
|
16823d07b1 | ||
|
73705b71fa | ||
|
6b31532688 | ||
|
039c798b43 | ||
1e43abc785 | |||
|
9b1081d2e1 | ||
|
c27e018fdb | ||
|
ef5067584b | ||
|
4ea425411a | ||
|
24bdc5169d | ||
|
753d87c690 | ||
07d02f579f | |||
|
ceb97ab392 | ||
|
6a15afa2a8 | ||
|
175d24afd2 | ||
|
12828eec63 | ||
|
156599031a | ||
|
15f4138c87 | ||
|
fd6e4f1ba1 | ||
|
72a47035ef | ||
|
d57bf205fa | ||
|
16a8e7092d | ||
|
00b1f77742 | ||
|
506270e413 | ||
|
303c76d7da | ||
|
73ea1bb7b3 | ||
|
9d4ca6a2c9 | ||
49b82d0eb8 | |||
|
106f155ecc | ||
|
53704b58aa | ||
|
da9add3637 | ||
|
db77c13552 | ||
|
be22c0e1f8 | ||
|
cd9d20eaf3 | ||
|
c4afbba9bc | ||
|
16089cd927 | ||
|
1ede8e621b | ||
|
64ead9a127 | ||
|
ff80906d11 | ||
|
8981e8bb15 | ||
|
83675005d2 | ||
|
4b7f2bfcee | ||
|
72ff3c52c0 | ||
|
69e11713e5 | ||
|
67cf2db493 | ||
|
631479d27d | ||
|
28b3d2ae71 | ||
|
ec1689dc68 | ||
|
3615992dc4 | ||
d9f83cc76b | |||
|
7e6ad9512d | ||
|
d7b4dfd44b | ||
|
c62ef28fc3 | ||
|
07fa790411 | ||
|
fb19a99ad4 | ||
d0dcf6c314 | |||
0d925a2471 | |||
|
2b4eb1abad | ||
|
251881d850 | ||
|
24fce7582c | ||
|
b56f3e529c | ||
|
130e3fcd44 | ||
|
c1b2bb7435 | ||
|
840e19d322 | ||
|
2ae85fdd31 | ||
|
0db0032648 | ||
|
8d9e2a04c0 | ||
|
31f81349a8 | ||
|
3de8e05432 | ||
|
1c019c8f08 | ||
|
8d76d6f1cc | ||
|
3e17f35e19 | ||
|
6f51589547 | ||
|
b18e734381 | ||
|
b5a8223ffe | ||
|
936adea879 | ||
|
1572378824 | ||
|
0cf3585a0d | ||
|
3f103f2089 | ||
|
ed308c3a69 | ||
|
985d077af5 | ||
|
86b8b170d1 | ||
|
d351e51f58 | ||
|
53c1788580 | ||
|
0870461731 | ||
|
043d2baaf7 | ||
|
32bb9bb6d9 | ||
|
2ff8b77c9d | ||
|
654db74167 | ||
fb2b66b5b0 | |||
663665a61b | |||
|
c9333ea955 | ||
|
bad23e7647 | ||
17909f889e | |||
8cabad6f0d | |||
|
566113f86c | ||
|
21e640184e | ||
|
202196f7d3 | ||
9c2aa45751 | |||
|
33a73b2fb5 | ||
|
06735f7a3e | ||
|
e7f7ef43c0 | ||
|
c5b692e805 | ||
|
6a91f270e3 | ||
|
6d3a7a3298 | ||
f81118ffe3 | |||
f9107ebe11 | |||
76f58d2995 | |||
b4722f57aa | |||
3314860d31 | |||
9a6b62ae98 | |||
e0b1612633 | |||
1e41ac637d | |||
858a3aa999 | |||
79c484e2e5 | |||
49793ff602 | |||
342619a567 | |||
|
27047c8832 | ||
|
b850940f19 | ||
|
de3334b0d2 | ||
|
373bb9f2ee | ||
|
833194705c | ||
|
392cc77a20 | ||
|
d272bf78ac | ||
|
9430366eaf | ||
|
71dc414592 | ||
|
2b41f9a8d2 | ||
|
70c01ec454 | ||
|
7840ae6d3c | ||
|
5b009dfb39 | ||
|
5565c02f74 | ||
|
793289bc7f | ||
|
7d92761860 | ||
|
1021adc33b | ||
|
c9374f7911 | ||
|
73a17a0306 | ||
|
0729386af9 | ||
|
4b8ec6e5a3 | ||
|
ea2fa8a8db | ||
|
d7e474257c | ||
|
4b3e8f2d46 | ||
|
54a6e3dd13 | ||
|
e20f37d936 | ||
|
4c1c566e78 | ||
|
a6c50b208a | ||
|
9a23761dc8 | ||
|
359334b85a | ||
|
f5ed6964dd | ||
|
882ee7f911 | ||
|
234a749b7f | ||
|
a9891f557e | ||
|
04640a5708 | ||
|
fac92be4d4 | ||
|
817ed5c1c1 | ||
|
a4abe0fa55 | ||
|
6920542d6c | ||
|
36f9e7402c | ||
|
cfa547972e | ||
|
84b5baf402 | ||
|
1b162e2c21 | ||
|
75f0c2e4f6 | ||
|
67369622fa | ||
|
17fdfb6f50 | ||
|
9e80fb13c7 | ||
|
1d6c3f4375 | ||
58c31f4ef6 | |||
|
84b767ddcd | ||
|
32d3f88b44 | ||
|
6156a2582f | ||
|
8a55228b8e | ||
|
fe7ab1fc43 | ||
30492ed2f2 | |||
da1ef5c0e0 | |||
93054c2091 | |||
|
f2abfaf292 | ||
|
5a42314c60 | ||
|
bbed6047e3 | ||
|
a7f6ac7389 | ||
|
f7748f7619 | ||
|
d4968e1b19 | ||
|
fb680469ac | ||
|
eaabbec7e0 | ||
|
693a0260af | ||
|
130c9c6373 | ||
73c264e9fe | |||
667741129a | |||
|
42fe45595f | ||
|
ed9b8322d4 | ||
|
cfb2daa531 | ||
|
694d35b412 | ||
|
4011d942ce | ||
|
5b95526e84 | ||
|
667418e656 | ||
|
c0d4e1de1b | ||
|
ec7ec769f4 | ||
|
1524f2d69a | ||
|
c9109d61bf | ||
|
a9a2b73849 | ||
|
b1b76d8ffa | ||
|
891ee6beff | ||
|
bd4eb4189b | ||
|
24fd56fff3 | ||
|
45d71932a1 | ||
|
f8ad3105c7 | ||
|
a8adba6728 | ||
|
6ab9f0a017 | ||
|
4904a5021c | ||
|
859e697413 | ||
|
c60cbb5c6e | ||
|
4a3a1f526d | ||
|
38758462f7 | ||
|
7e8b8e0a0a | ||
|
2f2f50d178 | ||
|
69d2996c31 | ||
|
f4a8bf0d81 | ||
|
9d63b5ecf8 | ||
|
95353c143a | ||
|
925fff60a9 | ||
|
8965860224 | ||
|
9d15302bf0 | ||
|
6bb27eec3a | ||
|
84ad25fe18 | ||
|
9f9429424f | ||
|
04e0fac01c | ||
|
27973b8e34 | ||
|
d6f8206275 | ||
|
cd1b4031df | ||
|
44dfe5a7b1 | ||
|
1ae58dc360 | ||
|
e0c0440d69 | ||
|
282cfc41ca | ||
|
26f261e93b | ||
|
65e67856bb | ||
|
b73327d4b7 | ||
|
3fdcbf125d | ||
|
941778d288 | ||
|
90cbff0058 | ||
|
30b76de196 | ||
|
7f681fe2fa | ||
|
72651576f4 | ||
|
2ea62cca3d | ||
|
20f34bc0b9 | ||
|
c2a4e68196 | ||
|
b75c420ce0 | ||
|
da18a7e213 | ||
|
f6ae173275 | ||
|
dd9474ef55 | ||
|
7512161e1a | ||
|
68fe9a9399 | ||
|
78826be39d | ||
|
c4eff321f2 | ||
|
6d2de179a0 | ||
|
1da8308b81 | ||
|
243b838d80 | ||
|
f93095d552 | ||
|
eed5a94d03 | ||
|
5c33796801 | ||
|
3e1e33094c | ||
|
63d4bab0d1 | ||
|
dbd1c9f7b1 | ||
|
446111b0b1 | ||
|
be0dcff031 | ||
|
cdce8e048a | ||
|
2494d1735e | ||
|
8328461cf5 | ||
|
1cb3134217 | ||
|
18872afdd0 | ||
|
fa5b932c13 | ||
|
aa489bb92d | ||
|
22124123a9 | ||
|
419b9a77e6 | ||
|
191f68642c | ||
|
cfa1ec4860 | ||
|
955ba7f0a6 | ||
|
8354a5c6ac | ||
|
392811005c | ||
|
b2916424be | ||
|
b35ce11344 | ||
|
91bc84a97a | ||
|
e61132fe69 | ||
|
15af3fb6eb | ||
|
c1493a61d7 | ||
|
a823ecf79c | ||
|
50a27242ab | ||
|
7656c9a55c | ||
|
15913a6c64 | ||
|
f8ce0a6e80 | ||
|
470e02ed55 | ||
|
40ac4fe6fe | ||
|
ef795438cc | ||
|
7a968eac84 | ||
|
3ce94f664b | ||
|
5daacf1e97 | ||
|
e621ccebb1 | ||
|
e28d4a32fb | ||
|
f6427a3361 | ||
|
91e14b22db | ||
|
095b931dad | ||
|
5303b18462 | ||
|
7bdb5eeb03 | ||
|
e6ff7a5d7f | ||
|
4fcf899c5a | ||
|
d9325649bb | ||
|
0f22fe77e6 | ||
|
45c1cab4b5 | ||
|
e29de3c3e5 | ||
|
a757fe0fe6 | ||
|
55c79439f3 | ||
|
32c769e6bd | ||
|
a6c0369444 | ||
|
74ee77521e | ||
|
290d4da90a | ||
|
efd598c8f8 | ||
|
19be69461e | ||
|
cf6f3acb3a | ||
|
b14922734a | ||
|
9aa50b1b1d | ||
|
24d5ed5f1b | ||
|
a2d35294d3 | ||
|
73b3829d61 | ||
|
7b568c2aa1 | ||
|
f76337c97f | ||
|
44e74e15d3 | ||
|
3ec2a92b41 | ||
|
e256fa8313 | ||
|
ad2e98ea46 | ||
|
ed1ee47ce4 | ||
|
cce6b7ea92 | ||
|
1d5ff688ba | ||
|
81e48a2291 | ||
|
3deacb643d | ||
|
a4f6b7dcad | ||
|
fecd6505af | ||
|
6938fbb992 | ||
|
8d1478a06e | ||
|
964d083551 | ||
|
70a24418b1 | ||
|
db094cdf89 | ||
|
05f6afd1a2 | ||
|
3e37b4a27d | ||
|
bdc9ef9782 | ||
|
215d16af41 | ||
|
dcb1e43d21 | ||
|
a5cab82c29 | ||
|
280e7a52cd | ||
|
f0d4c00389 | ||
|
3301357979 | ||
|
7701f10ef4 | ||
|
ab41b013e2 | ||
|
04ef869aba | ||
|
1be0380f36 | ||
|
8d4c007226 | ||
|
f5e5ae2d28 | ||
|
41363509e2 | ||
|
59835e14eb | ||
|
d6eb51f502 | ||
|
cebe945e81 | ||
|
7532448241 | ||
|
f184a79e85 | ||
|
45308bc7cb | ||
|
e988ef55fc | ||
|
4cf3e39add | ||
|
d85d68de2e | ||
|
6790400629 | ||
|
7f77338b3d | ||
|
70d3d5a656 | ||
|
69bd331d2d | ||
|
c44beb2eb7 | ||
|
2d136461fd | ||
|
414dddb008 | ||
|
894bc28aaa | ||
|
8bf3cae742 | ||
|
86a014f82e | ||
|
91edca4576 | ||
|
1c4f5983af | ||
|
f933c288e9 | ||
|
dd0cf7f2fa | ||
|
939a30774a | ||
|
6234c5edc0 | ||
|
b34fe9838a | ||
|
f65780a67e | ||
|
80e5481de9 | ||
|
40c25d9d18 | ||
|
5542b65e45 | ||
|
70760a302c | ||
|
0431343070 | ||
|
32f1c8c3ec | ||
|
8f8da76bf3 | ||
|
58f5fb269b | ||
|
9a76f69f5d | ||
|
dac9d237ee | ||
|
f492ee8f7f | ||
|
3cc0bf7e90 | ||
|
bf92bdb1af | ||
|
7fdb5cb5be | ||
|
c45dd68e08 | ||
|
8b5eb3f66d | ||
|
43ae4c785d | ||
|
8da53df357 | ||
|
b4a3a90a46 | ||
|
072e3512e0 | ||
|
ae8feb2dbc | ||
|
f901cc9d4e | ||
|
a8f0e3ec19 | ||
|
2db31b2012 | ||
|
85d1a2f6f5 | ||
|
9296e6297c | ||
|
245e6413cb | ||
|
4f82b16fdd | ||
|
a90d083cb0 | ||
|
efe372d1d9 | ||
|
995a95f053 | ||
|
0c32fe5d4d | ||
f8ff2333bb | |||
|
ba9bef985c | ||
|
45cfa13e14 | ||
|
424b5cb121 | ||
|
d1e5f3bb10 | ||
|
e6441a8345 | ||
|
43c71ec2da | ||
|
33b27f0a71 | ||
|
90eb25f114 | ||
|
71f99155b8 | ||
|
cb48e16845 | ||
|
ff2328ddca | ||
|
b0680ee1e1 | ||
|
c8b9f1c9cc | ||
|
863afd348d | ||
|
ae285176a9 | ||
|
d9588b7c83 | ||
|
f79f907923 | ||
|
a5b7247d88 | ||
|
2a6c09f9c9 | ||
|
b8400d4395 | ||
|
191c067465 | ||
|
511a8e47ac | ||
|
2bec253098 | ||
|
b21437cf5c | ||
|
da7749725f | ||
|
7cce87baa2 | ||
|
c3d232efc0 | ||
|
1b35b145f8 | ||
|
7e0f91cb53 | ||
|
b7f69da252 | ||
|
8f99ba2346 | ||
|
e87a15d439 | ||
|
1640e6d2a0 | ||
|
a76a68d4e2 | ||
|
f9398d01c6 | ||
|
56990109f5 | ||
|
80b200950f | ||
|
62ea5702ce | ||
|
e9c973baad | ||
|
ffabb996fc | ||
|
4a4444229e | ||
|
9ca6b38ecb | ||
|
083cd984ea | ||
|
3dda97319d | ||
|
064cac1110 | ||
|
3d1b53d0ea | ||
|
61e99a390e |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -54,7 +54,6 @@ sftp-config.json
|
||||
|
||||
# =========================
|
||||
# Exclude these dirs commonly found in /usr/local
|
||||
bin/
|
||||
games/
|
||||
info/
|
||||
lib64/
|
||||
@@ -69,3 +68,8 @@ emhttp/plugins/dynamix.my.servers/unraid-components/index.html
|
||||
|
||||
# development scripts
|
||||
.dev-scripts/
|
||||
emhttp/plugins/node_modules/
|
||||
emhttp/plugins/.prettierignore
|
||||
emhttp/plugins/.prettierrc
|
||||
emhttp/plugins/package-lock.json
|
||||
emhttp/plugins/package.json
|
@@ -256,7 +256,7 @@ Unraid OS uses these default options when creating a multiple-device pool:
|
||||
|
||||
`-dconvert=raid1 -mconvert=raid1`
|
||||
|
||||
For more complete documentation, please refer to the btrfs-balance [Manpage](https://btrfs.wiki.kernel.org/index.php/Manpage/btrfs-balance)
|
||||
For more complete documentation, please refer to the btrfs-balance [Manpage](https://man7.org/linux/man-pages/man8/btrfs-balance.8.html)
|
||||
|
||||
*Note: raid5 and raid6 are generally still considered **experimental** by the Linux community*
|
||||
:end
|
||||
@@ -1008,6 +1008,10 @@ Instead of chevrons indicating more data for Port and Volume mapping, all data i
|
||||
The time in seconds to allow a container to gracefully stop before forcing it to stop
|
||||
:end
|
||||
|
||||
:docker_pid_limit_help:
|
||||
Set a PID Limit to limit the number of PIDs that a docker can use. The default is 2048. Set to zero for unlimited PIDs (not recommended).
|
||||
:end
|
||||
|
||||
:docker_vdisk_type_help:
|
||||
Select where to keep the Docker persistent state.
|
||||
|
||||
@@ -1034,6 +1038,15 @@ You must specify a folder for Docker. The system will automatically create this
|
||||
It is recommended to create this folder under a share which resides on the Cache pool (setting: cache=Only). For best performance SSD devices are preferred.
|
||||
:end
|
||||
|
||||
:docker_storage_driver_help:
|
||||
overlay2 (default): Will use overlay2 as the storage driver for Docker, regardless of the underlying filesystem.
|
||||
|
||||
native: The native storage driver for your underlying filesystem will be used (XFS: overlay2 | ZFS: zfs | BTRFS: btrfs).
|
||||
|
||||
ATTENTION: Changing the storage type from an existing Docker installation is not possible, you have to delete your Docker directory first, change the storage type and then reinstall your containers.
|
||||
It is recommended to take a screenshot from your Docker containers before changing the storage type. After deleting and changing the storage type, reinstall the containers by clicking Add Container on the Docker page and select one by one from the drop down to reinstall them with your previous settings).
|
||||
:end
|
||||
|
||||
:docker_appdata_location_help:
|
||||
You can specify a folder to automatically generate and store subfolders containing configuration files for each Docker app (via the /config mapped volume).
|
||||
|
||||
@@ -1115,6 +1128,10 @@ This is the active Docker version.
|
||||
This is the location of the Docker image.
|
||||
:end
|
||||
|
||||
:docker_storage_driver_active_help:
|
||||
This is the storage driver for Docker.
|
||||
:end
|
||||
|
||||
:docker_appdata_location_active_help:
|
||||
This is the storage location for Docker containers.
|
||||
:end
|
||||
@@ -1266,6 +1283,14 @@ The Local Access URLs shown above are based on your current settings.
|
||||
To adjust URLs or redirects, see the help text for "Use SSL/TLS".
|
||||
:end
|
||||
|
||||
:mgmt_wg_access_urls_help:
|
||||
These URLs will only work when connected via the appropriate WireGuard tunnel as configured on ***Settings > VPN Manager***
|
||||
:end
|
||||
|
||||
:mgmt_tailscale_access_urls_help:
|
||||
These URLs will only work when connected to the appropriate Tailscale Tailnet.
|
||||
:end
|
||||
|
||||
:mgmt_certificate_expiration_help:
|
||||
**Provision** may be used to install a *free* myunraid.net SSL Certificate from
|
||||
[Let's Encrypt](https://letsencrypt.org/).
|
||||
@@ -1609,6 +1634,10 @@ Selects the temperature unit for the disk temperature thresholds. Changing the u
|
||||
Make sure any newly entered values represent the selected temperature unit.
|
||||
:end
|
||||
|
||||
:display_favorites_enabled_help:
|
||||
Enables favorite support. If set to no, will stop heart icon showing for additions. If existing favorites are saved, favorites tab and pre-saved options will still continue to show and function until all are deleted.
|
||||
:end
|
||||
|
||||
:vms_enable_help:
|
||||
Stopping the VM Manager will first attempt to shutdown all running VMs. After 60 seconds, any remaining VM instances will be terminated.
|
||||
:end
|
||||
@@ -1680,6 +1709,10 @@ For setting the console options to show on context menus. Web will show only inb
|
||||
Virtual Manager Remote Viewer will only show the Remote Viewer option. Both will show both Web and Remote Viewer.
|
||||
:end
|
||||
|
||||
:vms_rdpopt_help:
|
||||
Adds option to menu to start RDP. RDP file is downloaded. You need to set browser to open when ready.
|
||||
:end
|
||||
|
||||
:vms_usage_help:
|
||||
Show metrics for CPU both guest and host percentage, memory, disk io and network io.
|
||||
:end
|
||||
@@ -1954,6 +1987,10 @@ Enter the *device* which corresponds to your situation, only applicable when *UP
|
||||
+ **modbus** - /dev/tty**
|
||||
:end
|
||||
|
||||
:apc_ups_override_ups_capacity_help:
|
||||
If your device doesn't natively report Nominal Power (`NOMPOWER`) from `apcupsd`, but does report the Load Percentage (`LOADPCT`), you can manually define the UPS capacity rating in Watts (W) (this is the 'real power' value in Watts (W), not the 'apparent power' in Volt Amps (VA), and should be detailed on your UPS manual or product listing) and the plugin will dynamically calculate a virtual Nominal Power estimate (`≈`) by comparing the Override UPS Capacity (W) and the current Load Percentage. It is only an estimate, as it doesn't factor in things like the UPS' efficiency.
|
||||
:end
|
||||
|
||||
:apc_battery_level_help:
|
||||
If during a power failure, the remaining battery percentage (as reported by the UPS) is below or equal to *Battery level*, apcupsd will initiate a system shutdown.
|
||||
:end
|
||||
@@ -2266,6 +2303,136 @@ Generally speaking, it is recommended to leave this setting to its default value
|
||||
IMPORTANT NOTE: If adjusting port mappings, do not modify the settings for the Container port as only the Host port can be adjusted.
|
||||
:end
|
||||
|
||||
:docker_container_network_help:
|
||||
This allows your container to utilize the network configuration of another container. Select the appropriate container from the list.<br/>This setup can be particularly beneficial if you wish to route your container's traffic through a VPN.
|
||||
:end
|
||||
|
||||
:docker_tailscale_help:
|
||||
Enable Tailscale to add this container as a machine on your Tailnet.
|
||||
:end
|
||||
|
||||
:docker_tailscale_hostname_help:
|
||||
Provide the hostname for this container. It does not need to match the container name, but it must be unique on your Tailnet. Note that an HTTPS certificate will be generated for this hostname, which means it will be placed in a public ledger, so use a name that you don't mind being public.
|
||||
For more information see <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">enabling https</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_be_exitnode_help:
|
||||
Enable this if other machines on your Tailnet should route their Internet traffic through this container, this is most useful for containers that connect to commercial VPN services.
|
||||
Be sure to authorize this Exit Node in your <a href="https://login.tailscale.com/admin/machines" target="_blank">Tailscale Machines Admin Panel</a>.
|
||||
For more details, see the Tailscale documentation on <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_exitnode_ip_help:
|
||||
Optionally route this container's outgoing Internet traffic through an Exit Node on your Tailnet. Choose the Exit Node or input its Tailscale IP address.
|
||||
For more details, see <a href="https://tailscale.com/kb/1103/exit-nodes" target="_blank">Exit Nodes</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_lanaccess_help:
|
||||
Only applies when this container is using an Exit Node. Enable this to allow the container to access the local network.
|
||||
|
||||
<b>WARNING:</b> Even with this feature enabled, systems on your LAN may not be able to access the container unless they have Tailscale installed.
|
||||
:end
|
||||
|
||||
:docker_tailscale_userspace_networking_help:
|
||||
When enabled, this container will operate in a restricted environment. Tailscale DNS will not work, and the container will not be able to initiate connections to other Tailscale machines. However, other machines on your Tailnet will still be able to communicate with this container.
|
||||
|
||||
When disabled, this container will have full access to your Tailnet. Tailscale DNS will work, and the container can fully communicate with other machines on the Tailnet.
|
||||
However, systems on your LAN may not be able to access the container unless they have Tailscale installed.
|
||||
:end
|
||||
|
||||
:docker_tailscale_ssh_help:
|
||||
Tailscale SSH is similar to the Docker "Console" option in the Unraid webgui, except you connect with an SSH client and authenticate via Tailscale.
|
||||
For more details, see the <a href="https://tailscale.com/kb/1193/tailscale-ssh" target="_blank">Tailscale SSH</a> documentation..
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_mode_help:
|
||||
Enabling <b>Serve</b> will automatically reverse proxy the primary web service from this container and make it available on your Tailnet using https with a valid certificate!
|
||||
|
||||
Note that when accessing the <b>Tailscale WebUI</b> url, no additional authentication layer is added beyond restricting it to your Tailnet - the container is still responsible for managing usernames/passwords that are allowed to access it. Depending on your configuration, direct access to the container may still be possible as well.
|
||||
|
||||
For more details, see the <a href="https://tailscale.com/kb/1312/serve" target="_blank">Tailscale Serve</a> documentation.
|
||||
|
||||
If the documentation recommends additional settings for a more complex use case, enable "Tailscale Show Advanced Settings". Support for these advanced settings is not available beyond confirming the commands are passed to Tailscale correctly.
|
||||
|
||||
<b>Funnel</b> is similar to <b>Serve</b>, except that the web service is made available on the open Internet. Use with care as the service will likely be attacked. As with <b>Serve</b>, the container itself is responsible for handling any authentication.
|
||||
|
||||
We recommend reading the <a href="https://tailscale.com/kb/1223/funnel" target="_blank">Tailscale Funnel</a> documentation. before enabling this feature.
|
||||
|
||||
<b>Note:</b> Enabling <b>Serve</b> or <b>Funnel</b> publishes the Tailscale hostname to a public ledger.
|
||||
For more details, see the Tailscale Documentation: <a href="https://tailscale.com/kb/1153/enabling-https" target="_blank">Enabling HTTPS</a>.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_port_help:
|
||||
This field should specify the port for the primary web service this container offers. Note: it should specify the port in the container, not a port that was remapped on the host.
|
||||
|
||||
The system attempted to determine the correct port automatically. If it used the wrong value then there is likely an issue with the "Web UI" field for this container, visible by switching from "Basic View" to "Advanced View" in the upper right corner of this page.
|
||||
|
||||
In most cases this port is all you will need to specify in order to Serve the website in this container, although additional options are available below for more complex containers.
|
||||
|
||||
This value is passed to the `<serve_port>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:`**`<serve_port>`**`<local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_show_advanced_help:
|
||||
Here there be dragons!
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_local_path_help:
|
||||
When not specified, this value defaults to an empty string. It is passed to the `<local_path>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port><path> http://localhost:<serve_port>`**`<local_path>`**<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_protocol_help:
|
||||
When not specified, this value defaults to "https". It is passed to the `<protocol>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --`**`<protocol>`**`=<protocol_port><path> http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_protocol_port_help:
|
||||
When not specified, this value defaults to "=443". It is passed to the `<protocol_port>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol>`**`<protocol_port>`**`<path> http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_path_help:
|
||||
When not specified, this value defaults to an empty string. It is passed to the `<path>` portion of this command which starts serve or funnel:<br>
|
||||
`tailscale [serve|funnel] --bg --<protocol><protocol_port>`**`<path>`** `http://localhost:<serve_port><local_path>`<br>
|
||||
For more details see the <a href="https://tailscale.com/kb/1242/tailscale-serve" target="_blank">Tailscale Serve Command Line</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_serve_webui_help:
|
||||
If <b>Serve</b> is enabled this will be an https url with a proper domain name that is accessible over your Tailnet, no port needed!
|
||||
|
||||
If <b>Funnel</b> is enabled the same url will be available on the Internet.
|
||||
|
||||
If they are disabled then the url will be generated from the container's main "Web UI" field, but modified to use the Tailscale IP. If the wrong port is specified here then switch from "Basic View" to "Advanced View" and review the "Web UI" field for this container.
|
||||
:end
|
||||
|
||||
:docker_tailscale_advertise_routes_help:
|
||||
If desired, specify any routes that should be passed to the **`--advertise-routes=`** parameter when running **`tailscale up`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1019/subnets#connect-to-tailscale-as-a-subnet-router" target="_blank">Subnet routers</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_daemon_extra_params_help:
|
||||
Specify any extra parameters to pass when starting **`tailscaled`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1278/tailscaled" target="_blank">tailscaled</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_extra_param_help:
|
||||
Specify any extra parameters to pass when running **`tailscale up`**.
|
||||
For more details see the <a href="https://tailscale.com/kb/1080/cli#up" target="_blank">Tailscale CLI</a> documentation.
|
||||
:end
|
||||
|
||||
:docker_tailscale_statedir_help:
|
||||
If state directory detection fails on startup, you can specify a persistent directory in the container to override automatic detection.
|
||||
:end
|
||||
|
||||
:docker_tailscale_troubleshooting_packages_help:
|
||||
Enable this to install `ping`, `nslookup`, and `curl` into the container to help troubleshoot networking issues. Once the issues are resolved we recommend disabling this to reduce the size of the container.
|
||||
:end
|
||||
|
||||
:docker_privileged_help:
|
||||
For containers that require the use of host-device access directly or need full exposure to host capabilities, this option will need to be selected.
|
||||
For more information, see this link: <a href="https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities" target="_blank">https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities</a>
|
||||
@@ -2399,7 +2566,7 @@ Note to Cloudflare users: the Cloudflare proxy is designed for http traffic, it
|
||||
|
||||
:wg_local_server_uses_nat_help:
|
||||
When NAT is enabled, the server uses its own LAN address when forwarding traffic from the tunnel to other devices in the LAN network.
|
||||
Use this setting when no router modifications are desired, but this approach doesn't work with Docker containers using custom IP addressess.
|
||||
Use this setting when no router modifications are desired, but this approach doesn't work with Docker containers using custom IP addresses.
|
||||
|
||||
When NAT is disabled, the server uses the WireGuard tunnel address when forwarding traffic.
|
||||
In this case it is required that the default gateway (router) has a static route configured to refer tunnel address back to the server.
|
||||
|
@@ -14,10 +14,15 @@ Tag="battery-3"
|
||||
* The above copyright notice and this permission notice shall be included in
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
$docroot = $docroot ?? $_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp';
|
||||
require_once "$docroot/webGui/include/Wrappers.php";
|
||||
?>
|
||||
<script>
|
||||
function getUPSstatus() {
|
||||
$.post('/plugins/dynamix.apcupsd/include/UPSstatus.php',{level:<?=$cfg['BATTERYLEVEL']?>,runtime:<?=$cfg['MINUTES']?>},function(data) {
|
||||
var batteryLevel = "<?= _var($cfg,'BATTERYLEVEL',0) ?>";
|
||||
var batteryRuntime = "<?= _var($cfg,'MINUTES',0) ?>";
|
||||
|
||||
$.post('/plugins/dynamix.apcupsd/include/UPSstatus.php',{level:batteryLevel,runtime:batteryRuntime},function(data) {
|
||||
data = data.split('\n');
|
||||
$('#ups_summary').html(data[0]);
|
||||
$('#ups_status').html(data[1]);
|
||||
|
@@ -91,6 +91,11 @@ _(Device)_:
|
||||
|
||||
:apc_ups_device_help:
|
||||
|
||||
_(Override UPS Capacity (Watts))_:
|
||||
: <input type="number" name="OVERRIDE_UPS_CAPACITY" maxlength="5" class="narrow" value="<?=htmlspecialchars($cfg['OVERRIDE_UPS_CAPACITY']);?>">
|
||||
|
||||
:apc_ups_override_ups_capacity_help:
|
||||
|
||||
_(Battery level to initiate shutdown)_ (%):
|
||||
: <input type="text" name="BATTERYLEVEL" class="narrow" maxlength="3" value="<?=htmlspecialchars($cfg['BATTERYLEVEL']);?>">
|
||||
|
||||
|
@@ -3,6 +3,7 @@ UPSCABLE="usb"
|
||||
CUSTOMUPSCABLE=""
|
||||
UPSTYPE="usb"
|
||||
DEVICE=""
|
||||
OVERRIDE_UPS_CAPACITY=""
|
||||
BATTERYLEVEL="10"
|
||||
MINUTES="10"
|
||||
TIMEOUT="0"
|
||||
|
@@ -18,6 +18,10 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
$_SERVER['REQUEST_URI'] = 'settings';
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
require_once "$docroot/webGui/include/Helpers.php";
|
||||
$cfg = parse_plugin_cfg('dynamix.apcupsd');
|
||||
$overrideUpsCapacity = (int) htmlspecialchars($cfg['OVERRIDE_UPS_CAPACITY'] ?: 0);
|
||||
|
||||
$state = [
|
||||
'ONLINE' => _('Online'),
|
||||
'SLAVE' => '('._('slave').')',
|
||||
@@ -34,7 +38,8 @@ $state = [
|
||||
$red = "class='red-text'";
|
||||
$green = "class='green-text'";
|
||||
$orange = "class='orange-text'";
|
||||
$status = array_fill(0,7,"<td>-</td>");
|
||||
$defaultCell = "<td>-</td>";
|
||||
$status = array_fill(0,7,$defaultCell);
|
||||
$result = [];
|
||||
$level = $_POST['level'] ?: 10;
|
||||
$runtime = $_POST['runtime'] ?: 5;
|
||||
@@ -86,6 +91,14 @@ if (file_exists("/var/run/apcupsd.pid")) {
|
||||
if ($i%2==1) $result[] = "</tr>";
|
||||
}
|
||||
if (count($rows)%2==1) $result[] = "<td></td><td></td></tr>";
|
||||
|
||||
// If the override is defined, override the power value, using the same implementation as above.
|
||||
// This is a better implementation, as it allows the existing Unraid code to work with the override.
|
||||
if ($overrideUpsCapacity > 0) {
|
||||
$power = $overrideUpsCapacity;
|
||||
$status[4] = $power>0 ? "<td $green>$power W</td>" : "<td $red>$power W</td>";
|
||||
}
|
||||
|
||||
if ($power && isset($load)) $status[5] = ($load<90 ? "<td $green>" : "<td $red>").round($power*$load/100)." W (".$status[5].")</td>";
|
||||
elseif (isset($load)) $status[5] = ($load<90 ? "<td $green>" : "<td $red>").$status[5]."</td>";
|
||||
$status[6] = isset($output) ? ((!$volt || ($minv<$output && $output<$maxv) ? "<td $green>" : "<td $red>").$status[6].(isset($freq) ? " ~ $freq Hz" : "")."</td>") : $status[6];
|
||||
|
@@ -31,8 +31,8 @@ $cpus = cpu_list();
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/plugins/dynamix.docker.manager/styles/style-$theme.css")?>">
|
||||
|
||||
<table id="docker_containers" class="tablesorter shift">
|
||||
<thead><tr><th><a id="resetsort" class="nohand" onclick="resetSorting()" title="_(Reset sorting)_"><i class="fa fa-th-list"></i></a>_(Application)_</th><th>_(Version)_</th><th>_(Network)_</th><th>_(Port Mappings)_ <small>(_(App to Host)_)</small></th><th>_(Volume Mappings)_ <small>(_(App to Host)_)</small></th><th class="load advanced">_(CPU & Memory load)_</th><th class="nine">_(Autostart)_</th><th class="five">_(Uptime)_</th></tr></thead>
|
||||
<tbody id="docker_list"><tr><td colspan='8'></td></tr></tbody>
|
||||
<thead><tr><th><a id="resetsort" class="nohand" onclick="resetSorting()" title="_(Reset sorting)_"><i class="fa fa-th-list"></i></a>_(Application)_</th><th>_(Version)_</th><th>_(Network)_</th><th>_(Container IP)_</th><th>_(Container Port)_</th><th>_(LAN IP:Port)_</th><th>_(Volume Mappings)_ <small>(_(App to Host)_)</small></th><th class="load advanced">_(CPU & Memory load)_</th><th class="nine">_(Autostart)_</th><th class="five">_(Uptime)_</th></tr></thead>
|
||||
<tbody id="docker_list"><tr><td colspan='9'></td></tr></tbody>
|
||||
</table>
|
||||
<input type="button" onclick="addContainer()" value="_(Add Container)_" style="display:none">
|
||||
<input type="button" onclick="startAll()" value="_(Start All)_" style="display:none">
|
||||
@@ -183,3 +183,4 @@ window.onunload = function(){
|
||||
dockerload.stop();
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@@ -103,6 +103,9 @@ function base_net($route) {
|
||||
return substr(explode('/',$route)[0],0,-2);
|
||||
}
|
||||
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
|
||||
//Check if docker.cfg not exists
|
||||
$no_dockercfg = !is_file('/boot/config/docker.cfg');
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
@@ -116,6 +119,7 @@ $bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
<input type="hidden" name="#cleanup" value="true">
|
||||
<input type="hidden" name="DOCKER_CUSTOM_NETWORKS" value="<?=implode(' ',$unset)?> ">
|
||||
<input type="hidden" name="DOCKER_IMAGE_FILE" value="<?=_var($dockercfg,'DOCKER_IMAGE_FILE')?>">
|
||||
|
||||
_(Enable Docker)_:
|
||||
: <select id="DOCKER_ENABLED" name="DOCKER_ENABLED">
|
||||
<?=mk_option(_var($dockercfg,'DOCKER_ENABLED'), 'no', _('No'))?>
|
||||
@@ -144,6 +148,11 @@ _(Docker Stop Timeout)_ (_(seconds)_):
|
||||
|
||||
:docker_timeout_help:
|
||||
|
||||
_(Docker PID Limit)_:
|
||||
: <input class='narrow' id="DOCKER_PID_LIMIT" type="number" name="DOCKER_PID_LIMIT" min='1' value="<?=_var($dockercfg,'DOCKER_PID_LIMIT')?>" placeholder="2048">
|
||||
|
||||
:docker_pid_limit_help:
|
||||
|
||||
<?if ($DockerStopped):?>
|
||||
|
||||
_(Docker data-root)_:
|
||||
@@ -182,8 +191,25 @@ _(Docker directory)_:
|
||||
:docker_vdisk_directory_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="backingfs_type" style="display:none">
|
||||
_(Docker storage driver)_:
|
||||
: <select id="DOCKER_BACKINGFS" name="DOCKER_BACKINGFS" onchange="updateBackingFS(this.value)">
|
||||
<?=mk_option(_var($dockercfg,'DOCKER_BACKINGFS'), 'overlay2', _('overlay2'))?>
|
||||
<?=mk_option(_var($dockercfg,'DOCKER_BACKINGFS'), 'native', _('native'))?>
|
||||
</select>
|
||||
<?if ($var['fsState'] != "Started"):?>
|
||||
<span id="WARNING_BACKINGFS" style="display:none;"><i class="fa fa-warning icon warning"></i>_(Only modify if this is a new installation since this can lead to unwanted behaviour!)_</span>
|
||||
<?elseif (is_dir(_var($dockercfg,'DOCKER_IMAGE_FILE'))):?>
|
||||
<span id="WARNING_BACKINGFS" style="display:none;"><i class="fa fa-warning icon warning"></i>_(Switching the Storage Driver requires to delete and rebuild the Docker directory manually!)_</span>
|
||||
<?endif;?>
|
||||
|
||||
:docker_storage_driver_help:
|
||||
|
||||
</div>
|
||||
|
||||
_(Default appdata storage location)_:
|
||||
: <input type="text" id="DOCKER_APP_CONFIG_PATH" name="DOCKER_APP_CONFIG_PATH" autocomplete="off" spellcheck="false" value="<?=_var($dockercfg,'DOCKER_APP_CONFIG_PATH')?>" placeholder="_(e.g.)_ /mnt/user/appdata/" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" data-pickfolders="true" pattern="^[^\\]*/$">
|
||||
: <input type="text" id="DOCKER_APP_CONFIG_PATH" name="DOCKER_APP_CONFIG_PATH" autocomplete="off" spellcheck="false" value="<?=_var($dockercfg,'DOCKER_APP_CONFIG_PATH')?>" placeholder="_(e.g.)_ /mnt/user/appdata/" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="/mnt" data-pickfolders="true" pattern="^[^\\]*/$">
|
||||
<?if ($var['fsState'] != "Started"):?>
|
||||
<span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
|
||||
<?elseif (!is_dir(_var($dockercfg,'DOCKER_APP_CONFIG_PATH'))):?>
|
||||
@@ -412,6 +438,7 @@ _(IPv6 custom network on interface)_ <?=$network?> (_(optional)_):
|
||||
</div>
|
||||
<?else: /* DOCKER STARTED */?>
|
||||
|
||||
|
||||
_(Docker version)_:
|
||||
: <?$arrInfo = $DockerClient->getInfo(); echo $arrInfo['Version']?>
|
||||
|
||||
@@ -427,6 +454,11 @@ _(Docker directory)_:
|
||||
|
||||
:docker_vdisk_location_active_help:
|
||||
|
||||
_(Docker storage driver)_:
|
||||
: <?=_var($dockercfg,'DOCKER_BACKINGFS')?>
|
||||
|
||||
:docker_storage_driver_active_help:
|
||||
|
||||
_(Default appdata storage location)_:
|
||||
: <?=_var($dockercfg,'DOCKER_APP_CONFIG_PATH')?>
|
||||
|
||||
@@ -586,6 +618,9 @@ function prepareDocker(form) {
|
||||
$(form).find('input[name="DOCKER_IMAGE_FILE"]').val($('#DOCKER_IMAGE_TYPE').val()=='folder' ? $("#DOCKER_IMAGE_FILE2").val() : $("#DOCKER_IMAGE_FILE1").val());
|
||||
$("#DOCKER_IMAGE_FILE1").prop('disabled',true);
|
||||
$("#DOCKER_IMAGE_FILE2").prop('disabled',true);
|
||||
<?if ($no_dockercfg):?>
|
||||
$(form).find('input[name="DOCKER_BACKINGFS"]').val($('#DOCKER_IMAGE_TYPE').val()=='folder' ? $("#DOCKER_BACKINGFS").val() : $("#DOCKER_BACKINGFS").val('native'));
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
$(form).find('input:hidden[name^="DOCKER_DHCP_"]').each(function(){
|
||||
var id = '#'+$(this).attr('name')+'_';
|
||||
@@ -851,39 +886,54 @@ function btrfsScrub(path) {
|
||||
}
|
||||
});
|
||||
}
|
||||
var originalPath = $("#DOCKER_IMAGE_FILE2").val();
|
||||
function updateLocation(val) {
|
||||
var content1 = $("#DOCKER_IMAGE_FILE1");
|
||||
var content2 = $("#DOCKER_IMAGE_FILE2");
|
||||
var dropdown = $("#DOCKER_BACKINGFS");
|
||||
var path = originalPath.split('/');
|
||||
switch (val) {
|
||||
case 'xfs':
|
||||
var path = content2.val().split('/');
|
||||
path.splice(-1,1);
|
||||
content1.val((path.join('/') + '/docker-xfs.img'));
|
||||
$('#vdisk_file').show('slow');
|
||||
$('#vdisk_dir').hide('slow');
|
||||
$('#backingfs_type').hide();
|
||||
content1.prop('disabled',false).trigger('change');
|
||||
content2.prop('disabled',true);
|
||||
dropdown.val('native');
|
||||
break;
|
||||
case 'folder':
|
||||
var path = content2.val().split('/');
|
||||
if (path[path.length-1]=='') path.splice(-2,2); else path.splice(-1,1);
|
||||
content2.val(path.join('/') + '/docker/');
|
||||
content2.val(path.join('/') + '/');
|
||||
$('#vdisk_file').hide('slow');
|
||||
$('#vdisk_dir').show('slow');
|
||||
$('#backingfs_type').show('slow');
|
||||
content1.prop('disabled',true);
|
||||
content2.prop('disabled',false).trigger('change');
|
||||
break;
|
||||
default:
|
||||
var path = content2.val().split('/');
|
||||
path.splice(-1,1);
|
||||
content1.val((path.join('/') + '/docker.img'));
|
||||
$('#vdisk_file').show('slow');
|
||||
$('#vdisk_dir').hide('slow');
|
||||
$('#backingfs_type').hide();
|
||||
content1.prop('disabled',false).trigger('change');
|
||||
content2.prop('disabled',true);
|
||||
dropdown.val('native');
|
||||
break;
|
||||
}
|
||||
}
|
||||
function updateBackingFS(val) {
|
||||
var backingfs = "<?= _var($dockercfg,'DOCKER_BACKINGFS') ?>";
|
||||
var warning = document.getElementById("WARNING_BACKINGFS");
|
||||
var checkbox = $(".deleteCheckbox");
|
||||
if (val !== backingfs) {
|
||||
warning.style.display = "inline";
|
||||
} else {
|
||||
warning.style.display = "none";
|
||||
}
|
||||
}
|
||||
function checkbox_state(value) {
|
||||
$.post('/plugins/dynamix.docker.manager/include/UpdateConfig.php',{action:'exist',name:value},function(state){state==0 ? $('.deleteLabel').fadeIn() : $('.deleteLabel').fadeOut();});
|
||||
}
|
||||
@@ -891,6 +941,7 @@ $(function() {
|
||||
<?if ($DockerStopped):?>
|
||||
if ($('#DOCKER_IMAGE_TYPE').val()=='folder') {
|
||||
$('#vdisk_dir').show();
|
||||
$('#backingfs_type').show();
|
||||
checkbox_state($("#DOCKER_IMAGE_FILE2").val());
|
||||
$("#DOCKER_IMAGE_FILE2").prop('disabled',false);
|
||||
} else {
|
||||
|
@@ -8,3 +8,5 @@ DOCKER_USER_NETWORKS="remove"
|
||||
DOCKER_ALLOW_ACCESS=""
|
||||
DOCKER_TIMEOUT=10
|
||||
DOCKER_READMORE="yes"
|
||||
DOCKER_PID_LIMIT=""
|
||||
DOCKER_BACKINGFS="overlay2"
|
||||
|
BIN
emhttp/plugins/dynamix.docker.manager/images/tailscale.png
Executable file
BIN
emhttp/plugins/dynamix.docker.manager/images/tailscale.png
Executable file
Binary file not shown.
After Width: | Height: | Size: 300 KiB |
@@ -141,11 +141,24 @@ if (isset($_POST['contName'])) {
|
||||
@unlink("$userTmplDir/my-$existing.xml");
|
||||
}
|
||||
}
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if (isset($_POST['contTailscale']) && $_POST['contTailscale'] == 'on') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
if ($startContainer) $cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
|
||||
execCommand($cmd);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
|
||||
echo '<div style="text-align:center"><button type="button" onclick="done()">'._('Done').'</button></div><br>';
|
||||
echo '<div style="text-align:center"><button type="button" onclick="openTerminal(\'docker\',\''.addslashes($Name).'\',\'.log\')">'._('View Container Log').'</button> <button type="button" onclick="done()">'._('Done').'</button></div><br>';
|
||||
goto END;
|
||||
}
|
||||
|
||||
@@ -169,6 +182,9 @@ if (isset($_GET['updateContainer'])){
|
||||
$xml = file_get_contents($tmpl);
|
||||
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
|
||||
$Registry = getXmlVal($xml, "Registry");
|
||||
$ExtraParams = getXmlVal($xml, "ExtraParams");
|
||||
$Network = getXmlVal($xml, "Network");
|
||||
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
|
||||
$oldImageID = $DockerClient->getImageID($Repository);
|
||||
// pull image
|
||||
if ($echo && !pullImage($Name, $Repository)) continue;
|
||||
@@ -182,8 +198,39 @@ if (isset($_GET['updateContainer'])){
|
||||
// attempt graceful stop of container first
|
||||
stopContainer($Name, false, $echo);
|
||||
}
|
||||
// check if network from another container is specified in xml (Network & ExtraParams)
|
||||
if (preg_match('/^container:(.*)/', $Network)) {
|
||||
$Net_Container = str_replace("container:", "", $Network);
|
||||
} else {
|
||||
preg_match("/--(net|network)=container:[^\s]+/", $ExtraParams, $NetworkParam);
|
||||
if (!empty($NetworkParam[0])) {
|
||||
$Net_Container = explode(':', $NetworkParam[0])[1];
|
||||
$Net_Container = str_replace(['"', "'"], '', $Net_Container);
|
||||
}
|
||||
}
|
||||
// check if the container still exists from which the network should be used, if it doesn't exist any more recreate container with network none and don't start it
|
||||
if (!empty($Net_Container)) {
|
||||
$Net_Container_ID = $DockerClient->getContainerID($Net_Container);
|
||||
if (empty($Net_Container_ID)) {
|
||||
$cmd = str_replace('/docker run -d ', '/docker create ', $cmd);
|
||||
$cmd = preg_replace("/--(net|network)=(['\"]?)container:[^'\"]+\\2/", "--network=none ", $cmd);
|
||||
}
|
||||
}
|
||||
// force kill container if still running after time-out
|
||||
if (empty($_GET['communityApplications'])) removeContainer($Name, $echo);
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if ($TS_Enabled == 'true') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
execCommand($cmd, $echo);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
$DockerClient->flushCaches();
|
||||
@@ -213,6 +260,9 @@ if (isset($_GET['xmlTemplate'])) {
|
||||
if (is_file($xmlTemplate)) {
|
||||
$xml = xmlToVar($xmlTemplate);
|
||||
$templateName = $xml['Name'];
|
||||
if (preg_match('/^container:(.*)/', $xml['Network'])) {
|
||||
$xml['Network'] = explode(':', $xml['Network'], 2);
|
||||
}
|
||||
if ($xmlType == 'default') {
|
||||
if (!empty($dockercfg['DOCKER_APP_CONFIG_PATH']) && file_exists($dockercfg['DOCKER_APP_CONFIG_PATH'])) {
|
||||
// override /config
|
||||
@@ -269,6 +319,153 @@ $authoring = $authoringMode ? 'advanced' : 'noshow';
|
||||
$disableEdit = $authoringMode ? 'false' : 'true';
|
||||
$showAdditionalInfo = '';
|
||||
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
|
||||
# Search for existing TAILSCALE_ entries in the Docker template
|
||||
$TS_existing_vars = false;
|
||||
foreach ($xml["Config"] as $config) {
|
||||
if (isset($config["Target"]) && strpos($config["Target"], "TAILSCALE_") === 0) {
|
||||
$TS_existing_vars = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
# Look for Exit Nodes if Tailscale plugin is installed
|
||||
$ts_exit_nodes = [];
|
||||
$ts_en_check = false;
|
||||
if (file_exists('/usr/local/sbin/tailscale') && exec('pgrep --ns $$ -f "/usr/local/sbin/tailscaled"')) {
|
||||
exec('tailscale exit-node list', $ts_exit_node_list, $retval);
|
||||
if ($retval === 0) {
|
||||
foreach ($ts_exit_node_list as $line) {
|
||||
if (!empty(trim($line))) {
|
||||
if (preg_match('/^(\d+\.\d+\.\d+\.\d+)\s+(.+)$/', trim($line), $matches)) {
|
||||
$parts = preg_split('/\s+/', $matches[2]);
|
||||
$ts_exit_nodes[] = [
|
||||
'ip' => $matches[1],
|
||||
'hostname' => $parts[0],
|
||||
'country' => $parts[1],
|
||||
'city' => $parts[2],
|
||||
'status' => $parts[3]
|
||||
];
|
||||
$ts_en_check = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Try to detect port from WebUI and set webui_url
|
||||
$TSwebuiport = '';
|
||||
$webui_url = '';
|
||||
if (empty($xml['TailscalePort'])) {
|
||||
if (!empty($xml['WebUI'])) {
|
||||
$webui_url = parse_url($xml['WebUI']);
|
||||
preg_match('/:(\d+)\]/', $webui_url['host'], $matches);
|
||||
$TSwebuiport = $matches[1];
|
||||
}
|
||||
}
|
||||
|
||||
$TS_raw = [];
|
||||
$TS_container_raw = [];
|
||||
$TS_HostNameWarning = "";
|
||||
$TS_HTTPSDisabledWarning = "";
|
||||
$TS_ExitNodeNeedsApproval = false;
|
||||
$TS_MachinesLink = "https://login.tailscale.com/admin/machines/";
|
||||
$TS_DirectMachineLink = $TS_MachinesLink;
|
||||
$TS_HostNameActual = "";
|
||||
$TS_not_approved = "";
|
||||
// Get Tailscale information and create arrays/variables
|
||||
exec("docker exec -i ".$xml['Name']." /bin/sh -c \"tailscale status --peers=false --json\"", $TS_raw);
|
||||
$TS_no_peers = json_decode(implode('', $TS_raw),true);
|
||||
$TS_container = json_decode(implode('', $TS_raw),true);
|
||||
$TS_container = $TS_container['Self'];
|
||||
if (!empty($TS_no_peers) && !empty($TS_container)) {
|
||||
// define the direct link to this machine on the Tailscale website
|
||||
if (!empty($TS_container['TailscaleIPs']) && !empty($TS_container['TailscaleIPs'][0])) {
|
||||
$TS_DirectMachineLink = $TS_MachinesLink.$TS_container['TailscaleIPs'][0];
|
||||
}
|
||||
// warn if MagicDNS or HTTPS is disabled
|
||||
if (empty($TS_no_peers['CurrentTailnet']['MagicDNSEnabled']) || !$TS_no_peers['CurrentTailnet']['MagicDNSEnabled'] || empty($TS_no_peers['CertDomains']) || empty($TS_no_peers['CertDomains'][0])) {
|
||||
$TS_HTTPSDisabledWarning = "<span><b><a href='https://tailscale.com/kb/1153/enabling-https' target='_blank'>Enable HTTPS</a> on your Tailscale account to use Tailscale Serve/Funnel.</b></span>";
|
||||
}
|
||||
// In $TS_container, 'HostName' is what the user requested, need to parse 'DNSName' to find the actual HostName in use
|
||||
$TS_DNSName = _var($TS_container,'DNSName','');
|
||||
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
|
||||
// compare the actual HostName in use to the one in the XML file
|
||||
if (strcasecmp($TS_HostNameActual, _var($xml, 'TailscaleHostname')) !== 0 && !empty($TS_DNSName)) {
|
||||
// they are different, show a warning
|
||||
$TS_HostNameWarning = "<span><b>Warning: the actual Tailscale hostname is '".$TS_HostNameActual."'</b></span>";
|
||||
}
|
||||
// If this is an Exit Node, show warning if it still needs approval
|
||||
if (_var($xml,'TailscaleIsExitNode') == 'true' && _var($TS_container, 'ExitNodeOption') === false) {
|
||||
$TS_ExitNodeNeedsApproval = true;
|
||||
}
|
||||
//Check for key expiry
|
||||
if(!empty($TS_container['KeyExpiry'])) {
|
||||
$TS_expiry = new DateTime($TS_container['KeyExpiry']);
|
||||
$current_Date = new DateTime();
|
||||
$TS_expiry_diff = $current_Date->diff($TS_expiry);
|
||||
}
|
||||
// Check for non approved routes
|
||||
if(!empty($xml['TailscaleRoutes'])) {
|
||||
$TS_advertise_routes = str_replace(' ', '', $xml['TailscaleRoutes']);
|
||||
if (empty($TS_container['PrimaryRoutes'])) {
|
||||
$TS_container['PrimaryRoutes'] = [];
|
||||
}
|
||||
$routes = explode(',', $TS_advertise_routes);
|
||||
foreach ($routes as $route) {
|
||||
if (!in_array($route, $TS_container['PrimaryRoutes'])) {
|
||||
$TS_not_approved .= " " . $route;
|
||||
}
|
||||
}
|
||||
}
|
||||
// Check for exit nodes if ts_en_check was not already done
|
||||
if (!$ts_en_check) {
|
||||
exec("docker exec -i ".$xml['Name']." /bin/sh -c \"tailscale exit-node list\"", $ts_exit_node_list, $retval);
|
||||
if ($retval === 0) {
|
||||
foreach ($ts_exit_node_list as $line) {
|
||||
if (!empty(trim($line))) {
|
||||
if (preg_match('/^(\d+\.\d+\.\d+\.\d+)\s+(.+)$/', trim($line), $matches)) {
|
||||
$parts = preg_split('/\s+/', $matches[2]);
|
||||
$ts_exit_nodes[] = [
|
||||
'ip' => $matches[1],
|
||||
'hostname' => $parts[0],
|
||||
'country' => $parts[1],
|
||||
'city' => $parts[2],
|
||||
'status' => $parts[3]
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// Construct WebUI URL on container template page
|
||||
// Check if webui_url, Tailscale WebUI and MagicDNS are not empty and make sure that MagicDNS is enabled
|
||||
if (!empty($webui_url) && !empty($xml['TailscaleWebUI']) && (!empty($TS_no_peers['CurrentTailnet']['MagicDNSEnabled']) || $TS_no_peers['CurrentTailnet']['MagicDNSEnabled'])) {
|
||||
// Check if serve or funnel are enabled by checking for [hostname] and replace string with TS_DNSName
|
||||
if (!empty($xml['TailscaleWebUI']) && strpos($xml['TailscaleWebUI'], '[hostname]') !== false && isset($TS_DNSName)) {
|
||||
$TS_webui_url = str_replace("[hostname][magicdns]", rtrim($TS_DNSName, '.'), $xml['TailscaleWebUI']);
|
||||
// Check if serve is disabled, construct url with port, path and query if present and replace [noserve] with url
|
||||
} elseif (strpos($xml['TailscaleWebUI'], '[noserve]') !== false && isset($TS_container['TailscaleIPs'])) {
|
||||
$ipv4 = '';
|
||||
foreach ($TS_container['TailscaleIPs'] as $ip) {
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$ipv4 = $ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($ipv4)) {
|
||||
$webui_url = isset($xml['WebUI']) ? parse_url($xml['WebUI']) : '';
|
||||
$webui_port = (preg_match('/\[PORT:(\d+)\]/', $xml['WebUI'], $matches)) ? ':' . $matches[1] : '';
|
||||
$webui_path = $webui_url['path'] ?? '';
|
||||
$webui_query = isset($webui_url['query']) ? '?' . $webui_url['query'] : '';
|
||||
$TS_webui_url = 'http://' . $ipv4 . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
// Check if TailscaleWebUI in the xml is custom and display instead
|
||||
} elseif (strpos($xml['TailscaleWebUI'], '[hostname]') === false && strpos($xml['TailscaleWebUI'], '[noserve]') === false) {
|
||||
$TS_webui_url = $xml['TailscaleWebUI'];
|
||||
}
|
||||
}
|
||||
}
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.ui.css")?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/webGui/styles/jquery.switchbutton.css")?>">
|
||||
@@ -423,6 +620,9 @@ function addConfigPopup() {
|
||||
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button>";
|
||||
}
|
||||
Opts.Number = confNum;
|
||||
if (Opts.Type == "Device") {
|
||||
Opts.Target = Opts.Value;
|
||||
}
|
||||
newConf = makeConfig(Opts);
|
||||
$("#configLocation").append(newConf);
|
||||
reloadTriggers();
|
||||
@@ -491,6 +691,9 @@ function editConfigPopup(num,disabled) {
|
||||
}
|
||||
|
||||
Opts.Number = num;
|
||||
if (Opts.Type == "Device") {
|
||||
Opts.Target = Opts.Value;
|
||||
}
|
||||
newConf = makeConfig(Opts);
|
||||
if (config.hasClass("config_"+Opts.Display)) {
|
||||
config.html(newConf);
|
||||
@@ -666,6 +869,16 @@ $(function() {
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<?php
|
||||
foreach ($xml["Config"] as $config) {
|
||||
if (isset($config["Target"]) && strpos($config["Target"], "TAILSCALE_") === 0) {
|
||||
$tailscaleTargetFound = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
?>
|
||||
|
||||
<div id="canvas">
|
||||
<form markdown="1" method="POST" autocomplete="off" onsubmit="prepareConfig(this)">
|
||||
<input type="hidden" name="csrf_token" value="<?=$var['csrf_token']?>">
|
||||
@@ -706,7 +919,7 @@ _(Template)_:
|
||||
|
||||
<div markdown="1" class="<?=$showAdditionalInfo?>">
|
||||
_(Name)_:
|
||||
: <input type="text" name="contName" pattern="[a-zA-Z0-9][a-zA-Z0-9_.-]+" required>
|
||||
: <input type="text" name="contName" pattern="[a-zA-Z0-9][a-zA-Z0-9_.\-]+" required>
|
||||
|
||||
:docker_client_name_help:
|
||||
|
||||
@@ -858,6 +1071,7 @@ _(Network Type)_:
|
||||
: <select name="contNetwork" onchange="showSubnet(this.value)">
|
||||
<?=mk_option(1,'bridge',_('Bridge'))?>
|
||||
<?=mk_option(1,'host',_('Host'))?>
|
||||
<?=mk_option(1,'container',_('Container'))?>
|
||||
<?=mk_option(1,'none',_('None'))?>
|
||||
<?foreach ($custom as $network):?>
|
||||
<?$name = $network;
|
||||
@@ -881,6 +1095,275 @@ _(Fixed IP address)_ (_(optional)_):
|
||||
:docker_fixed_ip_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="netCONT noshow">
|
||||
_(Container Network)_:
|
||||
: <select name="netCONT" id="netCONT">
|
||||
<?php
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
if ($ct['Name'] !== $xml['Name']) {
|
||||
$list[] = $ct['Name'];
|
||||
echo mk_option($ct['Name'], $ct['Name'], $ct['Name']);
|
||||
}
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
|
||||
:docker_container_network_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdivider noshow"><hr></div>
|
||||
|
||||
<?if ($TS_existing_vars == 'true'):?>
|
||||
<div markdown="1" class="TSwarning noshow">
|
||||
<b style="color:red;">_(WARNING)_</b>:
|
||||
: <b>_(Existing TAILSCALE variables found, please remove any existing modifications in the Template for Tailscale before using this function!)_</b>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?if (empty($xml['TailscaleEnabled'])):?>
|
||||
<div markdown="1" class="TSdeploy noshow">
|
||||
<b>_(First deployment)_</b>:
|
||||
: <p>_(After deploying the container, open the log and follow the link to register the container to your Tailnet!)_</p>
|
||||
</div>
|
||||
|
||||
<?if (!file_exists('/usr/local/sbin/tailscale')):?>
|
||||
<div markdown="1" class="TSdeploy noshow">
|
||||
<b>_(Recommendation)_</b>:
|
||||
: <p>_(For the best experience with Tailscale, install "Tailscale (Plugin)" from)_ <a href="/Apps" target='_blank'> Community Applications</a>.</p>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<div markdown="1">
|
||||
_(Use Tailscale)_:
|
||||
: <input type="checkbox" class="switch-on-off" name="contTailscale" id="contTailscale" <?php if (!empty($xml['TailscaleEnabled']) && $xml['TailscaleEnabled'] == 'true') echo 'checked'; ?> onchange="showTailscale(this)">
|
||||
|
||||
:docker_tailscale_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<b>_(NOTE)_</b>:
|
||||
: <i>_(This option will install Tailscale and dependencies into the container.)_</i>
|
||||
</div>
|
||||
|
||||
<?if($TS_ExitNodeNeedsApproval):?>
|
||||
<div markdown="1" class="TShostname noshow">
|
||||
<b>Warning:</b>
|
||||
: Exit Node not yet approved. Navigate to the <a href="<?=$TS_DirectMachineLink?>" target='_blank'>Tailscale website</a> and approve it.
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?if(!empty($TS_expiry_diff)):?>
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<b>_(Warning)_</b>:
|
||||
<?if($TS_expiry_diff->invert):?>
|
||||
: <b>Tailscale Key expired!</b> <a href="<?=$TS_MachinesLink?>" target='_blank'>Renew/Disable key expiry</a> for '<b><?=$TS_HostNameActual?></b>'.
|
||||
<?else:?>
|
||||
: Tailscale Key will expire in <b><?=$TS_expiry_diff->days?> days</b>! <a href="<?=$TS_MachinesLink?>" target='_blank'>Disable Key Expiry</a> for '<b><?=$TS_HostNameActual?></b>'.
|
||||
<?endif;?>
|
||||
<label>See <a href="https://tailscale.com/kb/1028/key-expiry" target='_blank'>key-expiry</a>.</label>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<?if(!empty($TS_not_approved)):?>
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<b>_(Warning)_</b>:
|
||||
: The following route(s) are not approved: <b><?=trim($TS_not_approved)?></b>
|
||||
</div>
|
||||
<?endif;?>
|
||||
|
||||
<div markdown="1" class="TShostname noshow">
|
||||
_(Tailscale Hostname)_:
|
||||
: <input type="text" pattern="[A-Za-z0-9_\-]*" name="TShostname" <?php if (!empty($xml['TailscaleHostname'])) echo 'value="' . $xml['TailscaleHostname'] . '"'; ?> placeholder="_(Hostname for the container)_"> <?=$TS_HostNameWarning?>
|
||||
|
||||
:docker_tailscale_hostname_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSisexitnode noshow">
|
||||
_(Be a Tailscale Exit Node)_:
|
||||
: <select name="TSisexitnode" id="TSisexitnode" onchange="showTailscale(this)">
|
||||
<?=mk_option(1,'false',_('No'))?>
|
||||
<?=mk_option(1,'true',_('Yes'))?>
|
||||
</select>
|
||||
<span id='TSisexitnode_msg' style='font-style: italic;'></span>
|
||||
|
||||
:docker_tailscale_be_exitnode_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSexitnodeip noshow">
|
||||
_(Use a Tailscale Exit Node)_:
|
||||
<?if($ts_en_check !== true && empty($ts_exit_nodes)):?>
|
||||
: <input type="text" name="TSexitnodeip" <?php if (!empty($xml['TailscaleExitNodeIP'])) echo 'value="' . $xml['TailscaleExitNodeIP'] . '"'; ?> placeholder="_(IP/Hostname from Exit Node)_" onchange="processExitNodeoptions(this)">
|
||||
<?else:?>
|
||||
: <select name="TSexitnodeip" id="TSexitnodeip" onchange="processExitNodeoptions(this)">
|
||||
<?=mk_option(1,'',_('None'))?>
|
||||
<?foreach ($ts_exit_nodes as $ts_exit_node):?>
|
||||
<?=$node_offline = $ts_exit_node['status'] === 'offline' ? ' - OFFLINE' : '';?>
|
||||
<?=mk_option(1,$ts_exit_node['ip'],$ts_exit_node['ip'] . ' - ' . $ts_exit_node['hostname'] . $node_offline)?>
|
||||
<?endforeach;?></select>
|
||||
<?endif;?>
|
||||
</select>
|
||||
<span id='TSexitnodeip_msg' style='font-style: italic;'></span>
|
||||
|
||||
:docker_tailscale_exitnode_ip_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSallowlanaccess noshow">
|
||||
_(Tailscale Allow LAN Access)_:
|
||||
: <select name="TSallowlanaccess" id="TSallowlanaccess">
|
||||
<?=mk_option(1,'false',_('No'))?>
|
||||
<?=mk_option(1,'true',_('Yes'))?>
|
||||
</select>
|
||||
<?=$TS_HTTPSDisabledWarning?>
|
||||
|
||||
:docker_tailscale_lanaccess_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSuserspacenetworking noshow">
|
||||
_(Tailscale Userspace Networking)_:
|
||||
: <select name="TSuserspacenetworking" id="TSuserspacenetworking" onchange="setExitNodeoptions()">
|
||||
<?=mk_option(1,'true',_('Enabled'))?>
|
||||
<?=mk_option(1,'false',_('Disabled'))?>
|
||||
</select>
|
||||
<span id='TSuserspacenetworking_msg' style='font-style: italic;'></span>
|
||||
|
||||
:docker_tailscale_userspace_networking_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSssh noshow">
|
||||
_(Enable Tailscale SSH)_:
|
||||
: <select name="TSssh" id="TSssh">
|
||||
<?=mk_option(1,'false',_('No'))?>
|
||||
<?=mk_option(1,'true',_('Yes'))?>
|
||||
</select>
|
||||
|
||||
:docker_tailscale_ssh_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserve noshow">
|
||||
_(Tailscale Serve)_:
|
||||
: <select name="TSserve" id="TSserve" onchange="showServe(this.value)">
|
||||
<?=mk_option(1,'no',_('No'))?>
|
||||
<?=mk_option(1,'serve',_('Serve'))?>
|
||||
<?=mk_option(1,'funnel',_('Funnel'))?>
|
||||
</select>
|
||||
<?php if (!empty($TS_webui_url)) echo '<label for="TSserve"><a href="' . $TS_webui_url . '" target="_blank">' . $TS_webui_url . '</a></label>'; ?>
|
||||
|
||||
:docker_tailscale_serve_mode_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserveport noshow">
|
||||
_(Tailscale Serve Port)_:
|
||||
: <input type="text" name="TSserveport" value="<?php echo !empty($xml['TailscaleServePort']) ? $xml['TailscaleServePort'] : (!empty($TSwebuiport) ? $TSwebuiport : ''); ?>" placeholder="_(Will be detected automatically if possible)_">
|
||||
|
||||
:docker_tailscale_serve_port_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSadvanced noshow">
|
||||
_(Tailscale Show Advanced Settings)_:
|
||||
: <input type="checkbox" name="TSadvanced" class="switch-on-off" onchange="showTSAdvanced(this.checked)">
|
||||
|
||||
:docker_tailscale_show_advanced_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSservelocalpath noshow">
|
||||
_(Tailscale Serve Local Path)_:
|
||||
: <input type="text" name="TSservelocalpath" <?php if (!empty($xml['TailscaleServeLocalPath'])) echo 'value="' . $xml['TailscaleServeLocalPath'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_serve_local_path_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserveprotocol noshow">
|
||||
_(Tailscale Serve Protocol)_:
|
||||
: <input type="text" name="TSserveprotocol" <?php if (!empty($xml['TailscaleServeProtocol'])) echo 'value="' . $xml['TailscaleServeProtocol'] . '"'; ?> placeholder="_(Leave empty if unsure, defaults to https)_">
|
||||
|
||||
:docker_tailscale_serve_protocol_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSserveprotocolport noshow">
|
||||
_(Tailscale Serve Protocol Port)_:
|
||||
: <input type="text" name="TSserveprotocolport" <?php if (!empty($xml['TailscaleServeProtocolPort'])) echo 'value="' . $xml['TailscaleServeProtocolPort'] . '"'; ?> placeholder="_(Leave empty if unsure, defaults to =443)_">
|
||||
|
||||
:docker_tailscale_serve_protocol_port_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSservepath noshow">
|
||||
_(Tailscale Serve Path)_:
|
||||
: <input type="text" name="TSservepath" <?php if (!empty($xml['TailscaleServePath'])) echo 'value="' . $xml['TailscaleServePath'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_serve_path_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSwebui noshow">
|
||||
_(Tailscale WebUI)_:
|
||||
: <input type="text" name="TSwebui" value="<?php echo !empty($TS_webui_url) ? $TS_webui_url : ''; ?>" placeholder="Will be determined automatically if possible" disabled>
|
||||
<input type="hidden" name="TSwebui" <?php if (!empty($xml['TailscaleWebUI'])) echo 'value="' . $xml['TailscaleWebUI'] . '"'; ?>>
|
||||
|
||||
:docker_tailscale_serve_webui_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSroutes noshow">
|
||||
_(Tailscale Advertise Routes)_:
|
||||
: <input type="text" pattern="[0-9:., ]*" name="TSroutes" <?php if (!empty($xml['TailscaleRoutes'])) echo 'value="' . $xml['TailscaleRoutes'] . '"'?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_advertise_routes_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdaemonparams noshow">
|
||||
_(Tailscale Daemon Parameters)_:
|
||||
: <input type="text" name="TSdaemonparams" <?php if (!empty($xml['TailscaleDParams'])) echo 'value="' . $xml['TailscaleDParams'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_daemon_extra_params_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSextraparams noshow">
|
||||
_(Tailscale Extra Parameters)_:
|
||||
: <input type="text" name="TSextraparams" <?php if (!empty($xml['TailscaleParams'])) echo 'value="' . $xml['TailscaleParams'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_extra_param_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSstatedir noshow">
|
||||
_(Tailscale State Directory)_:
|
||||
: <input type="text" name="TSstatedir" <?php if (!empty($xml['TailscaleStateDir'])) echo 'value="' . $xml['TailscaleStateDir'] . '"'; ?> placeholder="_(Leave empty if unsure)_">
|
||||
|
||||
:docker_tailscale_statedir_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TStroubleshooting noshow">
|
||||
_(Tailscale Install Troubleshooting Packages)_:
|
||||
: <input type="checkbox" class="switch-on-off" name="TStroubleshooting" <?php if (!empty($xml['TailscaleTroubleshooting']) && $xml['TailscaleTroubleshooting'] == 'true') echo 'checked'; ?>>
|
||||
|
||||
:docker_tailscale_troubleshooting_packages_help:
|
||||
|
||||
</div>
|
||||
|
||||
<div markdown="1" class="TSdivider noshow">
|
||||
<hr>
|
||||
</div>
|
||||
|
||||
_(Console shell command)_:
|
||||
: <select name="contShell">
|
||||
<?=mk_option(1,'sh',_('Shell'))?>
|
||||
@@ -1013,9 +1496,222 @@ function showSubnet(bridge) {
|
||||
if (bridge.match(/^(bridge|host|none)$/i) !== null) {
|
||||
$('.myIP').hide();
|
||||
$('input[name="contMyIP"]').val('');
|
||||
$('.netCONT').hide();
|
||||
$('#netCONT').val('');
|
||||
} else if (bridge.match(/^(container)$/i) !== null) {
|
||||
$('.netCONT').show();
|
||||
$('#netCONT').val('<?php echo $xml['Network'][1]; ?>');
|
||||
$('.myIP').hide();
|
||||
$('input[name="contMyIP"]').val('');
|
||||
} else {
|
||||
$('.myIP').show();
|
||||
$('#myIP').html('Subnet: '+subnet[bridge]);
|
||||
$('.netCONT').hide();
|
||||
$('#netCONT').val('');
|
||||
}
|
||||
}
|
||||
|
||||
function processExitNodeoptions(value) {
|
||||
val = null;
|
||||
if (value.tagName.toLowerCase() === "input") {
|
||||
val = value.value.trim();
|
||||
} else if (value.tagName.toLowerCase() === "select") {
|
||||
val = value.value;
|
||||
}
|
||||
if (val) {
|
||||
$('.TSallowlanaccess').show();
|
||||
} else {
|
||||
$('#TSallowlanaccess').val('false');
|
||||
$('.TSallowlanaccess').hide();
|
||||
}
|
||||
setUserspaceNetworkOptions();
|
||||
setIsExitNodeoptions();
|
||||
}
|
||||
|
||||
function setUserspaceNetworkOptions() {
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "";
|
||||
value = null;
|
||||
|
||||
var network = $('select[name="contNetwork"]')[0].value;
|
||||
var isExitnode = $('#TSisexitnode').val();
|
||||
if (network == 'host' || isExitnode == 'true') {
|
||||
// in host mode or if this container is an Exit Node
|
||||
// then Userspace Networking MUST be enabled ('true')
|
||||
value = 'true';
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = true;
|
||||
optMessage = (isExitnode == 'true') ? "Enabled because this is an Exit Node" : "Enabled due to Docker "+network+" mode";
|
||||
} else {
|
||||
if (document.querySelector('input[name="TSexitnodeip"], select[name="TSexitnodeip"]').value) {
|
||||
// If an Exit Node IP is set, Userspace Networking MUST be disabled ('false')
|
||||
value = 'false';
|
||||
optTrueDisabled = true;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "Disabled due to use of an Exit Node";
|
||||
} else {
|
||||
// Exit Node IP is not set, user can decide whether to enable/disable Userspace Networking
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "";
|
||||
}
|
||||
}
|
||||
|
||||
$("#TSuserspacenetworking option[value='true']").prop("disabled", optTrueDisabled);
|
||||
$("#TSuserspacenetworking option[value='false']").prop("disabled", optFalseDisabled);
|
||||
if (value != null) $('#TSuserspacenetworking').val(value);
|
||||
$('#TSuserspacenetworking_msg').text(optMessage);
|
||||
setExitNodeoptions();
|
||||
}
|
||||
|
||||
function setIsExitNodeoptions() {
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "";
|
||||
value = null;
|
||||
|
||||
var network = $('select[name="contNetwork"]')[0].value;
|
||||
if (network == 'host') {
|
||||
// in host mode then this cannot be an Exit Node
|
||||
value = 'false';
|
||||
optTrueDisabled = true;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "Disabled due to Docker "+network+" mode";
|
||||
} else {
|
||||
if (document.querySelector('input[name="TSexitnodeip"], select[name="TSexitnodeip"]').value) {
|
||||
// If an Exit Node IP is set, this cannot be an Exit Node
|
||||
value = 'false';
|
||||
optTrueDisabled = true;
|
||||
optFalseDisabled = false;
|
||||
optMessage = "Disabled due to use of an Exit Node";
|
||||
} else {
|
||||
optTrueDisabled = false;
|
||||
optFalseDisabled = false;
|
||||
}
|
||||
}
|
||||
$("#TSisexitnode option[value='true']").prop("disabled", optTrueDisabled);
|
||||
$("#TSisexitnode option[value='false']").prop("disabled", optFalseDisabled);
|
||||
if (value != null) $('#TSisexitnode').val(value);
|
||||
$('#TSisexitnode_msg').text(optMessage);
|
||||
}
|
||||
|
||||
function setExitNodeoptions() {
|
||||
optMessage = "";
|
||||
var $exitNodeInput = $('input[name="TSexitnodeip"]');
|
||||
var $exitNodeSelect = $('#TSexitnodeip');
|
||||
// In host mode, TSuserspacenetworking is true
|
||||
if ($('#TSuserspacenetworking').val() == 'true') {
|
||||
// if TSuserspacenetworking is true, then TSexitnodeip must be "" and all options are disabled
|
||||
optMessage = "Disabled because Userspace Networking is Enabled.";
|
||||
$exitNodeInput.val('').prop('disabled', true); // Disable the input field
|
||||
$exitNodeSelect.val('').prop('disabled', true).find('option').each(function() {
|
||||
if ($(this).val() === "") {
|
||||
$(this).prop('disabled', false); // Enable the option with value=""
|
||||
} else {
|
||||
$(this).prop('disabled', true); // Disable all other options
|
||||
}
|
||||
});
|
||||
} else {
|
||||
// if TSuserspacenetworking is false, then all TSexitnodeip options can be enabled
|
||||
$exitNodeInput.prop('disabled', false); // Enable the input field
|
||||
$exitNodeSelect.prop('disabled', false).find('option').each(function() {
|
||||
$(this).prop('disabled', false); // Enable all options
|
||||
});
|
||||
}
|
||||
$('#TSexitnodeip_msg').text(optMessage);
|
||||
}
|
||||
|
||||
function showTSAdvanced(checked) {
|
||||
if (!checked) {
|
||||
<?if (!empty($TSwebuiport)):?>
|
||||
$('.TSserveport').hide();
|
||||
<?elseif (empty($contTailscale) || $contTailscale == 'false'):?>
|
||||
$('.TSserveport').hide();
|
||||
<?else:?>
|
||||
$('.TSserveport').show();
|
||||
<?endif;?>
|
||||
$('.TSdaemonparams').hide();
|
||||
$('.TSextraparams').hide();
|
||||
$('.TSstatedir').hide();
|
||||
$('.TSservepath').hide();
|
||||
$('.TSserveprotocol').hide();
|
||||
$('.TSserveprotocolport').hide();
|
||||
$('.TSservelocalpath').hide();
|
||||
$('.TSwebui').hide();
|
||||
$('.TStroubleshooting').hide();
|
||||
$('.TSroutes').hide();
|
||||
} else {
|
||||
$('.TSdaemonparams').show();
|
||||
$('.TSextraparams').show();
|
||||
$('.TSstatedir').show();
|
||||
$('.TSserveport').show();
|
||||
$('.TSservepath').show();
|
||||
$('.TSserveprotocol').show();
|
||||
$('.TSserveprotocolport').show();
|
||||
$('.TSservelocalpath').show();
|
||||
$('.TSwebui').show();
|
||||
$('.TStroubleshooting').show();
|
||||
$('.TSroutes').show();
|
||||
}
|
||||
}
|
||||
|
||||
function showTailscale(source) {
|
||||
if (!$.trim($('#TSallowlanaccess').val())) {
|
||||
$('#TSallowlanaccess').val('false');
|
||||
}
|
||||
if (!$.trim($('#TSserve').val())) {
|
||||
$('#TSserve').val('no');
|
||||
}
|
||||
checked = $('#contTailscale').prop('checked');
|
||||
if (!checked) {
|
||||
$('.TSdivider').hide();
|
||||
$('.TSwarning').hide();
|
||||
$('.TSdeploy').hide();
|
||||
$('.TSisexitnode').hide();
|
||||
$('.TShostname').hide();
|
||||
$('.TSexitnodeip').hide();
|
||||
$('.TSssh').hide();
|
||||
$('.TSallowlanaccess').hide();
|
||||
$('.TSdaemonparams').hide();
|
||||
$('.TSextraparams').hide();
|
||||
$('.TSstatedir').hide();
|
||||
$('.TSserve').hide();
|
||||
$('.TSuserspacenetworking').hide();
|
||||
$('.TSservepath').hide();
|
||||
$('.TSserveprotocol').hide();
|
||||
$('.TSserveprotocolport').hide();
|
||||
$('.TSservelocalpath').hide();
|
||||
$('.TSwebui').hide();
|
||||
$('.TSserveport').hide();
|
||||
$('.TSadvanced').hide();
|
||||
$('.TSroutes').hide();
|
||||
} else {
|
||||
// reset these vals back to what they were in the XML
|
||||
$('#TSssh').val('<?php echo !empty($xml['TailscaleSSH']) ? $xml['TailscaleSSH'] : 'false' ?>');
|
||||
$('#TSallowlanaccess').val('<?php echo $xml['TailscaleLANAccess']; ?>');
|
||||
$('#TSserve').val('<?php echo $xml['TailscaleServe']; ?>');
|
||||
$('#TSexitnodeip').val('<?php echo $xml['TailscaleExitNodeIP']; ?>');
|
||||
$('#TSuserspacenetworking').val('<?php echo !empty($xml['TailscaleUserspaceNetworking']) ? $xml['TailscaleUserspaceNetworking'] : 'false' ?>');
|
||||
<?if (empty($xml['TailscaleServe']) && !empty($TSwebuiport) && empty($xml['TailscaleServePort'])):?>
|
||||
$('#TSserve').val('serve');
|
||||
<?elseif (empty($xml['TailscaleServe']) && empty($TSwebuiport) && empty($xml['TailscaleServePort'])):?>
|
||||
$('#TSserve').val('no');
|
||||
<?endif;?>
|
||||
// don't reset this field if caller was the onchange event for this field
|
||||
if (source.id != 'TSisexitnode') $('#TSisexitnode').val('<?php echo !empty($xml['TailscaleIsExitNode']) ? $xml['TailscaleIsExitNode'] : 'false'; ?>');
|
||||
$('.TSisexitnode').show();
|
||||
$('.TShostname').show();
|
||||
$('.TSssh').show();
|
||||
$('.TSexitnodeip').show();
|
||||
$('.TSallowlanaccess').hide();
|
||||
$('.TSserve').show();
|
||||
$('.TSuserspacenetworking').show();
|
||||
processExitNodeoptions(document.querySelector('input[name="TSexitnodeip"], select[name="TSexitnodeip"]'));
|
||||
$('.TSdivider').show();
|
||||
$('.TSwarning').show();
|
||||
$('.TSdeploy').show();
|
||||
$('.TSadvanced').show();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1111,6 +1807,9 @@ $(function() {
|
||||
Opts.Buttons += "<button type='button' onclick='removeConfig("+confNum+")'>_(Remove)_</button>";
|
||||
}
|
||||
Opts.Number = confNum;
|
||||
if (Opts.Type == "Device") {
|
||||
Opts.Target = Opts.Value;
|
||||
}
|
||||
newConf = makeConfig(Opts);
|
||||
if (Opts.Display == 'advanced' || Opts.Display == 'advanced-hide') {
|
||||
$("#configLocationAdvanced").append(newConf);
|
||||
|
@@ -49,11 +49,11 @@ $port = file_exists('/sys/class/net/br0') ? 'BR0' : (file_exists('/sys/class/net
|
||||
// Docker configuration file - guaranteed to exist
|
||||
$docker_cfgfile = '/boot/config/docker.cfg';
|
||||
if (file_exists($docker_cfgfile)) {
|
||||
exec("grep -Pom2 '_SUBNET_|_{$port}(_[0-9]+)?=' $docker_cfgfile",$cfg);
|
||||
if (isset($cfg[0]) && $cfg[0]=='_SUBNET_' && empty($cfg[1])) {
|
||||
# interface has changed, update configuration
|
||||
exec("sed -ri 's/_(BR0|BOND0|ETH0)(_[0-9]+)?=/_{$port}\\2=/' $docker_cfgfile");
|
||||
}
|
||||
exec("grep -Pom2 '_SUBNET_|_{$port}(_[0-9]+)?=' $docker_cfgfile",$cfg);
|
||||
if (isset($cfg[0]) && $cfg[0]=='_SUBNET_' && empty($cfg[1])) {
|
||||
# interface has changed, update configuration
|
||||
exec("sed -ri 's/_(BR0|BOND0|ETH0)(_[0-9]+)?=/_{$port}\\2=/' $docker_cfgfile");
|
||||
}
|
||||
}
|
||||
|
||||
$defaults = (array)@parse_ini_file("$docroot/plugins/dynamix.docker.manager/default.cfg");
|
||||
@@ -255,7 +255,7 @@ class DockerTemplates {
|
||||
$doc = new DOMDocument();
|
||||
$doc->load($file['path']);
|
||||
if ($name) {
|
||||
if ($doc->getElementsByTagName('Name')->item(0)->nodeValue !== $name) continue;
|
||||
if (@$doc->getElementsByTagName('Name')->item(0)->nodeValue !== $name) continue;
|
||||
}
|
||||
$TemplateRepository = DockerUtil::ensureImageTag($doc->getElementsByTagName('Repository')->item(0)->nodeValue??'');
|
||||
if ($TemplateRepository && $TemplateRepository==$Repository) {
|
||||
@@ -292,13 +292,24 @@ class DockerTemplates {
|
||||
return $WebUI;
|
||||
}
|
||||
|
||||
private function getTailscaleJson($name) {
|
||||
$TS_raw = [];
|
||||
exec("docker exec -i ".$name." /bin/sh -c \"tailscale status --peers=false --json\" 2>/dev/null", $TS_raw);
|
||||
if (!empty($TS_raw)) {
|
||||
$TS_raw = implode("\n", $TS_raw);
|
||||
return json_decode($TS_raw, true);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
public function getAllInfo($reload=false,$com=true,$communityApplications=false) {
|
||||
global $dockerManPaths, $host;
|
||||
global $driver, $dockerManPaths, $host;
|
||||
$DockerClient = new DockerClient();
|
||||
$DockerUpdate = new DockerUpdate();
|
||||
//$DockerUpdate->verbose = $this->verbose;
|
||||
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
|
||||
$autoStart = array_map('var_split', @file($dockerManPaths['autostart-file'],FILE_IGNORE_NEW_LINES) ?: []);
|
||||
//$TS_dns = $this->getTailscaleDNS();
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$name = $ct['Name'];
|
||||
$image = $ct['Image'];
|
||||
@@ -307,9 +318,8 @@ class DockerTemplates {
|
||||
$tmp['paused'] = $ct['Paused'];
|
||||
$tmp['autostart'] = in_array($name,$autoStart);
|
||||
$tmp['cpuset'] = $ct['CPUset'];
|
||||
$tmp['url'] = $tmp['url'] ?? '';
|
||||
$tmp['url'] = $ct['Url'] ?? $tmp['url'] ?? '';
|
||||
// read docker label for WebUI & Icon
|
||||
if (isset($ct['Url']) && !$tmp['url']) $tmp['url'] = $ct['Url'];
|
||||
if (isset($ct['Icon'])) $tmp['icon'] = $ct['Icon'];
|
||||
if (isset($ct['Shell'])) $tmp['shell'] = $ct['Shell'];
|
||||
if (!$communityApplications) {
|
||||
@@ -322,8 +332,51 @@ class DockerTemplates {
|
||||
// non-templated webui, user specified
|
||||
$tmp['url'] = $webui;
|
||||
} else {
|
||||
$ip = ($ct['NetworkMode']=='host'||_var($port,'NAT')) ? $host : _var($port,'IP');
|
||||
if ($ct['NetworkMode']=='host') {
|
||||
$ip = $host;
|
||||
} elseif (isset($driver[$ct['NetworkMode']]) && ($driver[$ct['NetworkMode']] == 'ipvlan' || $driver[$ct['NetworkMode']] == 'macvlan')) {
|
||||
$ip = reset($ct['Networks'])['IPAddress'];
|
||||
} elseif (!is_null(_var($port,'PublicPort'))) {
|
||||
$ip = $host;
|
||||
} else {
|
||||
$ip = _var($port,'IP');
|
||||
}
|
||||
$tmp['url'] = $ip ? (strpos($tmp['url'],$ip)!==false ? $tmp['url'] : $this->getControlURL($ct, $ip, $tmp['url'])) : $tmp['url'];
|
||||
if (strpos($ct['NetworkMode'], 'container:') === 0)
|
||||
$tmp['url'] = '';
|
||||
}
|
||||
// Check if webui & ct TSurl is set, if set construct WebUI URL on Docker page
|
||||
$tmp['TSurl'] = '';
|
||||
if (!empty($webui) && !empty($ct['TSUrl'])) {
|
||||
$TS_no_peers = $this->getTailscaleJson($name);
|
||||
if (!empty($TS_no_peers) && (!empty($TS_no_peers['CurrentTailnet']['MagicDNSEnabled']) || $TS_no_peers['CurrentTailnet']['MagicDNSEnabled'])) {
|
||||
$TS_container = $TS_no_peers['Self'];
|
||||
$TS_DNSName = _var($TS_container,'DNSName','');
|
||||
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
|
||||
// Check if serve or funnel are enabled by checking for [hostname] and replace string with TS_DNSName
|
||||
if (strpos($ct['TSUrl'], '[hostname]') !== false && isset($TS_DNSName)) {
|
||||
$tmp['TSurl'] = str_replace("[hostname][magicdns]", rtrim($TS_DNSName, '.'), $ct['TSUrl']);
|
||||
// Check if serve is disabled, construct url with port, path and query if present and replace [noserve] with url
|
||||
} elseif (strpos($ct['TSUrl'], '[noserve]') !== false && isset($TS_container['TailscaleIPs'])) {
|
||||
$ipv4 = '';
|
||||
foreach ($TS_container['TailscaleIPs'] as $ip) {
|
||||
if (filter_var($ip, FILTER_VALIDATE_IP, FILTER_FLAG_IPV4)) {
|
||||
$ipv4 = $ip;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($ipv4)) {
|
||||
$webui_url = isset($webui) ? parse_url($webui) : '';
|
||||
$webui_port = (preg_match('/\[PORT:(\d+)\]/', $webui, $matches)) ? ':' . $matches[1] : '';
|
||||
$webui_path = $webui_url['path'] ?? '';
|
||||
$webui_query = isset($webui_url['query']) ? '?' . $webui_url['query'] : '';
|
||||
$tmp['TSurl'] = 'http://' . $ipv4 . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
// Check if TailscaleWebUI in the xml is custom and display instead
|
||||
} elseif (strpos($ct['TSUrl'], '[hostname]') === false && strpos($ct['TSUrl'], '[noserve]') === false) {
|
||||
$tmp['TSurl'] = $ct['TSUrl'];
|
||||
}
|
||||
}
|
||||
}
|
||||
if ( ($tmp['shell'] ?? false) == false )
|
||||
$tmp['shell'] = $this->getTemplateValue($image, 'Shell');
|
||||
@@ -338,8 +391,12 @@ class DockerTemplates {
|
||||
$tmp['updated'] = var_export($DockerUpdate->getUpdateStatus($image),true);
|
||||
}
|
||||
if (!$com) $tmp['updated'] = 'undef';
|
||||
if (empty($tmp['template']) || $reload) $tmp['template'] = $this->getUserTemplate($name);
|
||||
if ($reload) $DockerUpdate->updateUserTemplate($name);
|
||||
if ($ct['Manager'] !== 'dockerman')
|
||||
$tmp['template'] = null;
|
||||
else if (empty($tmp['template']) || $reload) {
|
||||
$tmp['template'] = $this->getUserTemplate($name);
|
||||
if ($reload) $DockerUpdate->updateUserTemplate($name);
|
||||
}
|
||||
//$this->debug("\n$name");
|
||||
//foreach ($tmp as $c => $d) $this->debug(sprintf(' %-10s: %s', $c, $d));
|
||||
}
|
||||
@@ -897,7 +954,7 @@ class DockerClient {
|
||||
}
|
||||
|
||||
public function getDockerContainers() {
|
||||
global $driver;
|
||||
global $driver, $host;
|
||||
// Return cached values
|
||||
if (is_array($this::$containersCache)) return $this::$containersCache;
|
||||
$this::$containersCache = [];
|
||||
@@ -916,29 +973,59 @@ class DockerClient {
|
||||
$c['Created'] = $this->humanTiming($ct['Created']);
|
||||
$c['NetworkMode'] = $ct['HostConfig']['NetworkMode'];
|
||||
$c['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
|
||||
if ($c['Manager'] == 'composeman') {
|
||||
$c['ComposeProject'] = $info['Config']['Labels']['com.docker.compose.project'];
|
||||
}
|
||||
[$net, $id] = array_pad(explode(':',$c['NetworkMode']),2,'');
|
||||
$c['CPUset'] = $info['HostConfig']['CpusetCpus'];
|
||||
$c['BaseImage'] = $ct['Labels']['BASEIMAGE'] ?? false;
|
||||
$c['Icon'] = $info['Config']['Labels']['net.unraid.docker.icon'] ?? false;
|
||||
$c['Url'] = $info['Config']['Labels']['net.unraid.docker.webui'] ?? false;
|
||||
$c['Shell'] = $info['Config']['Labels']['net.unraid.docker.shell'] ?? false;
|
||||
$c['TSUrl'] = $info['Config']['Labels']['net.unraid.docker.tailscale.webui'] ?? false;
|
||||
$c['TSHostname'] = $info['Config']['Labels']['net.unraid.docker.tailscale.hostname'] ?? false;
|
||||
$c['Shell'] = $info['Config']['Labels']['net.unraid.docker.shell'] ?? false;
|
||||
$c['Manager'] = $info['Config']['Labels']['net.unraid.docker.managed'] ?? false;
|
||||
$c['Ports'] = [];
|
||||
$c['Networks'] = [];
|
||||
if ($id) $c['NetworkMode'] = $net.str_replace('/',':',DockerUtil::ctMap($id)?:'/???');
|
||||
if (isset($driver[$c['NetworkMode']])) {
|
||||
if ($driver[$c['NetworkMode']]=='bridge') {
|
||||
$ports = &$info['HostConfig']['PortBindings'];
|
||||
$nat = true;
|
||||
} elseif ($driver[$c['NetworkMode']]=='host') {
|
||||
$c['Ports']['host'] = ['host' => ''];
|
||||
} elseif ($driver[$c['NetworkMode']]=='ipvlan' || $driver[$c['NetworkMode']]=='macvlan') {
|
||||
$i = $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'];
|
||||
$c['Ports']['vlan'] = ["$i" => $i];
|
||||
} else {
|
||||
$ports = &$info['Config']['ExposedPorts'];
|
||||
$nat = false;
|
||||
}
|
||||
$ip = $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'];
|
||||
} else if (!$id) {
|
||||
$c['NetworkMode'] = DockerUtil::ctMap($c['NetworkMode']);
|
||||
$ports = &$info['Config']['ExposedPorts'];
|
||||
}
|
||||
foreach($ct['NetworkSettings']['Networks'] as $netName => $netVals) {
|
||||
$i = $c['NetworkMode']=='host' ? $host : $netVals['IPAddress'];
|
||||
$c['Networks'][$netName] = [ 'IPAddress' => $i ];
|
||||
if ($driver[$netName]=='ipvlan' || $driver[$netName]=='macvlan') {
|
||||
if (!isset($c['Ports']['vlan'])) $c['Ports']['vlan'] = [];
|
||||
$c['Ports']['vlan']["$i"] = $i;
|
||||
}
|
||||
}
|
||||
$ip = $c['NetworkMode']=='host' ? $host : $ct['NetworkSettings']['Networks'][$c['NetworkMode']]['IPAddress'] ?? null;
|
||||
$c['Networks'][$c['NetworkMode']] = [ 'IPAddress' => $ip ];
|
||||
$ports = (isset($ports) && is_array($ports)) ? $ports : [];
|
||||
foreach ($ports as $port => $value) {
|
||||
if (!isset($info['HostConfig']['PortBindings'][$port])) {
|
||||
continue;
|
||||
}
|
||||
[$PrivatePort, $Type] = array_pad(explode('/', $port),2,'');
|
||||
$c['Ports'][] = ['IP' => $ip, 'PrivatePort' => $PrivatePort, 'PublicPort' => $nat ? $value[0]['HostPort'] : $PrivatePort, 'NAT' => $nat, 'Type' => $Type];
|
||||
$PublicPort = $info['HostConfig']['PortBindings']["$port"][0]['HostPort'] ?: null;
|
||||
$nat = ($driver[$c['NetworkMode']]=='bridge');
|
||||
if (array_key_exists($PrivatePort, $c['Ports']) && $Type != $c['Ports'][$PrivatePort]['Type'])
|
||||
$Type = $c['Ports'][$PrivatePort]['Type'] . '/' . $Type;
|
||||
$c['Ports'][$PrivatePort] = ['IP' => $ip, 'PrivatePort' => $PrivatePort, 'PublicPort' => $PublicPort, 'NAT' => $nat, 'Type' => $Type, 'Driver' => $driver[$c['NetworkMode']]];
|
||||
}
|
||||
ksort($c['Ports']);
|
||||
$this::$containersCache[] = $c;
|
||||
}
|
||||
array_multisort(array_column($this::$containersCache,'Name'), SORT_NATURAL|SORT_FLAG_CASE, $this::$containersCache);
|
||||
|
@@ -48,6 +48,55 @@ $null = '0.0.0.0';
|
||||
$autostart = (array)@file($autostart_file,FILE_IGNORE_NEW_LINES);
|
||||
$names = array_map('var_split',$autostart);
|
||||
|
||||
// Grab Tailscale json from container
|
||||
function tailscale_stats($name) {
|
||||
exec("docker exec -i ".$name." /bin/sh -c \"tailscale status --json | jq '{Self: .Self, ExitNodeStatus: .ExitNodeStatus, Version: .Version}'\" 2>/dev/null", $TS_stats);
|
||||
if (!empty($TS_stats)) {
|
||||
$TS_stats = implode("\n", $TS_stats);
|
||||
return json_decode($TS_stats, true);
|
||||
}
|
||||
return '';
|
||||
}
|
||||
|
||||
// Download Tailscal JSON and return Array, refresh file if older than 24 hours
|
||||
function tailscale_json_dl($file, $url) {
|
||||
$dl_status = 0;
|
||||
if (!is_dir('/tmp/tailscale')) {
|
||||
mkdir('/tmp/tailscale', 0777, true);
|
||||
}
|
||||
if (!file_exists($file)) {
|
||||
exec("wget -T 3 -q -O " . $file . " " . $url, $output, $dl_status);
|
||||
} else {
|
||||
$fileage = time() - filemtime($file);
|
||||
if ($fileage > 86400) {
|
||||
unlink($file);
|
||||
exec("wget -T 3 -q -O " . $file . " " . $url, $output, $dl_status);
|
||||
}
|
||||
}
|
||||
if ($dl_status === 0) {
|
||||
return json_decode(@file_get_contents($file), true);
|
||||
} elseif ($dl_status === 0 && is_file($file)) {
|
||||
return json_decode(@file_get_contents($file), true);
|
||||
} else {
|
||||
unlink($file);
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
// Grab Tailscale DERP map JSON
|
||||
$TS_derp_url = 'https://login.tailscale.com/derpmap/default';
|
||||
$TS_derp_file = '/tmp/tailscale/tailscale-derpmap.json';
|
||||
$TS_derp_list = tailscale_json_dl($TS_derp_file, $TS_derp_url);
|
||||
|
||||
// Grab Tailscale version JSON
|
||||
$TS_version_url = 'https://pkgs.tailscale.com/stable/?mode=json';
|
||||
$TS_version_file = '/tmp/tailscale/tailscale-latest-version.json';
|
||||
// Extract tarbal version string
|
||||
$TS_latest_version = tailscale_json_dl($TS_version_file, $TS_version_url);
|
||||
if (!empty($TS_latest_version)) {
|
||||
$TS_latest_version = $TS_latest_version["TarballsVersion"];
|
||||
}
|
||||
|
||||
function my_lang_time($text) {
|
||||
[$number, $text] = my_explode(' ',$text,2);
|
||||
return sprintf(_("%s $text"),$number);
|
||||
@@ -69,29 +118,51 @@ foreach ($containers as $ct) {
|
||||
$running = $info['running'] ? 1 : 0;
|
||||
$paused = $info['paused'] ? 1 : 0;
|
||||
$is_autostart = $info['autostart'] ? 'true':'false';
|
||||
$updateStatus = substr($ct['NetworkMode'],-4)==':???' ? 2 : ($info['updated']=='true' ? 0 : ($info['updated']=='false' ? 1 : 3));
|
||||
$composestack = isset($ct['ComposeProject']) ? $ct['ComposeProject'] : '';
|
||||
$updateStatus = substr($ct['NetworkMode'], -4) == ':???' ? 2 : ($info['updated'] == 'true' ? 0 : ($info['updated'] == 'false' ? 1 : 3));
|
||||
$template = $info['template']??'';
|
||||
$shell = $info['shell']??'';
|
||||
$webGui = html_entity_decode($info['url']??'');
|
||||
$TShostname = isset($ct['TSHostname']) ? $ct['TSHostname'] : '';
|
||||
$TSwebGui = html_entity_decode($info['TSurl']??'');
|
||||
$support = html_entity_decode($info['Support']??'');
|
||||
$project = html_entity_decode($info['Project']??'');
|
||||
$registry = html_entity_decode($info['registry']??'');
|
||||
$donateLink = html_entity_decode($info['DonateLink']??'');
|
||||
$readme = html_entity_decode($info['ReadMe']??'');
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s', '%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), $shell, $id, addslashes($support), addslashes($project),addslashes($registry),addslashes($donateLink),addslashes($readme));
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s', '%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), addslashes($TSwebGui), $shell, $id, addslashes($support), addslashes($project),addslashes($registry),addslashes($donateLink),addslashes($readme));
|
||||
$docker[] = "docker.push({name:'$name',id:'$id',state:$running,pause:$paused,update:$updateStatus});";
|
||||
$shape = $running ? ($paused ? 'pause' : 'play') : 'square';
|
||||
$status = $running ? ($paused ? 'paused' : 'started') : 'stopped';
|
||||
$color = $status=='started' ? 'green-text' : ($status=='paused' ? 'orange-text' : 'red-text');
|
||||
$update = $updateStatus==1 ? 'blue-text' : '';
|
||||
$update = $updateStatus==1 && !empty($compose) ? 'blue-text' : '';
|
||||
$icon = $info['icon'] ?: '/plugins/dynamix.docker.manager/images/question.png';
|
||||
$image = substr($icon,-4)=='.png' ? "<img src='$icon?".filemtime("$docroot{$info['icon']}")."' class='img' onerror=this.src='/plugins/dynamix.docker.manager/images/question.png';>" : (substr($icon,0,5)=='icon-' ? "<i class='$icon img'></i>" : "<i class='fa fa-$icon img'></i>");
|
||||
$wait = var_split($autostart[array_search($name,$names)]??'',1);
|
||||
$ports = [];
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
$intern = $running ? ($ct['NetworkMode']=='host' ? $host : _var($port,'IP')) : $null;
|
||||
$extern = $running ? (_var($port,'NAT') ? $host : $intern) : $null;
|
||||
$ports[] = sprintf('%s:%s/%s<i class="fa fa-arrows-h" style="margin:0 6px"></i>%s:%s', $intern, _var($port,'PrivatePort'), strtoupper(_var($port,'Type')), $extern, _var($port,'PublicPort'));
|
||||
$networks = [];
|
||||
$network_ips = [];
|
||||
$ports_internal = [];
|
||||
$ports_external = [];
|
||||
if (isset($ct['Ports']['vlan'])) {
|
||||
foreach ($ct['Ports']['vlan'] as $i)
|
||||
$ports_external[] = sprintf('%s', $i);
|
||||
$ports_internal[0] = sprintf('%s', 'all');
|
||||
}
|
||||
foreach($ct['Networks'] as $netName => $netVals) {
|
||||
$networks[] = $netName;
|
||||
$network_ips[] = $running ? $netVals['IPAddress'] : null;
|
||||
|
||||
if (isset($ct['Networks']['host'])) {
|
||||
$ports_external[] = sprintf('%s', $netVals['IPAddress']);
|
||||
$ports_internal[0] = sprintf('%s', 'all');
|
||||
} else if (!isset($ct['Ports']['vlan']) || strpos($ct['NetworkMode'], 'container:') != 0) {
|
||||
foreach ($ct['Ports'] as $port) {
|
||||
if (_var($port,'PublicPort') && _var($port,'Driver') == 'bridge')
|
||||
$ports_external[] = sprintf('%s:%s', $host, strtoupper(_var($port,'PublicPort')));
|
||||
if ((!isset($ct['Networks']['host'])) || (!isset($ct['Networks']['vlan'])))
|
||||
$ports_internal[] = sprintf('%s:%s', _var($port,'PrivatePort'), strtoupper(_var($port,'Type')));
|
||||
}
|
||||
}
|
||||
}
|
||||
$paths = [];
|
||||
$ct['Volumes'] = is_array($ct['Volumes']) ? $ct['Volumes'] : [];
|
||||
@@ -100,12 +171,12 @@ foreach ($containers as $ct) {
|
||||
$paths[] = sprintf('%s<i class="fa fa-%s" style="margin:0 6px"></i>%s', htmlspecialchars($container_path), $access_mode=='ro'?'long-arrow-left':'arrows-h', htmlspecialchars($host_path));
|
||||
}
|
||||
echo "<tr class='sortable'><td class='ct-name' style='width:220px;padding:8px'><i class='fa fa-arrows-v mover orange-text'></i>";
|
||||
if ($template) {
|
||||
if ($template && empty($composestack)) {
|
||||
$appname = "<a class='exec' onclick=\"editContainer('".addslashes(htmlspecialchars($name))."','".addslashes(htmlspecialchars($template))."')\">".htmlspecialchars($name)."</a>";
|
||||
} else {
|
||||
$appname = htmlspecialchars($name);
|
||||
}
|
||||
echo "<span class='outer'><span id='$id' $menu class='hand'>$image</span><span class='inner'><span class='appname $update'>$appname</span><br><i id='load-$id' class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span>";
|
||||
echo "<span class='outer'><span id='$id' $menu class='hand'>$image</span><span class='inner'><span class='appname $update'>$appname</span><br><i id='load-$id' class='fa fa-$shape $status $color'></i><span class='state'>"._($status).(!empty($composestack) ? '<br/>Compose Stack: ' . $composestack : '')."</span></span></span>";
|
||||
echo "<div class='advanced' style='margin-top:8px'>"._('Container ID').": $id<br>";
|
||||
if ($ct['BaseImage']) echo "<i class='fa fa-cubes' style='margin-right:5px'></i>".htmlspecialchars($ct['BaseImage'])."<br>";
|
||||
echo _('By').": ";
|
||||
@@ -118,35 +189,153 @@ foreach ($containers as $ct) {
|
||||
}
|
||||
echo "</div></td><td class='updatecolumn'>";
|
||||
switch ($updateStatus) {
|
||||
case 0:
|
||||
echo "<span class='green-text' style='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
if ($ct['Manager'] == "dockerman")
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
break;
|
||||
case 0:
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<span class='green-text' style='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
} elseif (!empty($composestack)) {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> Compose</span></div>";
|
||||
echo "<span tyle='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
} else {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> 3rd Party</span></div>";
|
||||
echo "<span tyle='white-space:nowrap;'><i class='fa fa-check fa-fw'></i> "._('up-to-date')."</span>";
|
||||
}
|
||||
break;
|
||||
case 1:
|
||||
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('update ready')."</span></div>";
|
||||
if ($ct['Manager'] == "dockerman")
|
||||
echo "<a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('apply update')."</span></a>";
|
||||
else
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('apply update')."</span></a>";
|
||||
} elseif (!empty($composestack)) {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> Compose</span></a></div>";
|
||||
echo "<span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('update available')."</span>";
|
||||
} else {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> 3rd Party</span></div>";
|
||||
echo "<span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('update available')."</span>";
|
||||
}
|
||||
break;
|
||||
case 2:
|
||||
echo "<div class='advanced'><span class='orange-text' style='white-space:nowrap;'><i class='fa fa-flash fa-fw'></i> "._('rebuild ready')."</span></div>";
|
||||
echo "<a class='exec'><span style='white-space:nowrap;'><i class='fa fa-recycle fa-fw'></i> "._('rebuilding')."</span></a>";
|
||||
break;
|
||||
default:
|
||||
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
|
||||
if ($ct['Manager'] == "dockerman")
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<span class='orange-text' style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
|
||||
echo "<div class='advanced'><a class='exec' onclick=\"updateContainer('".addslashes(htmlspecialchars($name))."');\"><span style='white-space:nowrap;'><i class='fa fa-cloud-download fa-fw'></i> "._('force update')."</span></a></div>";
|
||||
} elseif (!empty($composestack)) {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> Compose</span></div>";
|
||||
echo "<span style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
|
||||
} else {
|
||||
echo "<div><span><i class='fa fa-docker fa-fw'/></i> 3rd Party</span></div>";
|
||||
echo "<span style='white-space:nowrap;'><i class='fa fa-unlink'></i> "._('not available')."</span>";
|
||||
}
|
||||
break;
|
||||
}
|
||||
// Check if Tailscale for container is enabled by checking if TShostname is set
|
||||
if (!empty($TShostname)) {
|
||||
if ($running) {
|
||||
// Get stats from container and check if they are not empty
|
||||
$TSstats = tailscale_stats($name);
|
||||
if (!empty($TSstats)) {
|
||||
// Construct TSinfo from TSstats
|
||||
$TSinfo = '';
|
||||
if (!$TSstats["Self"]["Online"]) {
|
||||
$TSinfo .= "Online:\t\t❌\nPlease check the logs!";
|
||||
} else {
|
||||
$TS_version = explode('-', $TSstats["Version"])[0];
|
||||
if (!empty($TS_version)) {
|
||||
if (!empty($TS_latest_version)) {
|
||||
if ($TS_version !== $TS_latest_version) {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . " ➔ " . $TS_latest_version . " available!\n";
|
||||
} else {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
|
||||
}
|
||||
} else {
|
||||
$TSinfo .= "Version:\t\t" . $TS_version . "\n";
|
||||
}
|
||||
}
|
||||
$TSinfo .= "Online:\t\t✅\n";
|
||||
$TS_DNSName = $TSstats["Self"]["DNSName"];
|
||||
$TS_HostNameActual = substr($TS_DNSName, 0, strpos($TS_DNSName, '.'));
|
||||
if (strcasecmp($TS_HostNameActual, $TShostname) !== 0 && !empty($TS_DNSName)) {
|
||||
$TSinfo .= "Hostname:\tReal Hostname ➔ " . $TS_HostNameActual . "\n";
|
||||
} else {
|
||||
$TSinfo .= "Hostname:\t" . $TShostname . "\n";
|
||||
}
|
||||
// Map region relay code to cleartext region if TS_derp_list is available
|
||||
if (!empty($TS_derp_list)) {
|
||||
foreach ($TS_derp_list['Regions'] as $region) {
|
||||
if ($region['RegionCode'] === $TSstats["Self"]["Relay"]) {
|
||||
$TSregion = $region['RegionName'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!empty($TSregion)) {
|
||||
$TSinfo .= "Main Relay:\t" . $TSregion . "\n";
|
||||
} else {
|
||||
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
|
||||
}
|
||||
} else {
|
||||
$TSinfo .= "Main Relay:\t" . $TSstats["Self"]["Relay"] . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["TailscaleIPs"])) {
|
||||
$TSinfo .= "Addresses:\t" . implode("\n\t\t\t", $TSstats["Self"]["TailscaleIPs"]) . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["PrimaryRoutes"])) {
|
||||
$TSinfo .= "Routes:\t\t" . implode("\n\t\t\t", $TSstats["Self"]["PrimaryRoutes"]) . "\n";
|
||||
}
|
||||
if ($TSstats["Self"]["ExitNodeOption"]) {
|
||||
$TSinfo .= "Is Exit Node:\t✅\n";
|
||||
} else {
|
||||
if (!empty($TSstats["ExitNodeStatus"])) {
|
||||
$TS_exit_node_status = ($TSstats["ExitNodeStatus"]["Online"]) ? "✅" : "❌";
|
||||
$TSinfo .= "Exit Node:\t" . strstr($TSstats["ExitNodeStatus"]["TailscaleIPs"][0], '/', true) . " | Status: " . $TS_exit_node_status ."\n";
|
||||
} else {
|
||||
$TSinfo .= "Is Exit Node:\t❌\n";
|
||||
}
|
||||
}
|
||||
if (!empty($TSwebGui)) {
|
||||
$TSinfo .= "URL:\t\t" . $TSwebGui . "\n";
|
||||
}
|
||||
if (!empty($TSstats["Self"]["KeyExpiry"])) {
|
||||
$TS_expiry = new DateTime($TSstats["Self"]["KeyExpiry"]);
|
||||
$current_Date = new DateTime();
|
||||
$TS_expiry_formatted = $TS_expiry->format('Y-m-d');
|
||||
$TS_expiry_diff = $current_Date->diff($TS_expiry);
|
||||
if ($TS_expiry_diff->invert) {
|
||||
$TSinfo .= "Key Expiry:\t❌ Expired! Renew/Disable key expiry!\n";
|
||||
} else {
|
||||
$TSinfo .= "Key Expiry:\t" . $TS_expiry_formatted . " (" . $TS_expiry_diff->days . " days)\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
// Display message to refresh page if Tailscale in the container wasn't maybe ready to get the data
|
||||
} else {
|
||||
echo "<div title='Error gathering Tailscale information from container.\nPlease check the logs and refresh the page.'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
|
||||
}
|
||||
// Display TSinfo if data was fetched correctly
|
||||
echo "<div title='" . $TSinfo . "'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div>";
|
||||
// Display message that container isn't running
|
||||
} else {
|
||||
echo "<div title='Container not runnig'><img src='/plugins/dynamix.docker.manager/images/tailscale.png' style='height: 16px;'> Tailscale</div></td>";
|
||||
}
|
||||
}
|
||||
echo "<div class='advanced'><i class='fa fa-info-circle fa-fw'></i> ".compress(_($version),12,0)."</div></td>";
|
||||
echo "<td>{$ct['NetworkMode']}</td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports)."</span></td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'> ".implode('<br>',$networks)."</span></td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'> ".implode('<br>',$network_ips)."</span></td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports_internal)."</span></td>";
|
||||
echo "<td style='white-space:nowrap'><span class='docker_readmore'>".implode('<br>',$ports_external)."</span></td>";
|
||||
echo "<td style='word-break:break-all'><span class='docker_readmore'>".implode('<br>',$paths)."</span></td>";
|
||||
echo "<td class='advanced'><span class='cpu-$id'>0%</span><div class='usage-disk mm'><span id='cpu-$id' style='width:0'></span><span></span></div>";
|
||||
echo "<br><span class='mem-$id'>0 / 0</span></td>";
|
||||
echo "<td><input type='checkbox' id='$id-auto' class='autostart' container='".htmlspecialchars($name)."'".($info['autostart'] ? ' checked':'').">";
|
||||
if (empty($composestack)) {
|
||||
if ($ct['Manager'] == "dockerman") {
|
||||
echo "<td><input type='checkbox' id='$id-auto' class='autostart' container='".htmlspecialchars($name)."'".($info['autostart'] ? ' checked':'').">";
|
||||
} else {
|
||||
echo "<td><i class='fa fa-docker fa-fw'/></i> 3rd Party";
|
||||
}
|
||||
} else {
|
||||
echo "<td><i class='fa fa-docker'/></i> Compose";
|
||||
}
|
||||
echo "<span id='$id-wait' style='float:right;display:none'>"._('wait')."<input class='wait' container='".htmlspecialchars($name)."' type='number' value='$wait' placeholder='0' title=\""._('seconds')."\"></span></td>";
|
||||
echo "<td><div style='white-space:nowrap'>".htmlspecialchars(str_replace('Up',_('Uptime').':',my_lang_log($ct['Status'])))."<div style='margin-top:4px'>"._('Created').": ".htmlspecialchars(my_lang_time($ct['Created']))."</div></div></td></tr>";
|
||||
}
|
||||
@@ -161,4 +350,4 @@ foreach ($images as $image) {
|
||||
echo "</td><td>"._('Created')." ".htmlspecialchars(_($image['Created'],0))."</td></tr>";
|
||||
}
|
||||
echo "\0".implode($docker)."\0".(pgrep('rc.docker')!==false ? 1:0);
|
||||
?>
|
||||
?>
|
@@ -32,33 +32,65 @@ function xml_decode($string) {
|
||||
return strval(html_entity_decode($string, ENT_XML1, 'UTF-8'));
|
||||
}
|
||||
|
||||
function generateTSwebui($url, $serve, $webUI) {
|
||||
if (!isset($webUI)) {
|
||||
return '';
|
||||
}
|
||||
$webui_url = isset($webUI) ? parse_url($webUI) : '';
|
||||
$webui_port = (preg_match('/\[PORT:(\d+)\]/', $webUI, $matches)) ? ':' . $matches[1] : '';
|
||||
$webui_path = $webui_url['path'] ?? '';
|
||||
$webui_query = isset($webui_url['query']) ? '?' . $webui_url['query'] : '';
|
||||
if (!empty($url)) {
|
||||
if (strpos($url, '[hostname]') !== false || strpos($url, '[noserve]') !== false) {
|
||||
if ($serve === 'serve' || $serve === 'funnel') {
|
||||
return 'https://[hostname][magicdns]' . $webui_path . $webui_query;
|
||||
} elseif ($serve === 'no') {
|
||||
return 'http://[noserve]' . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
}
|
||||
return $url;
|
||||
} else {
|
||||
if (!empty($webUI)) {
|
||||
if ($serve === 'serve' || $serve === 'funnel') {
|
||||
return 'https://[hostname][magicdns]' . $webui_path . $webui_query;
|
||||
} elseif ($serve === 'no') {
|
||||
return 'http://[noserve]' . $webui_port . $webui_path . $webui_query;
|
||||
}
|
||||
}
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
function postToXML($post, $setOwnership=false) {
|
||||
$dom = new domDocument;
|
||||
$dom->appendChild($dom->createElement("Container"));
|
||||
$xml = simplexml_import_dom($dom);
|
||||
$xml['version'] = 2;
|
||||
$xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
|
||||
$xml->Repository = xml_encode(trim($post['contRepository']));
|
||||
$xml->Registry = xml_encode(trim($post['contRegistry']));
|
||||
$xml->Network = xml_encode($post['contNetwork']);
|
||||
$xml->MyIP = xml_encode($post['contMyIP']);
|
||||
$xml->Shell = xml_encode($post['contShell']);
|
||||
$xml->Privileged = strtolower($post['contPrivileged']??'')=='on' ? 'true' : 'false';
|
||||
$xml->Support = xml_encode($post['contSupport']);
|
||||
$xml->Project = xml_encode($post['contProject']);
|
||||
$xml->Overview = xml_encode($post['contOverview']);
|
||||
$xml->Category = xml_encode($post['contCategory']);
|
||||
$xml->WebUI = xml_encode(trim($post['contWebUI']));
|
||||
$xml->TemplateURL = xml_encode($post['contTemplateURL']);
|
||||
$xml->Icon = xml_encode(trim($post['contIcon']));
|
||||
$xml->ExtraParams = xml_encode($post['contExtraParams']);
|
||||
$xml->PostArgs = xml_encode($post['contPostArgs']);
|
||||
$xml->CPUset = xml_encode($post['contCPUset']);
|
||||
$xml->DateInstalled = xml_encode(time());
|
||||
$xml->DonateText = xml_encode($post['contDonateText']);
|
||||
$xml->DonateLink = xml_encode($post['contDonateLink']);
|
||||
$xml->Requires = xml_encode($post['contRequires']);
|
||||
|
||||
$xml['version'] = 2;
|
||||
$xml->Name = xml_encode(preg_replace('/\s+/', '', $post['contName']));
|
||||
$xml->Repository = xml_encode(trim($post['contRepository']));
|
||||
$xml->Registry = xml_encode(trim($post['contRegistry']));
|
||||
if (isset($post['netCONT']) && !empty(trim($post['netCONT']))) {
|
||||
$xml->Network = xml_encode($post['contNetwork'].':'.$post['netCONT']);
|
||||
} else {
|
||||
$xml->Network = xml_encode($post['contNetwork']);
|
||||
}
|
||||
$xml->MyIP = xml_encode($post['contMyIP']);
|
||||
$xml->Shell = xml_encode($post['contShell']);
|
||||
$xml->Privileged = strtolower($post['contPrivileged']??'')=='on' ? 'true' : 'false';
|
||||
$xml->Support = xml_encode($post['contSupport']);
|
||||
$xml->Project = xml_encode($post['contProject']);
|
||||
$xml->Overview = xml_encode($post['contOverview']);
|
||||
$xml->Category = xml_encode($post['contCategory']);
|
||||
$xml->WebUI = xml_encode(trim($post['contWebUI']));
|
||||
$xml->TemplateURL = xml_encode($post['contTemplateURL']);
|
||||
$xml->Icon = xml_encode(trim($post['contIcon']));
|
||||
$xml->ExtraParams = xml_encode($post['contExtraParams']);
|
||||
$xml->PostArgs = xml_encode($post['contPostArgs']);
|
||||
$xml->CPUset = xml_encode($post['contCPUset']);
|
||||
$xml->DateInstalled = xml_encode(time());
|
||||
$xml->DonateText = xml_encode($post['contDonateText']);
|
||||
$xml->DonateLink = xml_encode($post['contDonateLink']);
|
||||
$xml->Requires = xml_encode($post['contRequires']);
|
||||
$size = is_array($post['confName']??null) ? count($post['confName']) : 0;
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
$Type = $post['confType'][$i];
|
||||
@@ -73,6 +105,31 @@ function postToXML($post, $setOwnership=false) {
|
||||
$config['Required'] = xml_encode($post['confRequired'][$i]);
|
||||
$config['Mask'] = xml_encode($post['confMask'][$i]);
|
||||
}
|
||||
if (isset($post['contTailscale']) && strtolower($post['contTailscale']) == 'on') {
|
||||
$xml->TailscaleEnabled = 'true';
|
||||
$xml->TailscaleIsExitNode = xml_encode($post['TSisexitnode']);
|
||||
$xml->TailscaleHostname = xml_encode($post['TShostname']);
|
||||
$xml->TailscaleExitNodeIP = xml_encode($post['TSexitnodeip']);
|
||||
$xml->TailscaleSSH = xml_encode($post['TSssh']);
|
||||
$xml->TailscaleUserspaceNetworking = xml_encode($post['TSuserspacenetworking']);
|
||||
$xml->TailscaleLANAccess = xml_encode($post['TSallowlanaccess']);
|
||||
$xml->TailscaleServe = xml_encode($post['TSserve']);
|
||||
$xml->TailscaleWebUI = xml_encode(generateTSwebui($post['TSwebui'], $post['TSserve'], $post['contWebUI']));
|
||||
if (isset($post['TSserve']) && strtolower($post['TSserve']) !== 'no') {
|
||||
$xml->TailscaleServePort = xml_encode($post['TSserveport']);
|
||||
$xml->TailscaleServeLocalPath = xml_encode($post['TSservelocalpath']);
|
||||
$xml->TailscaleServeProtocol = xml_encode($post['TSserveprotocol']);
|
||||
$xml->TailscaleServeProtocolPort = xml_encode($post['TSserveprotocolport']);
|
||||
$xml->TailscaleServePath = xml_encode($post['TSservepath']);
|
||||
}
|
||||
$xml->TailscaleDParams = xml_encode($post['TSdaemonparams']);
|
||||
$xml->TailscaleParams = xml_encode($post['TSextraparams']);
|
||||
$xml->TailscaleStateDir = xml_encode($post['TSstatedir']);
|
||||
$xml->TailscaleRoutes = xml_encode($post['TSroutes']);;
|
||||
if (isset($post['TStroubleshooting']) && strtolower($post['TStroubleshooting']) === 'on') {
|
||||
$xml->TailscaleTroubleshooting = 'true';
|
||||
}
|
||||
}
|
||||
$dom = new DOMDocument('1.0');
|
||||
$dom->preserveWhiteSpace = false;
|
||||
$dom->formatOutput = true;
|
||||
@@ -82,29 +139,48 @@ function postToXML($post, $setOwnership=false) {
|
||||
|
||||
function xmlToVar($xml) {
|
||||
global $subnet;
|
||||
$xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml);
|
||||
$out = [];
|
||||
$out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name));
|
||||
$out['Repository'] = xml_decode($xml->Repository);
|
||||
$out['Registry'] = xml_decode($xml->Registry);
|
||||
$out['Network'] = xml_decode($xml->Network);
|
||||
$out['MyIP'] = xml_decode($xml->MyIP ?? '');
|
||||
$out['Shell'] = xml_decode($xml->Shell ?? 'sh');
|
||||
$out['Privileged'] = xml_decode($xml->Privileged);
|
||||
$out['Support'] = xml_decode($xml->Support);
|
||||
$out['Project'] = xml_decode($xml->Project);
|
||||
$out['Overview'] = stripslashes(xml_decode($xml->Overview));
|
||||
$out['Category'] = xml_decode($xml->Category);
|
||||
$out['WebUI'] = xml_decode($xml->WebUI);
|
||||
$out['TemplateURL'] = xml_decode($xml->TemplateURL);
|
||||
$out['Icon'] = xml_decode($xml->Icon);
|
||||
$out['ExtraParams'] = xml_decode($xml->ExtraParams);
|
||||
$out['PostArgs'] = xml_decode($xml->PostArgs);
|
||||
$out['CPUset'] = xml_decode($xml->CPUset);
|
||||
$out['DonateText'] = xml_decode($xml->DonateText);
|
||||
$out['DonateLink'] = xml_decode($xml->DonateLink);
|
||||
$out['Requires'] = xml_decode($xml->Requires);
|
||||
$out['Config'] = [];
|
||||
$xml = is_file($xml) ? simplexml_load_file($xml) : simplexml_load_string($xml);
|
||||
$out = [];
|
||||
$out['Name'] = preg_replace('/\s+/', '', xml_decode($xml->Name));
|
||||
$out['Repository'] = xml_decode($xml->Repository);
|
||||
$out['Registry'] = xml_decode($xml->Registry);
|
||||
$out['Network'] = xml_decode($xml->Network);
|
||||
$out['MyIP'] = xml_decode($xml->MyIP ?? '');
|
||||
$out['Shell'] = xml_decode($xml->Shell ?? 'sh');
|
||||
$out['Privileged'] = xml_decode($xml->Privileged);
|
||||
$out['Support'] = xml_decode($xml->Support);
|
||||
$out['Project'] = xml_decode($xml->Project);
|
||||
$out['Overview'] = stripslashes(xml_decode($xml->Overview));
|
||||
$out['Category'] = xml_decode($xml->Category);
|
||||
$out['WebUI'] = xml_decode($xml->WebUI);
|
||||
$out['TemplateURL'] = xml_decode($xml->TemplateURL);
|
||||
$out['Icon'] = xml_decode($xml->Icon);
|
||||
$out['ExtraParams'] = xml_decode($xml->ExtraParams);
|
||||
$out['PostArgs'] = xml_decode($xml->PostArgs);
|
||||
$out['CPUset'] = xml_decode($xml->CPUset);
|
||||
$out['DonateText'] = xml_decode($xml->DonateText);
|
||||
$out['DonateLink'] = xml_decode($xml->DonateLink);
|
||||
$out['Requires'] = xml_decode($xml->Requires);
|
||||
$out['TailscaleEnabled'] = xml_decode($xml->TailscaleEnabled ?? '');
|
||||
$out['TailscaleIsExitNode'] = xml_decode($xml->TailscaleIsExitNode ?? '');
|
||||
$out['TailscaleHostname'] = xml_decode($xml->TailscaleHostname ?? '');
|
||||
$out['TailscaleExitNodeIP'] = xml_decode($xml->TailscaleExitNodeIP ?? '');
|
||||
$out['TailscaleSSH'] = xml_decode($xml->TailscaleSSH ?? '');
|
||||
$out['TailscaleLANAccess'] = xml_decode($xml->TailscaleLANAccess ?? '');
|
||||
$out['TailscaleUserspaceNetworking'] = xml_decode($xml->TailscaleUserspaceNetworking ?? '');
|
||||
$out['TailscaleServe'] = xml_decode($xml->TailscaleServe ?? '');
|
||||
$out['TailscaleServePort'] = xml_decode($xml->TailscaleServePort ?? '');
|
||||
$out['TailscaleServeLocalPath'] = xml_decode($xml->TailscaleServeLocalPath ?? '');
|
||||
$out['TailscaleServeProtocol'] = xml_decode($xml->TailscaleServeProtocol ?? '');
|
||||
$out['TailscaleServeProtocolPort'] = xml_decode($xml->TailscaleServeProtocolPort ?? '');
|
||||
$out['TailscaleServePath'] = xml_decode($xml->TailscaleServePath ?? '');
|
||||
$out['TailscaleWebUI'] = xml_decode($xml->TailscaleWebUI ?? '');
|
||||
$out['TailscaleRoutes'] = xml_decode($xml->TailscaleRoutes ?? '');
|
||||
$out['TailscaleDParams'] = xml_decode($xml->TailscaleDParams ?? '');
|
||||
$out['TailscaleParams'] = xml_decode($xml->TailscaleParams ?? '');
|
||||
$out['TailscaleStateDir'] = xml_decode($xml->TailscaleStateDir ?? '');
|
||||
$out['TailscaleTroubleshooting'] = xml_decode($xml->TailscaleTroubleshooting ?? '');
|
||||
$out['Config'] = [];
|
||||
if (isset($xml->Config)) {
|
||||
foreach ($xml->Config as $config) {
|
||||
$c = [];
|
||||
@@ -132,7 +208,11 @@ function xmlToVar($xml) {
|
||||
$out['Network'] = xml_decode($xml->Networking->Mode);
|
||||
}
|
||||
// check if network exists
|
||||
if (!key_exists($out['Network'],$subnet)) $out['Network'] = 'none';
|
||||
if (preg_match('/^container:(.*)/', $out['Network'])) {
|
||||
$out['Network'] = $out['Network'];
|
||||
} elseif (!key_exists($out['Network'],$subnet)) {
|
||||
$out['Network'] = 'none';
|
||||
}
|
||||
// V1 compatibility
|
||||
if ($xml['version'] != '2') {
|
||||
if (isset($xml->Description)) {
|
||||
@@ -237,11 +317,15 @@ function xmlSecurity(&$template) {
|
||||
}
|
||||
|
||||
function xmlToCommand($xml, $create_paths=false) {
|
||||
global $docroot, $var, $cfg, $driver;
|
||||
global $docroot, $var, $driver;
|
||||
$xml = xmlToVar($xml);
|
||||
$cmdName = strlen($xml['Name']) ? '--name='.escapeshellarg($xml['Name']) : '';
|
||||
$cmdPrivileged = strtolower($xml['Privileged'])=='true' ? '--privileged=true' : '';
|
||||
$cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg(strtolower($xml['Network']));
|
||||
if (preg_match('/^container:(.*)/', $xml['Network'])) {
|
||||
$cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg($xml['Network']);
|
||||
} else {
|
||||
$cmdNetwork = preg_match('/\-\-net(work)?=/',$xml['ExtraParams']) ? "" : '--net='.escapeshellarg(strtolower($xml['Network']));
|
||||
}
|
||||
$cmdMyIP = '';
|
||||
foreach (explode(' ',str_replace(',',' ',$xml['MyIP'])) as $myIP) if ($myIP) $cmdMyIP .= (strpos($myIP,':')?'--ip6=':'--ip=').escapeshellarg($myIP).' ';
|
||||
$cmdCPUset = strlen($xml['CPUset']) ? '--cpuset-cpus='.escapeshellarg($xml['CPUset']) : '';
|
||||
@@ -254,7 +338,7 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
$Variables[] = 'TZ="'.$var['timeZone'].'"';
|
||||
// Add HOST_OS variable
|
||||
$Variables[] = 'HOST_OS="Unraid"';
|
||||
// Add HOST_HOSTNAME variable
|
||||
// Add HOST_HOSTNAME variable
|
||||
$Variables[] = 'HOST_HOSTNAME="'.$var['NAME'].'"';
|
||||
// Add HOST_CONTAINERNAME variable
|
||||
$Variables[] = 'HOST_CONTAINERNAME="'.$xml['Name'].'"';
|
||||
@@ -263,6 +347,68 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
if (strlen($xml['WebUI'])) $Labels[] = 'net.unraid.docker.webui='.escapeshellarg($xml['WebUI']);
|
||||
if (strlen($xml['Icon'])) $Labels[] = 'net.unraid.docker.icon='.escapeshellarg($xml['Icon']);
|
||||
|
||||
// Initialize Tailscale variables
|
||||
$TS_entrypoint = '';
|
||||
$TS_hook = '';
|
||||
$TS_hostname = '';
|
||||
$TS_hostname_label = '';
|
||||
$TS_ssh = '';
|
||||
$TS_tundev = '';
|
||||
$TS_cap = '';
|
||||
$TS_exitnode = '';
|
||||
$TS_exitnode_ip = '';
|
||||
$TS_lan_access = '';
|
||||
$TS_userspace_networking = '';
|
||||
$TS_daemon_params = '';
|
||||
$TS_extra_params = '';
|
||||
$TS_state_dir = '';
|
||||
$TS_serve_funnel = '';
|
||||
$TS_serve_port = '';
|
||||
$TS_serve_local_path = '';
|
||||
$TS_serve_protocol = '';
|
||||
$TS_serve_protocol_port = '';
|
||||
$TS_serve_path = '';
|
||||
$TS_web_ui = '';
|
||||
$TS_troubleshooting = '';
|
||||
$TS_routes = '';
|
||||
$TS_postargs = '';
|
||||
// Get all information from xml and create variables for cmd
|
||||
if ($xml['TailscaleEnabled'] == 'true') {
|
||||
$TS_entrypoint = '--entrypoint=\'/opt/unraid/tailscale\'';
|
||||
$TS_hook = '-v \'/usr/local/share/docker/tailscale_container_hook\':\'/opt/unraid/tailscale\'';
|
||||
$TS_hostname = !empty($xml['TailscaleHostname']) ? '-e TAILSCALE_HOSTNAME=' . escapeshellarg($xml['TailscaleHostname']) : '';
|
||||
$TS_hostname_label = !empty($xml['TailscaleHostname']) ? '-l net.unraid.docker.tailscale.hostname=' . escapeshellarg($xml['TailscaleHostname']) : '';
|
||||
$TS_ssh = !empty($xml['TailscaleSSH']) ? '-e TAILSCALE_USE_SSH=' . escapeshellarg($xml['TailscaleSSH']) : '';
|
||||
$TS_daemon_params = !empty($xml['TailscaleDParams']) ? '-e TAILSCALED_PARAMS=' . escapeshellarg($xml['TailscaleDParams']) : '';
|
||||
$TS_extra_params = !empty($xml['TailscaleParams']) ? '-e TAILSCALE_PARAMS=' . escapeshellarg($xml['TailscaleParams']) : '';
|
||||
$TS_state_dir = !empty($xml['TailscaleStateDir']) ? '-e TAILSCALE_STATE_DIR=' . escapeshellarg($xml['TailscaleStateDir']) : '';
|
||||
$TS_userspace_networking = !empty($xml['TailscaleUserspaceNetworking']) ? '-e TAILSCALE_USERSPACE_NETWORKING=' . escapeshellarg($xml['TailscaleUserspaceNetworking']) : '';
|
||||
// Only add tun, cap and specific vairables to containers which are defined as Exit Nodes and Userspace Networking disabled
|
||||
if (_var($xml,'TailscaleIsExitNode') == 'true') {
|
||||
$TS_tundev = preg_match('/--d(evice)?[= ](\'?\/dev\/net\/tun\'?)/', $xml['ExtraParams']) ? "" : "--device='/dev/net/tun'";
|
||||
$TS_cap = preg_match('/--cap\-add=NET_ADMIN/', $xml['ExtraParams']) ? "" : "--cap-add=NET_ADMIN";
|
||||
$TS_exitnode = '-e TAILSCALE_EXIT_NODE=true';
|
||||
} elseif (_var($xml,'TailscaleUserspaceNetworking') == 'false') {
|
||||
$TS_tundev = preg_match('/--d(evice)?[= ](\'?\/dev\/net\/tun\'?)/', $xml['ExtraParams']) ? "" : "--device='/dev/net/tun'";
|
||||
$TS_cap = preg_match('/--cap\-add=NET_ADMIN/', $xml['ExtraParams']) ? "" : "--cap-add=NET_ADMIN";
|
||||
$TS_lan_access = '-e TAILSCALE_ALLOW_LAN_ACCESS=' . escapeshellarg($xml['TailscaleLANAccess']);
|
||||
$TS_exitnode_ip = !empty($xml['TailscaleExitNodeIP']) ? '-e TAILSCALE_EXIT_NODE_IP=' . escapeshellarg($xml['TailscaleExitNodeIP']) : '';
|
||||
}
|
||||
$TS_serve_funnel = ($xml['TailscaleServe'] == 'funnel') ? '-e TAILSCALE_FUNNEL=true' : '';
|
||||
$TS_serve_port = !empty($xml['TailscaleServePort']) ? '-e TAILSCALE_SERVE_PORT=' . escapeshellarg($xml['TailscaleServePort']) : '';
|
||||
$TS_serve_local_path = !empty($xml['TailscaleServeLocalPath']) ? '-e TAILSCALE_SERVE_LOCALPATH=' . escapeshellarg($xml['TailscaleServeLocalPath']) : '';
|
||||
$TS_serve_protocol = !empty($xml['TailscaleServeProtocol']) ? '-e TAILSCALE_SERVE_PROTOCOL=' . escapeshellarg($xml['TailscaleServeProtocol']) : '';
|
||||
$TS_serve_protocol_port = !empty($xml['TailscaleServeProtocolPort']) ? '-e TAILSCALE_SERVE_PROTOCOL_PORT=' . escapeshellarg($xml['TailscaleServeProtocolPort']) : '';
|
||||
$TS_serve_path = !empty($xml['TailscaleServePath']) ? '-e TAILSCALE_SERVE_PATH=' . escapeshellarg($xml['TailscaleServePath']) : '';
|
||||
$TS_web_ui = !empty($xml['TailscaleWebUI']) ? '-l net.unraid.docker.tailscale.webui=' . escapeshellarg($xml['TailscaleWebUI']) : '';
|
||||
$TS_troubleshooting = !empty($xml['TailscaleTroubleshooting']) ? '-e TAILSCALE_TROUBLESHOOTING=' . escapeshellarg($xml['TailscaleTroubleshooting']) : '';
|
||||
$TS_routes = !empty($xml['TailscaleRoutes']) ? '-e TAILSCALE_ADVERTISE_ROUTES=' . escapeshellarg($xml['TailscaleRoutes']) : '';
|
||||
if (!empty($xml['PostArgs'])) {
|
||||
$TS_postargs = '-e ORG_POSTARGS=' . escapeshellarg($xml['PostArgs']);
|
||||
$xml['PostArgs'] = '';
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($xml['Config'] as $key => $config) {
|
||||
$confType = strtolower(strval($config['Type']));
|
||||
$hostConfig = strlen($config['Value']) ? $config['Value'] : $config['Default'];
|
||||
@@ -301,15 +447,27 @@ function xmlToCommand($xml, $create_paths=false) {
|
||||
$Devices[] = escapeshellarg($hostConfig);
|
||||
}
|
||||
}
|
||||
$logSize = $logFile = '';
|
||||
if (($cfg['DOCKER_LOG_ROTATION']??'')=='yes') {
|
||||
$logSize = $cfg['DOCKER_LOG_SIZE'] ?? '10m';
|
||||
$logSize = "--log-opt max-size='$logSize'";
|
||||
$logFile = $cfg['DOCKER_LOG_FILES'] ?? '1';
|
||||
$logFile = "--log-opt max-file='$logFile'";
|
||||
|
||||
/* Read the docker configuration file. */
|
||||
$cfgfile = "/boot/config/docker.cfg";
|
||||
$config_ini = @parse_ini_file($cfgfile, true, INI_SCANNER_RAW);
|
||||
$docker_cfg = ($config_ini !== false) ? $config_ini : [];
|
||||
|
||||
// Add pid limit if user has not specified it as an extra parameter
|
||||
$pidsLimit = preg_match('/--pids-limit (\d+)/', $xml['ExtraParams'], $matches) ? $matches[1] : null;
|
||||
if ($pidsLimit === null) {
|
||||
$pid_limit = "--pids-limit ";
|
||||
if (($docker_cfg['DOCKER_PID_LIMIT']??'') != "") {
|
||||
$pid_limit .= $docker_cfg['DOCKER_PID_LIMIT'];
|
||||
} else {
|
||||
$pid_limit .= "2048";
|
||||
}
|
||||
} else {
|
||||
$pid_limit = "";
|
||||
}
|
||||
$cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s',
|
||||
$cmdName, $cmdNetwork, $cmdMyIP, $cmdCPUset, $logSize, $logFile, $cmdPrivileged, implode(' -e ', $Variables), implode(' -l ', $Labels), implode(' -p ', $Ports), implode(' -v ', $Volumes), implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $xml['PostArgs']);
|
||||
|
||||
$cmd = sprintf($docroot.'/plugins/dynamix.docker.manager/scripts/docker create %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s %s',
|
||||
$cmdName, $TS_entrypoint, $cmdNetwork, $cmdMyIP, $cmdCPUset, $pid_limit, $cmdPrivileged, implode(' -e ', $Variables), $TS_hostname, $TS_exitnode, $TS_exitnode_ip, $TS_lan_access, $TS_routes, $TS_ssh, $TS_userspace_networking, $TS_serve_funnel, $TS_serve_port, $TS_serve_local_path, $TS_serve_protocol, $TS_serve_protocol_port, $TS_serve_path, $TS_daemon_params, $TS_extra_params, $TS_state_dir, $TS_troubleshooting, $TS_postargs, implode(' -l ', $Labels), $TS_web_ui, $TS_hostname_label, implode(' -p ', $Ports), implode(' -v ', $Volumes), $TS_hook, $TS_cap, $TS_tundev, implode(' --device=', $Devices), $xml['ExtraParams'], escapeshellarg($xml['Repository']), $xml['PostArgs']);
|
||||
return [preg_replace('/\s\s+/', ' ', $cmd), $xml['Name'], $xml['Repository']];
|
||||
}
|
||||
function stopContainer($name, $t=false, $echo=true) {
|
||||
@@ -496,16 +654,22 @@ function setXmlVal(&$xml, $value, $el, $attr=null, $pos=0) {
|
||||
|
||||
function getAllocations() {
|
||||
global $DockerClient, $host;
|
||||
|
||||
|
||||
$ports = [];
|
||||
foreach ($DockerClient->getDockerContainers() as $ct) {
|
||||
$list = $port = [];
|
||||
$nat = $ip = false;
|
||||
$list['Name'] = $ct['Name'];
|
||||
foreach ($ct['Ports'] as $tmp) {
|
||||
$nat = $tmp['NAT'];
|
||||
$ip = $tmp['IP'];
|
||||
$port[] = $tmp['PublicPort'];
|
||||
if (isset($tmp['NAT'])) {
|
||||
$nat = $tmp['NAT'];
|
||||
}
|
||||
if (isset($tmp['IP'])) {
|
||||
$ip = $tmp['IP'];
|
||||
}
|
||||
if (isset($tmp['PublicPort'])) {
|
||||
$port[] = $tmp['PublicPort'];
|
||||
}
|
||||
}
|
||||
sort($port);
|
||||
$ip = $ct['NetworkMode']=='host'||$nat ? $host : ($ip ?: DockerUtil::myIP($ct['Name']) ?: '0.0.0.0');
|
||||
|
@@ -1,10 +1,11 @@
|
||||
var eventURL = '/plugins/dynamix.docker.manager/include/Events.php';
|
||||
|
||||
function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, shell, id, Support, Project, Registry, donateLink, ReadMe) {
|
||||
function addDockerContainerContext(container, image, template, started, paused, update, autostart, webui, tswebui, shell, id, Support, Project, Registry, donateLink, ReadMe) {
|
||||
var opts = [];
|
||||
context.settings({right:false,above:false});
|
||||
if (started && !paused) {
|
||||
if (webui !== '' && webui != '#') opts.push({text:_('WebUI'), icon:'fa-globe', href:webui, target:'_blank'});
|
||||
if (tswebui !== '' && tswebui != '#') opts.push({text:_('Tailscale WebUI'), icon:'fa-globe', href:tswebui, target:'_blank'});
|
||||
opts.push({text:_('Console'), icon:'fa-terminal', action:function(e){e.preventDefault(); openTerminal('docker',container,shell);}});
|
||||
opts.push({divider:true});
|
||||
}
|
||||
|
@@ -13,27 +13,46 @@
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
/* Define the path to the docker configuration file */
|
||||
$cfgfile = "/boot/config/docker.cfg";
|
||||
|
||||
/* Define the default configuration values */
|
||||
$cfg_defaults = [
|
||||
"DOCKER_ENABLED" => "no",
|
||||
"DOCKER_IMAGE_FILE" => "/mnt/user/system/docker/docker.img",
|
||||
"DOCKER_IMAGE_SIZE" => "20",
|
||||
"DOCKER_APP_CONFIG_PATH" => "/mnt/user/appdata/",
|
||||
"DOCKER_APP_UNRAID_PATH" => "",
|
||||
"DOCKER_READMORE" => "yes"
|
||||
"DOCKER_ENABLED" => "no",
|
||||
"DOCKER_IMAGE_FILE" => "/mnt/user/system/docker/docker.img",
|
||||
"DOCKER_IMAGE_SIZE" => "20",
|
||||
"DOCKER_APP_CONFIG_PATH" => "/mnt/user/appdata/",
|
||||
"DOCKER_APP_UNRAID_PATH" => "",
|
||||
"DOCKER_READMORE" => "yes",
|
||||
"DOCKER_PID_LIMIT" => ""
|
||||
];
|
||||
|
||||
/* Initialize the new configuration with the default values */
|
||||
$cfg_new = $cfg_defaults;
|
||||
|
||||
/* Check if the configuration file exists */
|
||||
if (file_exists($cfgfile)) {
|
||||
$cfg_old = parse_ini_file($cfgfile);
|
||||
if (!empty($cfg_old)) {
|
||||
$cfg_new = array_merge($cfg_defaults, $cfg_old);
|
||||
if (empty(array_diff($cfg_new, $cfg_old))) unset($cfg_new);
|
||||
}
|
||||
/* Parse the existing configuration file */
|
||||
$cfg_old = parse_ini_file($cfgfile);
|
||||
|
||||
/* If the existing configuration is not empty, merge it with the defaults */
|
||||
if (!empty($cfg_old)) {
|
||||
/* Merge only missing keys from defaults */
|
||||
$cfg_new = array_merge($cfg_defaults, $cfg_old);
|
||||
|
||||
/* If there are no changes between the new and old configurations, unset the new configuration */
|
||||
if (empty(array_diff_assoc($cfg_new, $cfg_old))) {
|
||||
$cfg_new = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* If the new configuration is set, write it to the configuration file */
|
||||
if (isset($cfg_new)) {
|
||||
$tmp = '';
|
||||
foreach ($cfg_new as $key => $value) $tmp .= "$key=\"$value\"\n";
|
||||
file_put_contents($cfgfile, $tmp);
|
||||
$tmp = '';
|
||||
foreach ($cfg_new as $key => $value) {
|
||||
$tmp .= "$key=\"$value\"\n";
|
||||
}
|
||||
file_put_contents($cfgfile, $tmp);
|
||||
}
|
||||
?>
|
||||
|
@@ -24,7 +24,7 @@ $_SERVER['REQUEST_URI'] = "scripts";
|
||||
$login_locale = _var($display,'locale');
|
||||
require_once "$docroot/webGui/include/Translations.php";
|
||||
|
||||
exec("pgrep docker", $pid);
|
||||
exec('pgrep --ns $$ docker', $pid);
|
||||
if (count($pid) == 1) exit(0);
|
||||
|
||||
$DockerClient = new DockerClient();
|
||||
|
@@ -170,6 +170,7 @@ foreach (explode('*',rawurldecode($argv[1])) as $value) {
|
||||
$xml = file_get_contents($tmpl);
|
||||
[$cmd, $Name, $Repository] = xmlToCommand($tmpl);
|
||||
$Registry = getXmlVal($xml, "Registry");
|
||||
$TS_Enabled = getXmlVal($xml, "TailscaleEnabled");
|
||||
$oldImageID = $DockerClient->getImageID($Repository);
|
||||
// pull image
|
||||
if (!pullImage_nchan($Name, $Repository)) continue;
|
||||
@@ -182,14 +183,25 @@ foreach (explode('*',rawurldecode($argv[1])) as $value) {
|
||||
// attempt graceful stop of container first
|
||||
stopContainer_nchan($Name);
|
||||
}
|
||||
if ( ($argv[2]??null) == "ca_docker_run_override" )
|
||||
if ( ($argv[2]??null) == "ca_docker_run_override" )
|
||||
$startContainer = true;
|
||||
|
||||
if ( $startContainer )
|
||||
$cmd = str_replace('/docker create ', '/docker run -d ', $cmd);
|
||||
|
||||
// force kill container if still running after 10 seconds
|
||||
if (empty($_GET['communityApplications'])) removeContainer_nchan($Name);
|
||||
// Extract real Entrypoint and Cmd from container for Tailscale
|
||||
if ($TS_Enabled == 'true') {
|
||||
// Create preliminary base container but don't run it
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker create --name '" . escapeshellarg($Name) . "' '" . escapeshellarg($Repository) . "'");
|
||||
// Get Entrypoint and Cmd from docker inspect
|
||||
$containerInfo = $DockerClient->getContainerDetails($Name);
|
||||
$ts_env = isset($containerInfo['Config']['Entrypoint']) ? '-e ORG_ENTRYPOINT="' . implode(' ', $containerInfo['Config']['Entrypoint']) . '" ' : '';
|
||||
$ts_env .= isset($containerInfo['Config']['Cmd']) ? '-e ORG_CMD="' . implode(' ', $containerInfo['Config']['Cmd']) . '" ' : '';
|
||||
// Insert Entrypoint and Cmd to docker command
|
||||
$cmd = str_replace('-l net.unraid.docker.managed=dockerman', $ts_env . '-l net.unraid.docker.managed=dockerman' , $cmd);
|
||||
// Remove preliminary container
|
||||
exec("/usr/local/emhttp/plugins/dynamix.docker.manager/scripts/docker rm '" . escapeshellarg($Name) . "'");
|
||||
}
|
||||
execCommand_nchan($cmd);
|
||||
if ($startContainer) addRoute($Name); // add route for remote WireGuard access
|
||||
$DockerClient->flushCaches();
|
||||
|
28
emhttp/plugins/dynamix.docker.manager/system/Docker
Executable file
28
emhttp/plugins/dynamix.docker.manager/system/Docker
Executable file
@@ -0,0 +1,28 @@
|
||||
#!/bin/bash
|
||||
# Get active containers
|
||||
ACTIVE_CONTAINERS="$(docker ps -q --no-trunc 2>/dev/null)"
|
||||
|
||||
# Exit if no containers are active and return zero
|
||||
if [ -z "${ACTIVE_CONTAINERS}" ]; then
|
||||
echo "0"
|
||||
exit
|
||||
fi
|
||||
|
||||
# Get all relevant memory entries from containers
|
||||
for container in ${ACTIVE_CONTAINERS} ; do
|
||||
CONT_MEMORY="$(cat /sys/fs/cgroup/docker/${container}/memory.stat 2>/dev/null | grep -Ew "anon|kernel|kernel_stack|pagetables|sec_pagetables|percpu|sock|vmalloc|shmem" | awk '{print $2}')"
|
||||
# Add up memory values
|
||||
for value in ${CONT_MEMORY} ; do
|
||||
if [[ ${value} =~ ^[0-9]+$ ]]; then
|
||||
((MEMORY_USAGE += value))
|
||||
fi
|
||||
done
|
||||
unset CONT_MEMORY
|
||||
done
|
||||
|
||||
# Check if value is a integer and return the value otherwiese return zero
|
||||
if [[ ${MEMORY_USAGE} =~ ^[0-9]+$ ]]; then
|
||||
echo "${MEMORY_USAGE}"
|
||||
else
|
||||
echo "0"
|
||||
fi
|
@@ -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'];
|
||||
}
|
||||
}
|
||||
}
|
@@ -93,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();
|
||||
|
||||
@@ -236,6 +236,10 @@ 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,
|
||||
@@ -270,7 +274,8 @@ class ServerState
|
||||
"osVersion" => $this->osVersion,
|
||||
"osVersionBranch" => $this->osVersionBranch,
|
||||
"protocol" => _var($_SERVER, 'REQUEST_SCHEME'),
|
||||
"rebootType" => $this->rebootDetails->getRebootType(),
|
||||
"rebootType" => $this->rebootDetails->rebootType,
|
||||
"rebootVersion" => $this->rebootDetails->rebootVersion,
|
||||
"regDevs" => @(int)$this->var['regDevs'] ?? 0,
|
||||
"regGen" => @(int)$this->var['regGen'],
|
||||
"regGuid" => @$this->var['regGUID'] ?? '',
|
||||
@@ -334,4 +339,4 @@ class ServerState
|
||||
$json = json_encode($this->getServerState());
|
||||
return htmlspecialchars($json, ENT_QUOTES, 'UTF-8');
|
||||
}
|
||||
}
|
||||
}
|
@@ -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 {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!'),
|
||||
];
|
||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -1,14 +1,12 @@
|
||||
{
|
||||
".nuxt/nuxt-custom-elements/entries/unraid-components.client.css": {
|
||||
"file": "_nuxt/unraid-components.client-fad7c220.css",
|
||||
"src": ".nuxt/nuxt-custom-elements/entries/unraid-components.client.css"
|
||||
},
|
||||
".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs": {
|
||||
"css": [
|
||||
"_nuxt/unraid-components.client-fad7c220.css"
|
||||
],
|
||||
"file": "_nuxt/unraid-components.client-cd1b3939.js",
|
||||
"file": "_nuxt/unraid-components.client-CQOXNcK4.js",
|
||||
"name": "unraid-components.client",
|
||||
"src": ".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs",
|
||||
"isEntry": true,
|
||||
"src": ".nuxt/nuxt-custom-elements/entries/unraid-components.client.mjs"
|
||||
}
|
||||
"css": [
|
||||
"_nuxt/unraid-components-p_3YF86n.css"
|
||||
]
|
||||
},
|
||||
"ts": 1723595088
|
||||
}
|
@@ -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>
|
||||
|
@@ -19,7 +19,7 @@ Code="e944"
|
||||
?>
|
||||
<?
|
||||
// Remove stale /tmp/plugin/*.plg entries (check that script 'plugin' is not running to avoid clashes)
|
||||
if (!exec("pgrep -f $docroot/plugins/dynamix.plugin.manager/scripts/plugin")) {
|
||||
if (!exec('pgrep --ns $$ -f '."$docroot/plugins/dynamix.plugin.manager/scripts/plugin")) {
|
||||
foreach (glob("/tmp/plugins/*.{plg,txt}", GLOB_NOSORT+GLOB_BRACE) as $entry) if (!file_exists("/var/log/plugins/".basename($entry))) @unlink($entry);
|
||||
}
|
||||
$check = $notify['version'] ? 0 : 1;
|
||||
|
@@ -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>
|
||||
|
@@ -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()
|
||||
{
|
||||
@@ -124,7 +124,7 @@ class UnraidOsCheck
|
||||
if ($parsedAltUrl) $params['altUrl'] = $parsedAltUrl;
|
||||
|
||||
$urlbase = $parsedAltUrl ?? $defaultUrl;
|
||||
$url = $urlbase.'?'.http_build_query($params);
|
||||
$url = $urlbase.'?'.http_build_query($params);
|
||||
$curlinfo = [];
|
||||
$response = http_get_contents($url,[],$curlinfo);
|
||||
if (array_key_exists('error', $curlinfo)) {
|
||||
@@ -258,4 +258,4 @@ $isGetRequest = !empty($_SERVER) && isset($_SERVER['REQUEST_METHOD']) && $_SERVE
|
||||
$getHasAction = $_GET !== null && !empty($_GET) && isset($_GET['action']);
|
||||
if ($isGetRequest && $getHasAction) {
|
||||
new UnraidOsCheck();
|
||||
}
|
||||
}
|
@@ -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();
|
@@ -49,7 +49,7 @@ function showCPUs($uuid) {
|
||||
function vsize($size,$expand=true) {
|
||||
$units = ['','K','M','G','T','P','E','Z','Y'];
|
||||
if ($expand) {
|
||||
$size = str_replace(['B',' ',',', '.'],'',strtoupper($size));
|
||||
$size = str_replace(['B',' ',','],'',strtoupper($size));
|
||||
[$c1,$c2] = my_preg_split('/(?<=[0-9])(?=[A-Z])/',$size);
|
||||
return $c1 * pow(1024,array_search($c2,$units)?:0);
|
||||
} else {
|
||||
@@ -489,6 +489,7 @@ $(function() {
|
||||
<input type="button" onclick="stopAll()" value="_(Stop All)_" style="display:none">
|
||||
|
||||
<div id="dialogWindow"></div>
|
||||
<div id="iframe-popup"></div>
|
||||
|
||||
<div id="templateISO" class="template">
|
||||
<dl>
|
||||
|
@@ -24,36 +24,74 @@ require_once "$docroot/plugins/dynamix.vm.manager/include/libvirt_helpers.php";
|
||||
$hardware = !empty(shell_exec("/etc/rc.d/rc.libvirt test"));
|
||||
if (!$hardware) {
|
||||
echo "<p class='notice'>"._('Your hardware does not have Intel VT-x or AMD-V capability').". "._('This is required to create VMs in KVM').". "._('Please disable the VM function').". ";
|
||||
echo "<a href='https://docs.unraid.net/unraid-os/manual/vm-management#determining-hvmiommu-hardware-support' target='_blank'> "._('Click here to see the Unraid Wiki for more information')."</a></p>";
|
||||
echo "<a href='https://docs.unraid.net/go/determining-hvmiommu-hardware-support/' target='_blank'> "._('View the Docs for more information')."</a></p>";
|
||||
}
|
||||
|
||||
function scan($area, $text) {
|
||||
return strpos($area,$text)!==false;
|
||||
}
|
||||
function detect(&$syslinux, $key) {
|
||||
$size = count($syslinux);
|
||||
$menu = $i = 0;
|
||||
$value = '';
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($syslinux[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'menu default')) $menu = 1;
|
||||
if (scan($syslinux[$n],'append')) foreach (explode(' ',$syslinux[$n]) as $cmd) if (scan($cmd,$key)) {$value = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
function detect(&$bootcfg, $bootenv, $key) {
|
||||
if ($bootenv === 'syslinux') {
|
||||
$size = count($bootcfg);
|
||||
$menu = $i = 0;
|
||||
$value = '';
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($bootcfg[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'menu default')) $menu = 1;
|
||||
if (scan($bootcfg[$n],'append')) foreach (explode(' ',$bootcfg[$n]) as $cmd) if (scan($cmd,$key)) {$value = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
} elseif ($bootenv === 'grub') {
|
||||
$menu_entries = [];
|
||||
// find the current boot entry
|
||||
foreach ($bootcfg as $line) {
|
||||
if (preg_match('/set default=(\d+)/', $line, $match)) {
|
||||
$bootentry = (int)$match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// split boot entries
|
||||
foreach ($bootcfg as $line) {
|
||||
if (strpos($line, 'menuentry ') === 0) {
|
||||
$in_menuentry = true;
|
||||
$current_entry = $line . "\n";
|
||||
} elseif ($in_menuentry) {
|
||||
$current_entry .= $line . "\n";
|
||||
if (trim($line) === "}") {
|
||||
$menu_entries[] = $current_entry;
|
||||
$in_menuentry = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// search in selected menuentry
|
||||
$menuentry = explode("\n", $menu_entries[$bootentry]);
|
||||
foreach (explode(' ', $menu_entries[$bootentry]) as $cmd) {
|
||||
if (scan($cmd,$key)) {
|
||||
$value = explode('=',$cmd)[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
return $value;
|
||||
return trim($value);
|
||||
}
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$bootcfg = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$bootenv = 'syslinux';
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$bootcfg = file('/boot/grub/grub.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$bootenv = 'grub';
|
||||
}
|
||||
$syslinux = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$arrValidNetworks = getValidNetworks();
|
||||
$pcie_acs_override = detect($syslinux, 'pcie_acs_override');
|
||||
$vfio_allow_unsafe = detect($syslinux, 'allow_unsafe_interrupts');
|
||||
$pcie_acs_override = detect($bootcfg, $bootenv, 'pcie_acs_override');
|
||||
$vfio_allow_unsafe = detect($bootcfg, $bootenv, 'allow_unsafe_interrupts');
|
||||
$bgcolor = strstr('white,azure',$display['theme']) ? '#f2f2f2' : '#1c1c1c';
|
||||
$started = $var['fsState']=='Started';
|
||||
$libvirt_up = $libvirt_running=='yes';
|
||||
@@ -125,7 +163,7 @@ _(Libvirt storage location)_:
|
||||
|
||||
<?endif;?>
|
||||
_(Default VM storage path)_:
|
||||
: <input type="text" id="domaindir" name="DOMAINDIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="<?=is_dir('/mnt/user')?'/mnt/user':'/mnt'?>" value="<?=htmlspecialchars($domain_cfg['DOMAINDIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
|
||||
: <input type="text" id="domaindir" name="DOMAINDIR" autocomplete="off" spellcheck="false" data-pickfolders="true" data-pickfilter="HIDE_FILES_FILTER" data-pickroot="/mnt" value="<?=htmlspecialchars($domain_cfg['DOMAINDIR'])?>" placeholder="_(Click to Select)_" pattern="^[^\\]*/$">
|
||||
<?if (!$started):?><span><i class="fa fa-warning icon warning"></i> _(Modify with caution: unable to validate path until Array is Started)_</span>
|
||||
<?elseif (!is_dir($domain_cfg['DOMAINDIR'])):?><span><i class="fa fa-warning icon warning"></i> _(Path does not exist)_</span><?endif;?>
|
||||
|
||||
@@ -183,6 +221,14 @@ _(Console Options)_:
|
||||
|
||||
:vms_console_help:
|
||||
|
||||
_(Show RDP menu option)_:
|
||||
: <select id="vmsrdpopt" name="RDPOPT">
|
||||
<?=mk_option($domain_cfg['RDPOPT'], 'no', _('Dont show RDP option'))?>
|
||||
<?=mk_option($domain_cfg['RDPOPT'], 'yes', _('Show RDP option'))?>
|
||||
</select>
|
||||
|
||||
:vms_rdpopt_help:
|
||||
|
||||
_(Show VM Usage)_:
|
||||
: <select id="vmusage" name="USAGE">
|
||||
<?=mk_option($domain_cfg['USAGE'], 'N', _('Disabled'))?>
|
||||
@@ -313,7 +359,7 @@ $(function(){
|
||||
<?if ($safemode):?>
|
||||
if (run) $("#settingsForm").submit();
|
||||
<?else:?>
|
||||
if (run) $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:'syslinux',pcie:$('#pcie_acs_override').val(),vfio:$('#vfio_allow_unsafe').val()}, function(data){
|
||||
if (run) $.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:'cmdlineoverride',pcie:$('#pcie_acs_override').val(),vfio:$('#vfio_allow_unsafe').val()}, function(data){
|
||||
$("#settingsForm").submit();
|
||||
});
|
||||
<?endif;?>
|
||||
|
@@ -19,7 +19,7 @@ Cond="exec(\"grep -o '^USAGE=.Y' /boot/config/domain.cfg 2>/dev/null\") && is_fi
|
||||
$docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
|
||||
?>
|
||||
<table id="vmstats" class="tablesorter four shift">
|
||||
<thead class='child'><tr><th class="th1">_(Name)_</th><th class="th2">_(Guest CPU)_</th><th>_(Host CPU)_</th><th>_(Memory)_</th><th>_(Disk IO)_</th><th>_(Network IO)_</th></tr></thead>
|
||||
<thead class='child'><tr><th class="th1">_(Name)_</th><th class="th2">_(Guest CPU)_</th><th>_(Host CPU)_</th><th>_(Memory inuse/Current/Maximum)_</th><th>_(Disk IO)_</th><th>_(Network IO)_</th></tr></thead>
|
||||
<tbody id ="vmstatsbody" class='child'>
|
||||
</tbody>
|
||||
</table>
|
||||
|
@@ -72,6 +72,7 @@ foreach ($vms as $vm) {
|
||||
$diskdesc = 'Current physical size: '.$lv->get_disk_capacity($res, true)."\nDefault snapshot type: $fstype";
|
||||
}
|
||||
$arrValidDiskBuses = getValidDiskBuses();
|
||||
$WebUI = html_entity_decode($arrConfig['template']['webui']);
|
||||
$vmrcport = $lv->domain_get_vnc_port($res);
|
||||
$autoport = $lv->domain_get_vmrc_autoport($res);
|
||||
$vmrcurl = '';
|
||||
@@ -93,6 +94,7 @@ foreach ($vms as $vm) {
|
||||
if (!empty($arrConfig['gpu'])) {
|
||||
$arrValidGPUDevices = getValidGPUDevices();
|
||||
foreach ($arrConfig['gpu'] as $arrGPU) {
|
||||
if ($arrGPU['id'] == "nogpu") {$graphics .= "No GPU"."\n";continue;}
|
||||
foreach ($arrValidGPUDevices as $arrDev) {
|
||||
if ($arrGPU['id'] == $arrDev['id']) {
|
||||
if (count(array_filter($arrValidGPUDevices, function($v) use ($arrDev) { return $v['name'] == $arrDev['name']; })) > 1) {
|
||||
@@ -109,7 +111,8 @@ foreach ($vms as $vm) {
|
||||
}
|
||||
unset($dom);
|
||||
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
|
||||
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log),addslashes($fstype), $vmrcconsole,false);
|
||||
if (!isset($domain_cfg["RDPOPT"])) $vmrcconsole .= ";no" ; else $vmrcconsole .= ";".$domain_cfg["RDPOPT"] ;
|
||||
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s', '%s')\"", addslashes($vm),addslashes($uuid),addslashes($template),$state,addslashes($vmrcurl),strtoupper($vmrcprotocol),addslashes($log),addslashes($fstype), $vmrcconsole,false,addslashes(str_replace('"',"'",$WebUI)));
|
||||
$kvm[] = "kvm.push({id:'$uuid',state:'$state'});";
|
||||
switch ($state) {
|
||||
case 'running':
|
||||
|
@@ -33,30 +33,60 @@ function scan($line, $text) {
|
||||
return stripos($line,$text)!==false;
|
||||
}
|
||||
|
||||
function embed(&$syslinux, $key, $value) {
|
||||
$size = count($syslinux);
|
||||
$make = false;
|
||||
$new = strlen($value) ? "$key=$value" : false;
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode
|
||||
if (scan($syslinux[$i],'label ') && !scan($syslinux[$i],'safe mode') && !scan($syslinux[$i],'safemode')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'append ')) {
|
||||
$cmd = preg_split('/\s+/',trim($syslinux[$n]));
|
||||
// replace the existing setting
|
||||
for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],$key)) {$make |= ($cmd[$c]!=$new); $cmd[$c] = $new; break;}
|
||||
// or insert the new setting
|
||||
if ($c==count($cmd) && $new) {array_splice($cmd,-1,0,$new); $make = true;}
|
||||
$syslinux[$n] = ' '.str_replace(' ',' ',implode(' ',$cmd));
|
||||
function embed(&$bootcfg, $env, $key, $value) {
|
||||
if ($env === 'syslinux') {
|
||||
$size = count($bootcfg);
|
||||
$make = false;
|
||||
$new = strlen($value) ? "$key=$value" : false;
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode
|
||||
if (scan($bootcfg[$i],'label ') && !scan($bootcfg[$i],'safe mode') && !scan($bootcfg[$i],'safemode')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'append ')) {
|
||||
$cmd = preg_split('/\s+/',trim($bootcfg[$n]));
|
||||
// replace the existing setting
|
||||
for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],$key)) {$make |= ($cmd[$c]!=$new); $cmd[$c] = $new; break;}
|
||||
// or insert the new setting
|
||||
if ($c==count($cmd) && $new) {array_splice($cmd,-1,0,$new); $make = true;}
|
||||
$bootcfg[$n] = ' '.str_replace(' ',' ',implode(' ',$cmd));
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
$n++;
|
||||
$i = $n - 1;
|
||||
}
|
||||
$i = $n - 1;
|
||||
$i++;
|
||||
}
|
||||
} elseif ($env === 'grub') {
|
||||
$size = count($bootcfg);
|
||||
$make = false;
|
||||
$new = strlen($value) ? "$key=$value" : false;
|
||||
$i = 0;
|
||||
while ($i < $size) {
|
||||
// find sections and exclude safemode/memtest
|
||||
if (scan($bootcfg[$i],'menuentry ') && !scan($bootcfg[$i],'safe mode') && !scan($bootcfg[$i],'safemode') && !scan($bootcfg[$i],'memtest')) {
|
||||
$n = $i + 1;
|
||||
// find the current requested setting
|
||||
while (!scan($bootcfg[$n],'menuentry ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'linux ')) {
|
||||
$cmd = preg_split('/\s+/',trim($bootcfg[$n]));
|
||||
// replace the existing setting
|
||||
for ($c = 1; $c < count($cmd); $c++) if (scan($cmd[$c],$key)) {$make |= ($cmd[$c]!=$new); $cmd[$c] = $new; break;}
|
||||
// or insert the new setting
|
||||
if ($c == count($cmd) && $new) {
|
||||
$cmd[] = $new;
|
||||
$make = true;
|
||||
}
|
||||
$bootcfg[$n] = ' ' . str_replace(' ', ' ', implode(' ', $cmd));
|
||||
}
|
||||
$n++;
|
||||
}
|
||||
$i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
return $make;
|
||||
}
|
||||
@@ -128,6 +158,20 @@ case 'domain-start-consoleRV':
|
||||
$arrResponse['vvfile'] = $vvfile;
|
||||
break;
|
||||
|
||||
case 'domain-consoleRDP':
|
||||
requireLibvirt();
|
||||
$dom = $lv->get_domain_by_name($domName);
|
||||
$rdpvarray = array() ;
|
||||
$myIP=get_vm_ip($dom);
|
||||
if ($myIP == NULL) {$arrResponse['error'] = "No IP, guest agent not installed?"; break; }
|
||||
$rdparray[] = "full address:s: $myIP\n";
|
||||
#$rdparray[] = "administrative session:1\n";
|
||||
if (!is_dir("/mnt/user/system/remoteviewer")) mkdir("/mnt/user/system/remoteviewer") ;
|
||||
$rdpfile = "/mnt/user/system/remoteviewer/rv"._var($_SERVER,'HTTP_HOST').".$port.rdp" ;
|
||||
file_put_contents($rdpfile,$rdparray) ;
|
||||
$arrResponse['vvfile'] = $rdpfile;
|
||||
break;
|
||||
|
||||
case 'domain-consoleRV':
|
||||
requireLibvirt();
|
||||
$dom = $lv->get_domain_by_name($domName);
|
||||
@@ -146,6 +190,23 @@ case 'domain-consoleRV':
|
||||
file_put_contents($vvfile,$vvarray) ;
|
||||
$arrResponse['vvfile'] = $vvfile;
|
||||
break;
|
||||
|
||||
case 'domain-openWebUI':
|
||||
requireLibvirt();
|
||||
$dom = $lv->get_domain_by_name($domName);
|
||||
$WebUI = unscript(_var($_REQUEST,'vmrcurl'));
|
||||
$myIP = get_vm_ip($dom);
|
||||
if (strpos($WebUI,"[IP]") && $myIP == NULL) $arrResponse['error'] = "No IP, guest agent not installed?";
|
||||
$WebUI = preg_replace("%\[IP\]%", $myIP, $WebUI);
|
||||
$vmnamehypen = str_replace(" ","-",$domName);
|
||||
$WebUI = preg_replace("%\[VMNAME\]%", $vmnamehypen, $WebUI);
|
||||
if (preg_match("%\[PORT:(\d+)\]%", $WebUI, $matches)) {
|
||||
$ConfigPort = $matches[1] ?? '';
|
||||
$WebUI = preg_replace("%\[PORT:\d+\]%", $ConfigPort, $WebUI);
|
||||
}
|
||||
$arrResponse['vmrcurl'] = $WebUI;
|
||||
break;
|
||||
|
||||
|
||||
case 'domain-pause':
|
||||
requireLibvirt();
|
||||
@@ -391,13 +452,68 @@ case 'snap-desc':
|
||||
: ['error' => $lv->get_last_error()];
|
||||
break;
|
||||
|
||||
case 'get_storage_fstype':
|
||||
$fstype = get_storage_fstype(unscript(_var($_REQUEST,'storage')));
|
||||
$arrResponse = ['fstype' => $fstype , 'success' => true] ;
|
||||
break;
|
||||
|
||||
case 'vm-removal':
|
||||
requireLibvirt();
|
||||
$arrResponse = ($data = getvmsnapshots($domName))
|
||||
? ['success' => true]
|
||||
: ['error' => $lv->get_last_error()];
|
||||
$datartn = $disksrtn = "";
|
||||
foreach($data as $snap=>$snapdetail) {
|
||||
$snapshotdatetime = date("Y-m-d H:i:s",$snapdetail["creationtime"]) ;
|
||||
$datartn .= "$snap $snapshotdatetime\n" ;
|
||||
}
|
||||
$disks = $lv->get_disk_stats($domName);
|
||||
|
||||
foreach($disks as $diskid=>$diskdetail) {
|
||||
if ($diskid == 0) $pathinfo = pathinfo($diskdetail['file']);
|
||||
}
|
||||
|
||||
$list = glob($pathinfo['dirname']."/*");
|
||||
$uuid = $lv->domain_get_uuid($domName);
|
||||
|
||||
$list2 = glob("/etc/libvirt/qemu/nvram/*$uuid*");
|
||||
$listnew = array();
|
||||
$list=array_merge($list,$list2);
|
||||
foreach($list as $key => $listent)
|
||||
{
|
||||
$pathinfo = pathinfo($listent);
|
||||
$listnew[] = "{$pathinfo['basename']} ({$pathinfo['dirname']})";
|
||||
}
|
||||
sort($listnew,SORT_NATURAL);
|
||||
$listcount = count($listnew);
|
||||
$snapcount = count($data);
|
||||
$disksrtn=implode("\n",$listnew);
|
||||
|
||||
|
||||
|
||||
if (strpos($dirname,'/mnt/user/')===0) {
|
||||
$realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($dirname)." 2>/dev/null"));
|
||||
if (!empty($realdisk)) {
|
||||
$dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", $dirname);
|
||||
}
|
||||
}
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' $dirname"));
|
||||
$html = '<table class="snapshot">
|
||||
<tr><td>'._('VM Being removed').':</td><td><span id="VMBeingRemoved">'.$domName.'</span></td></tr>
|
||||
<tr><td>'._('Remove all files').':</td><td><input type="checkbox" id="All" checked value="" ></td></tr>
|
||||
<tr><td>'._('Files being removed').':</td><td><textarea id="textfiles" class="xml" rows="'.$listcount.'" style="white-space: pre; overflow: auto; width:600px" disabled>'.$disksrtn.'</textarea></td></tr>
|
||||
<tr><td>'._('Snapshots being removed').':</td><td><textarea id="textsnaps" rows="'.$snapsount.'" cols="80" disabled>'.$datartn.'</textarea></td></tr>
|
||||
</table>';
|
||||
$arrResponse = ['html' => $html , 'success' => true] ;
|
||||
break;
|
||||
|
||||
case 'disk-create':
|
||||
$disk = $_REQUEST['disk'];
|
||||
$driver = $_REQUEST['driver'];
|
||||
$size = str_replace(["KB","MB","GB","TB","PB", " ", ","], ["K","M","G","T","P", "", ""], strtoupper($_REQUEST['size']));
|
||||
$dir = dirname($disk);
|
||||
if (!is_dir($dir)) mkdir($dir);
|
||||
#if (!is_dir($dir)) my_mkdir($dir);
|
||||
#if (!is_dir($dir)) mkdir($dir);
|
||||
if (!is_dir($dir)) my_mkdir($dir);
|
||||
// determine the actual disk if user share is being used
|
||||
$dir = transpose_user_path($dir);
|
||||
#@exec("chattr +C -R ".escapeshellarg($dir)." >/dev/null");
|
||||
@@ -505,26 +621,39 @@ case 'hot-detach-usb':
|
||||
//TODO
|
||||
break;
|
||||
|
||||
case 'syslinux':
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$syslinux = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$m1 = embed($syslinux, 'pcie_acs_override', $_REQUEST['pcie']);
|
||||
$m2 = embed($syslinux, 'vfio_iommu_type1.allow_unsafe_interrupts', $_REQUEST['vfio']);
|
||||
if ($m1||$m2) file_put_contents($cfg, implode("\n",$syslinux)."\n");
|
||||
case 'cmdlineoverride':
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$env = 'syslinux';
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$cfg = '/boot/grub/grub.cfg';
|
||||
$env = 'grub';
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES);
|
||||
}
|
||||
$m1 = embed($bootcfg, $env, 'pcie_acs_override', $_REQUEST['pcie']);
|
||||
$m2 = embed($bootcfg, $env, 'vfio_iommu_type1.allow_unsafe_interrupts', $_REQUEST['vfio']);
|
||||
if ($m1||$m2) file_put_contents($cfg, implode("\n",$bootcfg)."\n");
|
||||
$arrResponse = ['success' => true, 'modified' => $m1|$m2];
|
||||
break;
|
||||
|
||||
case 'reboot':
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$syslinux = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$cfg = '/boot/syslinux/syslinux.cfg';
|
||||
$env = 'syslinux';
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$cfg = '/boot/grub/grub.cfg';
|
||||
$env = 'grub';
|
||||
}
|
||||
$bootcfg = file($cfg, FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$cmdline = explode(' ',file_get_contents('/proc/cmdline'));
|
||||
$pcie = $vfio = '';
|
||||
foreach ($cmdline as $cmd) {
|
||||
if (scan($cmd,'pcie_acs_override')) $pcie = explode('=',$cmd)[1];
|
||||
if (scan($cmd,'allow_unsafe_interrupts')) $vfio = explode('=',$cmd)[1];
|
||||
}
|
||||
$m1 = embed($syslinux, 'pcie_acs_override', $pcie);
|
||||
$m2 = embed($syslinux, 'vfio_iommu_type1.allow_unsafe_interrupts', $vfio);
|
||||
$m1 = embed($bootcfg, $env, 'pcie_acs_override', $pcie);
|
||||
$m2 = embed($bootcfg, $env, 'vfio_iommu_type1.allow_unsafe_interrupts', $vfio);
|
||||
$arrResponse = ['success' => true, 'modified' => $m1|$m2];
|
||||
break;
|
||||
|
||||
|
@@ -93,14 +93,18 @@ if (isset($_GET['uuid'])) {
|
||||
}
|
||||
$arrLoad['form'] = $arrAllTemplates[$strSelectedTemplate]['form'];
|
||||
}
|
||||
$usertemplate = 0;
|
||||
$strSelectedTemplateUT = $strSelectedTemplate;
|
||||
if (strpos($strSelectedTemplate,"User-") !== false) $strSelectedTemplateUT = str_replace("User-","",$strSelectedTemplateUT);
|
||||
if (strpos($strSelectedTemplate,"User-") !== false) {
|
||||
$strSelectedTemplateUT = str_replace("User-","",$strSelectedTemplateUT);
|
||||
$usertemplate = 1;
|
||||
}
|
||||
?>
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/plugins/dynamix.vm.manager/styles/dynamix.vm.manager.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.filetree.css')?>">
|
||||
<link type="text/css" rel="stylesheet" href="<?autov('/webGui/styles/jquery.switchbutton.css')?>">
|
||||
|
||||
<span class="status advancedview_panel" style="margin-top:<?=$top?>px"><input type="checkbox" class="advancedview"></span>
|
||||
<span class="status advancedview_panel" style="margin-top:<?=$top?>px;"><input type="checkbox" class="inlineview"><input type="checkbox" class="advancedview"></span>
|
||||
<div class="domain">
|
||||
<form id="vmform" method="POST">
|
||||
<input type="hidden" name="domain[type]" value="kvm" />
|
||||
@@ -109,7 +113,7 @@ if (strpos($strSelectedTemplate,"User-") !== false) $strSelectedTemplateUT = str
|
||||
<table>
|
||||
<tr>
|
||||
<td>_(Icon)_:</td>
|
||||
<td>
|
||||
<td class="template_img_parent">
|
||||
<input type="hidden" name="template[icon]" id="template_icon" value="<?=htmlspecialchars($arrLoad['icon'])?>" />
|
||||
<img id="template_img" src="<?=htmlspecialchars($strIconURL)?>" width="48" height="48" title="_(Change Icon)_..."/>
|
||||
<div id="template_img_chooser_outer">
|
||||
@@ -159,6 +163,21 @@ function isVMXMLMode() {
|
||||
return ($.cookie('vmmanager_listview_mode') == 'xml');
|
||||
}
|
||||
|
||||
function isinlineXMLMode() {
|
||||
return ($.cookie('vmmanager_inline_mode') == 'show');
|
||||
}
|
||||
|
||||
function hidexml(checked)
|
||||
{
|
||||
var form = document.getElementById("vmform"); // Replace "yourFormId" with the actual ID of your form
|
||||
var xmlElements = form.getElementsByClassName("xml");
|
||||
if (checked == 0) xmldisplay = "none"; else xmldisplay = "";
|
||||
// Unhide each element
|
||||
for (var i = 0; i < xmlElements.length; i++) {
|
||||
xmlElements[i].style.display = xmldisplay; // Setting to empty string will revert to default style
|
||||
}
|
||||
}
|
||||
|
||||
$(function() {
|
||||
$('.autostart').switchButton({
|
||||
on_label: "_(Yes)_",
|
||||
@@ -170,15 +189,25 @@ $(function() {
|
||||
});
|
||||
|
||||
$('.advancedview').switchButton({
|
||||
labels_placement: "left",
|
||||
labels_placement: "right",
|
||||
on_label: "_(XML View)_",
|
||||
off_label: "_(Form View)_",
|
||||
checked: isVMXMLMode()
|
||||
});
|
||||
$('.inlineview').switchButton({
|
||||
labels_placement: "right",
|
||||
off_label: "_(Hide inline xml)_",
|
||||
on_label: "_(Show Inline XML)_",
|
||||
checked: isinlineXMLMode()
|
||||
});
|
||||
$('.advancedview').change(function () {
|
||||
toggleRows('xmlview', $(this).is(':checked'), 'formview');
|
||||
$.cookie('vmmanager_listview_mode', $(this).is(':checked') ? 'xml' : 'form', { expires: 3650 });
|
||||
});
|
||||
$('.inlineview').change(function () {
|
||||
hidexml($(this).is(':checked'));
|
||||
$.cookie('vmmanager_inline_mode', $(this).is(':checked') ? 'show' : 'hide', { expires: 3650 });
|
||||
});
|
||||
|
||||
$('#template_img').click(function (){
|
||||
var p = $(this).position();
|
||||
@@ -225,6 +254,7 @@ $(function() {
|
||||
} else {
|
||||
$('.advancedview_panel').fadeOut('fast');
|
||||
}
|
||||
hidexml(isinlineXMLMode());
|
||||
|
||||
$("#vmform #btnCancel").click(function (){
|
||||
done();
|
||||
|
@@ -175,10 +175,10 @@
|
||||
|
||||
// create folder if needed
|
||||
if (!is_dir($strImgFolder)) {
|
||||
mkdir($strImgFolder, 0777, true);
|
||||
#my_mkdir($strImgFolder, 0777, true);
|
||||
chown($strImgFolder, 'nobody');
|
||||
chgrp($strImgFolder, 'users');
|
||||
#mkdir($strImgFolder, 0777, true);
|
||||
my_mkdir($strImgFolder, 0777, true);
|
||||
#chown($strImgFolder, 'nobody');
|
||||
#chgrp($strImgFolder, 'users');
|
||||
}
|
||||
|
||||
$this->set_folder_nodatacow($strImgFolder);
|
||||
@@ -192,10 +192,10 @@
|
||||
|
||||
// create parent folder if needed
|
||||
if (!is_dir($path_parts['dirname'])) {
|
||||
mkdir($path_parts['dirname'], 0777, true);
|
||||
#my_mkdir($path_parts['dirname'], 0777, true);
|
||||
chown($path_parts['dirname'], 'nobody');
|
||||
chgrp($path_parts['dirname'], 'users');
|
||||
#mkdir($path_parts['dirname'], 0777, true);
|
||||
my_mkdir($path_parts['dirname'], 0777, true);
|
||||
#chown($path_parts['dirname'], 'nobody');
|
||||
#chgrp($path_parts['dirname'], 'users');
|
||||
}
|
||||
|
||||
$this->set_folder_nodatacow($path_parts['dirname']);
|
||||
@@ -219,10 +219,10 @@
|
||||
// create folder if needed
|
||||
$strImgRawLocationParent = dirname($strImgRawLocationPath);
|
||||
if (!is_dir($strImgRawLocationParent)) {
|
||||
mkdir($strImgRawLocationParent, 0777, true);
|
||||
#my_mkdir($strImgRawLocationParent, 0777, true);
|
||||
chown($strImgRawLocationParent, 'nobody');
|
||||
chgrp($strImgRawLocationParent, 'users');
|
||||
#mkdir($strImgRawLocationParent, 0777, true);
|
||||
my_mkdir($strImgRawLocationParent, 0777, true);
|
||||
#chown($strImgRawLocationParent, 'nobody');
|
||||
#chgrp($strImgRawLocationParent, 'users');
|
||||
}
|
||||
|
||||
$this->set_folder_nodatacow($strImgRawLocationParent);
|
||||
@@ -267,6 +267,9 @@
|
||||
if (!empty($disk['serial'])) {
|
||||
$arrReturn['serial'] = $disk['serial'];
|
||||
}
|
||||
if (!empty($disk['discard'])) {
|
||||
$arrReturn['discard'] = $disk['discard'];
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -290,6 +293,7 @@
|
||||
$audios = $config['audio'];
|
||||
$template = $config['template'];
|
||||
$clocks = $config['clock'];
|
||||
$evdevs = $config['evdev'];
|
||||
|
||||
$type = $domain['type'];
|
||||
$name = $domain['name'];
|
||||
@@ -698,8 +702,10 @@
|
||||
if ($strDevType == 'file' || $strDevType == 'block') {
|
||||
$strSourceType = ($strDevType == 'file' ? 'file' : 'dev');
|
||||
|
||||
if (isset($disk['discard'])) $strDevUnmap = " discard=\"{$disk['discard']}\" "; else $strDevUnmap = " discard=\"ignore\" ";
|
||||
|
||||
$diskstr .= "<disk type='" . $strDevType . "' device='disk'>
|
||||
<driver name='qemu' type='" . $disk['driver'] . "' cache='writeback'/>
|
||||
<driver name='qemu' type='" . $disk['driver'] . "' cache='writeback'".$strDevUnmap."/>
|
||||
<source " . $strSourceType . "='" . htmlspecialchars($disk['image'], ENT_QUOTES | ENT_XML1) . "'/>
|
||||
<target bus='" . $disk['bus'] . "' dev='" . $disk['dev'] . "' $rotation_rate />
|
||||
$bootorder
|
||||
@@ -798,7 +804,7 @@
|
||||
if (empty($gpu['id']) || in_array($gpu['id'], $gpudevs_used)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($gpu['id'] == 'nogpu') break;
|
||||
if ($gpu['id'] == 'virtual') {
|
||||
$strKeyMap = '';
|
||||
if (!empty($gpu['keymap'])) {
|
||||
@@ -850,6 +856,7 @@
|
||||
</graphics>
|
||||
<video>
|
||||
<model type='$strModelType'/>
|
||||
<address type='pci' domain='0x0000' bus='0x00' slot='0x1e' function='0x0'/>
|
||||
</video>
|
||||
<audio id='1' type='$virtualaudio'/>";
|
||||
|
||||
@@ -903,10 +910,17 @@
|
||||
}
|
||||
|
||||
if ($gpu['multi'] == "on"){
|
||||
$newgpu_bus = dechex(hexdec($gpu_bus) + 0x20) ;
|
||||
$newgpu_bus= 0x07;
|
||||
if (!isset($multibus[$newgpu_bus])) {
|
||||
$multibus[$newgpu_bus] = 0x07;
|
||||
} else {
|
||||
#Get next bus
|
||||
$newgpu_bus = end($multibus) + 0x01;
|
||||
$multibus[$newgpu_bus] = $newgpu_bus;
|
||||
}
|
||||
if ($machine_type == "pc") $newgpu_slot = "0x01" ; else $newgpu_slot = "0x00" ;
|
||||
$strSpecialAddress = "<address type='pci' domain='0x0000' bus='0x$newgpu_bus' slot='$newgpu_slot' function='0x".$gpu_function."' multifunction='on' />" ;
|
||||
$multidevices[$gpu_bus] = "0x$gpu_bus" ;
|
||||
$strSpecialAddress = "<address type='pci' domain='0x0000' bus='$newgpu_bus' slot='$newgpu_slot' function='0x".$gpu_function."' multifunction='on' />" ;
|
||||
$multidevices[$gpu_bus] = $newgpu_bus ;
|
||||
}
|
||||
|
||||
|
||||
@@ -924,9 +938,9 @@
|
||||
}
|
||||
}
|
||||
$audiodevs_used=[];
|
||||
$strSpecialAddressAudio = "" ;
|
||||
if (!empty($audios)) {
|
||||
foreach ($audios as $i => $audio) {
|
||||
$strSpecialAddressAudio = "" ;
|
||||
// Skip duplicate audio devices
|
||||
if (empty($audio['id']) || in_array($audio['id'], $audiodevs_used)) {
|
||||
continue;
|
||||
@@ -935,9 +949,9 @@
|
||||
[$audio_bus, $audio_slot, $audio_function] = my_explode(":", str_replace('.', ':', $audio['id']), 3);
|
||||
if ($audio_function != 0) {
|
||||
if (isset($multidevices[$audio_bus])) {
|
||||
$newaudio_bus = dechex(hexdec($audio_bus) + 0x20) ;
|
||||
$newaudio_bus = $multidevices[$audio_bus] ;
|
||||
if ($machine_type == "pc") $newaudio_slot = "0x01" ; else $newaudio_slot = "0x00" ;
|
||||
$strSpecialAddressAudio = "<address type='pci' domain='0x0000' bus='0x$newaudio_bus' slot='$newaudio_slot' function='0x".$audio_function."' />" ;
|
||||
$strSpecialAddressAudio = "<address type='pci' domain='0x0000' bus='$newaudio_bus' slot='$newaudio_slot' function='0x".$audio_function."' />" ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -954,9 +968,9 @@
|
||||
}
|
||||
|
||||
$pcidevs_used=[];
|
||||
$strSpecialAddressOther = "" ;
|
||||
if (!empty($pcis)) {
|
||||
foreach ($pcis as $i => $pci_id) {
|
||||
$strSpecialAddressOther = "" ;
|
||||
// Skip duplicate other pci devices
|
||||
if (empty($pci_id) || in_array($pci_id, $pcidevs_used)) {
|
||||
continue;
|
||||
@@ -966,9 +980,9 @@
|
||||
|
||||
if ($pci_function != 0) {
|
||||
if (isset($multidevices[$pci_bus])) {
|
||||
$newpci_bus = dechex(hexdec($pci_bus) + 0x20) ;
|
||||
$newpci_bus = $multidevices[$pci_bus];
|
||||
if ($machine_type == "pc") $newpci_slot = "0x01" ; else $newpci_slot = "0x00" ;
|
||||
$strSpecialAddressOther = "<address type='pci' domain='0x0000' bus='0x$newpci_bus' slot='$newpci_slot' function='0x".$pci_function."' />" ;
|
||||
$strSpecialAddressOther = "<address type='pci' domain='0x0000' bus='$newpci_bus' slot='$newpci_slot' function='0x".$pci_function."' />" ;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,6 +1012,16 @@
|
||||
</memballoon>";
|
||||
}
|
||||
#$osbootdev = "" ;
|
||||
$evdevstr = "";
|
||||
foreach($evdevs as $evdev) {
|
||||
if ($evdev['dev'] == "") continue;
|
||||
$evdevstr .= "<input type='evdev'>\n<source dev='{$evdev['dev']}'";
|
||||
if ($evdev['grab'] != "") $evdevstr .= " grab='{$evdev['grab']}' ";
|
||||
if ($evdev['grabToggle'] != "") $evdevstr .= " grabToggle='{$evdev['grabToggle']}' ";
|
||||
if ($evdev['repeat'] != "") $evdevstr .= " repeat='{$evdev['repeat']}' ";
|
||||
$evdevstr .= "/>\n</input>\n";
|
||||
}
|
||||
|
||||
$memorybackingXML = Array2XML::createXML('memoryBacking', $memorybacking);
|
||||
$memoryBackingXML = $memorybackingXML->saveXML($memorybackingXML->documentElement);
|
||||
return "<domain type='$type' xmlns:qemu='http://libvirt.org/schemas/domain/qemu/1.0'>
|
||||
@@ -1043,6 +1067,7 @@
|
||||
$channelscopypaste
|
||||
$swtpm
|
||||
$memballoon
|
||||
$evdevstr
|
||||
</devices>
|
||||
</domain>";
|
||||
|
||||
@@ -1326,6 +1351,7 @@
|
||||
if ($tmp) {
|
||||
$tmp['bus'] = $disk->target->attributes()->bus->__toString();
|
||||
$tmp["boot order"] = $disk->boot->attributes()->order ?? "";
|
||||
$tmp["discard"] = $disk->driver->attributes()->discard ?? "ignore";
|
||||
$tmp["rotation"] = $disk->target->attributes()->rotation_rate ?? "0";
|
||||
$tmp['serial'] = $disk->serial ;
|
||||
|
||||
@@ -1347,7 +1373,7 @@
|
||||
$this->_set_last_error();
|
||||
|
||||
$ret[] = [
|
||||
'device' => $disk->target->attributes()->dev,
|
||||
'device' => $disk->target->attributes()->dev->__toString(),
|
||||
'file' => $disk->source->attributes()->file,
|
||||
'type' => '-',
|
||||
'capacity' => '-',
|
||||
@@ -1356,7 +1382,8 @@
|
||||
'bus' => $disk->target->attributes()->bus->__toString(),
|
||||
'boot order' => $disk->boot->attributes()->order ,
|
||||
'rotation' => $disk->target->attributes()->rotation_rate ?? "0",
|
||||
'serial' => $disk->serial
|
||||
'serial' => $disk->serial,
|
||||
'discard' => $disk->driver->attributes()->discard ?? "ignore"
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -1473,7 +1500,7 @@
|
||||
$unit = strtoupper($unit);
|
||||
|
||||
switch ($unit) {
|
||||
case 'T': return number_format($value / (float)1099511627776, $decimals).'T';
|
||||
case 'T': return number_format($value / (float)1099511627776, $decimals +2).'T';
|
||||
case 'G': return number_format($value / (float)(1 << 30), $decimals).'G';
|
||||
case 'M': return number_format($value / (float)(1 << 20), $decimals).'M';
|
||||
case 'K': return number_format($value / (float)(1 << 10), $decimals).'K';
|
||||
@@ -1624,7 +1651,7 @@
|
||||
}
|
||||
|
||||
function get_domain_by_name($name) {
|
||||
$tmp = libvirt_domain_lookup_by_name($this->conn, $name);
|
||||
$tmp = @libvirt_domain_lookup_by_name($this->conn, $name);
|
||||
return ($tmp) ? $tmp : $this->_set_last_error();
|
||||
}
|
||||
|
||||
@@ -1801,7 +1828,7 @@
|
||||
}
|
||||
|
||||
function domain_define($xml, $autostart=false) {
|
||||
if (strpos($xml,'<qemu:commandline>')) {
|
||||
if (strpos($xml,'<qemu:commandline>') || strpos($xml,'<qemu:override>')) {
|
||||
$tmp = explode("\n", $xml);
|
||||
for ($i = 0; $i < sizeof($tmp); $i++)
|
||||
if (strpos('.'.$tmp[$i], "<domain type='kvm'") || strpos('.'.$tmp[$i], '<domain type="kvm"'))
|
||||
@@ -1976,7 +2003,19 @@
|
||||
if (is_file($disk)) unlink($disk);
|
||||
if (is_file($cfg)) unlink($cfg);
|
||||
if (is_file($xml)) unlink($xml);
|
||||
if (is_dir($dir) && $this->is_dir_empty($dir)) rmdir($dir);
|
||||
if (is_dir($dir) && $this->is_dir_empty($dir)) {
|
||||
$result= my_rmdir($dir);
|
||||
if ($result['type'] == "zfs") {
|
||||
qemu_log("$domain","delete empty zfs $dir {$result['rtncode']}");
|
||||
if (isset($result['dataset'])) qemu_log("$domain","dataset {$result['dataset']} ");
|
||||
if (isset($result['cmd'])) qemu_log("$domain","Command {$result['cmd']} ");
|
||||
if (isset($result['output'])) {
|
||||
$outputlogs = implode(" ",$result['output']);
|
||||
qemu_log("$domain","Output $outputlogs end");
|
||||
}
|
||||
}
|
||||
else qemu_log("$domain","delete empty $dir {$result['rtncode']}");
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
|
@@ -870,7 +870,7 @@ private static $encoding = 'UTF-8';
|
||||
$arrMatch['vendorname'] = sanitizeVendor($arrMatch['vendorname']);
|
||||
$arrMatch['productname'] = sanitizeProduct($arrMatch['productname']);
|
||||
|
||||
$arrValidPCIDevices[] = [
|
||||
$arrValidPCIDevices[$arrMatch['id']] = [
|
||||
'id' => $arrMatch['id'],
|
||||
'type' => $arrMatch['type'],
|
||||
'typeid' => $arrMatch['typeid'],
|
||||
@@ -931,6 +931,29 @@ private static $encoding = 'UTF-8';
|
||||
return $arrValidOtherStubbedDevices;
|
||||
}
|
||||
|
||||
function getValidevDev() {
|
||||
$inputevdev = array_merge(glob("/dev/input/by-id/*event-kbd"),glob("/dev/input/by-id/*event-mouse"));
|
||||
return $inputevdev;
|
||||
}
|
||||
|
||||
function getevDev($res) {
|
||||
global $lv ;
|
||||
$xml = $lv->domain_get_xml($res) ;
|
||||
$xmldoc = new SimpleXMLElement($xml);
|
||||
$xmlpath = $xmldoc->xpath('//devices/input[@type="evdev"] ');
|
||||
$evdevs = [];
|
||||
foreach ($xmlpath as $i => $evDev) {
|
||||
$evdevs[$i] = [
|
||||
'dev' => $evDev->source->attributes()->dev,
|
||||
'grab' => $evDev->source->attributes()->grab,
|
||||
'repeat' => $evDev->source->attributes()->repeat,
|
||||
'grabToggle' => $evDev->source->attributes()->grabToggle
|
||||
];
|
||||
}
|
||||
if (empty($evdevs)) $evdevs[0] = ['dev'=>"",'grab'=>"",'repeat'=>"",'grabToggle'=>""];
|
||||
return $evdevs;
|
||||
}
|
||||
|
||||
$cacheValidUSBDevices = null;
|
||||
function getValidUSBDevices() {
|
||||
global $cacheValidUSBDevices;
|
||||
@@ -974,7 +997,7 @@ private static $encoding = 'UTF-8';
|
||||
// Clean up the name
|
||||
$arrMatch['name'] = sanitizeVendor($arrMatch['name']);
|
||||
|
||||
$arrValidUSBDevices[] = [
|
||||
$arrValidUSBDevices[$arrMatch['id']] = [
|
||||
'id' => $arrMatch['id'],
|
||||
'name' => $arrMatch['name'],
|
||||
];
|
||||
@@ -1068,6 +1091,15 @@ private static $encoding = 'UTF-8';
|
||||
return $arrValidDiskBuses;
|
||||
}
|
||||
|
||||
function getValidDiskDiscard() {
|
||||
$arrValidDiskDiscard = [
|
||||
'ignore' => 'Ignore(No Trim)',
|
||||
'unmap' => 'Unmap(Trim)',
|
||||
];
|
||||
|
||||
return $arrValidDiskDiscard;
|
||||
}
|
||||
|
||||
function getValidCdromBuses() {
|
||||
$arrValidCdromBuses = [
|
||||
'scsi' => 'SCSI',
|
||||
@@ -1083,6 +1115,7 @@ private static $encoding = 'UTF-8';
|
||||
$arrValidVNCModels = [
|
||||
'cirrus' => 'Cirrus',
|
||||
'qxl' => 'QXL (best)',
|
||||
'virtio' => 'Virtio(2d)',
|
||||
'vmvga' => 'vmvga'
|
||||
];
|
||||
|
||||
@@ -1258,6 +1291,11 @@ private static $encoding = 'UTF-8';
|
||||
continue;
|
||||
}
|
||||
}
|
||||
if (empty($arrGPUDevices)) {
|
||||
$arrGPUDevices[] = [
|
||||
'id' => 'nogpu',
|
||||
];
|
||||
}
|
||||
|
||||
// Add claimed USB devices by this VM to the available USB devices
|
||||
/*
|
||||
@@ -1288,6 +1326,7 @@ private static $encoding = 'UTF-8';
|
||||
'driver' => $disk['type'],
|
||||
'dev' => $disk['device'],
|
||||
'bus' => $disk['bus'],
|
||||
'discard' => $disk['discard'],
|
||||
'boot' => $disk['boot order'],
|
||||
'rotation' => $disk['rotation'],
|
||||
'serial' => $disk['serial'],
|
||||
@@ -1302,6 +1341,7 @@ private static $encoding = 'UTF-8';
|
||||
'dev' => 'hda',
|
||||
'select' => '',
|
||||
'bus' => 'virtio',
|
||||
'discard' => 'ignore',
|
||||
'rotation' => "0"
|
||||
];
|
||||
}
|
||||
@@ -1330,7 +1370,13 @@ private static $encoding = 'UTF-8';
|
||||
}
|
||||
|
||||
if ($lv->domain_get_boot_devices($res)[0] == "fd") $osbootdev = "Yes" ; else $osbootdev = "No" ;
|
||||
|
||||
$vmname = $lv->domain_get_name($res);
|
||||
$cmdline = null;
|
||||
$QEMUCmdline = getQEMUCmdLine($strDOMXML);
|
||||
$QEMUOverride = getQEMUOverride($strDOMXML);
|
||||
if (isset($QEMUCmdline)) $cmdline = $QEMUCmdline;
|
||||
if (isset($QEMUOverride) && isset($QEMUCmdline)) $cmdline .= "\n".$QEMUOverride;
|
||||
if (isset($QEMUOverride) && !isset($QEMUCmdline)) $cmdline = $QEMUOverride;
|
||||
return [
|
||||
'template' => $arrTemplateValues,
|
||||
'domain' => [
|
||||
@@ -1370,8 +1416,13 @@ private static $encoding = 'UTF-8';
|
||||
'nic' => $arrNICs,
|
||||
'usb' => $arrUSBDevs,
|
||||
'shares' => $lv->domain_get_mount_filesystems($res),
|
||||
'qemucmdline' => getQEMUCmdLine($strDOMXML),
|
||||
'clocks' => getClocks($strDOMXML)
|
||||
'evdev' => getevDev($res),
|
||||
'qemucmdline' => $cmdline,
|
||||
'clocks' => getClocks($strDOMXML),
|
||||
'xml' => [
|
||||
'machine' => $lv->domain_get_xml($vmname, "//domain/os/*"),
|
||||
'devices' => $lv->get_xpath($vmname, "//domain/os/*"),
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
@@ -1418,10 +1469,16 @@ private static $encoding = 'UTF-8';
|
||||
// preserve existing disk driver settings
|
||||
foreach ($new['devices']['disk'] as $key => $disk) {
|
||||
$source = $disk['source']['@attributes']['file'];
|
||||
foreach ($old['devices']['disk'] as $k => $d) if ($source==$d['source']['@attributes']['file']) $new['devices']['disk'][$key]['driver']['@attributes'] = $d['driver']['@attributes'];
|
||||
if (isset($disk['driver']['@attributes']['discard'])) $discard = $disk['driver']['@attributes']['discard']; else $discard = null;
|
||||
foreach ($old['devices']['disk'] as $k => $d)
|
||||
if ($source==$d['source']['@attributes']['file']) {
|
||||
if (isset($discard)) $d['driver']['@attributes']['discard'] = $discard;
|
||||
$new['devices']['disk'][$key]['driver']['@attributes'] = $d['driver']['@attributes'];
|
||||
}
|
||||
}
|
||||
// settings not in the GUI, but maybe customized
|
||||
unset($old['clock']);
|
||||
unset($old['devices']['input']);
|
||||
// preserve vnc/spice port settings
|
||||
// unset($new['devices']['graphics']['@attributes']['port'],$new['devices']['graphics']['@attributes']['autoport']);
|
||||
if (!$new['devices']['graphics']) unset($old['devices']['graphics']);
|
||||
@@ -1561,7 +1618,19 @@ private static $encoding = 'UTF-8';
|
||||
$y = strpos($xml,"<qemu:commandline>", $z +19) ;
|
||||
if ($y != false) $z =$y ;
|
||||
}
|
||||
return substr($xml,$x, ($z + 19) -$x) ;
|
||||
return substr($xml,$x, ($z + 19) -$x);
|
||||
}
|
||||
|
||||
function getQEMUOverride($xml) {
|
||||
$x = strpos($xml,"<qemu:override>", 0) ;
|
||||
if ($x === false) return null ;
|
||||
$y = strpos($xml,"</qemu:override>", 0) ;
|
||||
$z=$y ;
|
||||
while ($y!=false) {
|
||||
$y = strpos($xml,"<qemu:override>", $z +16) ;
|
||||
if ($y != false) $z =$y ;
|
||||
}
|
||||
return substr($xml,$x, ($z + 16) -$x) ;
|
||||
}
|
||||
|
||||
function getchannels($res) {
|
||||
@@ -1596,6 +1665,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
Get new VM Name
|
||||
Extract XML for VM to be cloned.
|
||||
Check if snapshots.
|
||||
Check if directory exists.
|
||||
Check for disk space
|
||||
|
||||
@@ -1613,6 +1683,10 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
If option to edit, show VMUpdate
|
||||
*/
|
||||
$snaps = getvmsnapshots($vm);
|
||||
if (is_array($snaps)) {
|
||||
if (count($snaps) ) {write("addLog\0".htmlspecialchars(_("Clone of VM not currently supported if it has snapshots"))); $arrResponse = ['error' => _("Clone of VM not currently supported if it has snapshots")]; return false ;}
|
||||
}
|
||||
$uuid = $lv->domain_get_uuid($clone) ;
|
||||
write("addLog\0".htmlspecialchars(_("Checking if clone exists")));
|
||||
if ($uuid) { $arrResponse = ['error' => _("Clone VM name already inuse")]; return false ;}
|
||||
@@ -1641,7 +1715,7 @@ private static $encoding = 'UTF-8';
|
||||
write("addLog\0".htmlspecialchars("Checking for free space"));
|
||||
$dirfree = disk_free_space($pathinfo["dirname"]) ;
|
||||
$sourcedir = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($pathinfo["dirname"])." 2>/dev/null"));
|
||||
$repdir = str_replace('/mnt/user/', "/mnt/$sourcedir/", $pathinfo["dirname"]);
|
||||
if (!empty($sourcedir)) $repdir = str_replace('/mnt/user/', "/mnt/$sourcedir/", $pathinfo["dirname"]); else $repdir = $pathinfo["dirname"];
|
||||
$repdirfree = disk_free_space($repdir) ;
|
||||
$reflink = true ;
|
||||
$capacity *= 1 ;
|
||||
@@ -1669,6 +1743,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
$files_exist = false ;
|
||||
$files_clone = array() ;
|
||||
if ($config['disk'][0]['new'] != "") {
|
||||
foreach ($config["disk"] as $diskid => $disk) {
|
||||
$file_clone[$diskid]["source"] = $config["disk"][$diskid]["new"] ;
|
||||
$config["disk"][$diskid]["new"] = str_replace($vm,$clone,$config["disk"][$diskid]["new"]) ;
|
||||
@@ -1680,29 +1755,31 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
if ($storage == "default") $clonedir = $domain_cfg['DOMAINDIR'].$clone ; else $clonedir = str_replace('/mnt/user/', "/mnt/$storage/", $domain_cfg['DOMAINDIR']).$clone;
|
||||
if (!is_dir($clonedir)) {
|
||||
mkdir($clonedir,0777,true);
|
||||
#my_mkdir($clonedir,0777,true);
|
||||
chown($clonedir, 'nobody');
|
||||
chgrp($clonedir, 'users');
|
||||
#mkdir($clonedir,0777,true);
|
||||
my_mkdir($clonedir,0777,true);
|
||||
#chown($clonedir, 'nobody');
|
||||
#chgrp($clonedir, 'users');
|
||||
}
|
||||
write("addLog\0".htmlspecialchars("Checking for image files"));
|
||||
if ($file_exists && $overwrite != "yes") { write("addLog\0".htmlspecialchars(_("New image file names exist and Overwrite is not allowed"))); return( false) ; }
|
||||
|
||||
#Create duplicate files.
|
||||
foreach($file_clone as $diskid => $disk) {
|
||||
$target = $disk['target'] ;
|
||||
$source = $disk['source'] ;
|
||||
$reptgt = $target = $disk['target'] ;
|
||||
$repsrc = $source = $disk['source'] ;
|
||||
if ($target == $source) { write("addLog\0".htmlspecialchars(_("New image file is same as old"))); return( false) ; }
|
||||
if ($storage == "default") $sourcerealdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($source)." 2>/dev/null")); else $sourcerealdisk = $storage;
|
||||
if (!empty($sourcerealdisk)) {
|
||||
$reptgt = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $target);
|
||||
$repsrc = str_replace('/mnt/user/', "/mnt/$sourcerealdisk/", $source);
|
||||
|
||||
}
|
||||
$cmdstr = "cp --reflink=always '$repsrc' '$reptgt'" ;
|
||||
if ($reflink == true) { $refcmd = $cmdstr ; } else {$refcmd = false; }
|
||||
$cmdstr = "rsync -ahPIXS --out-format=%f --info=flist0,misc0,stats0,name1,progress2 '$repsrc' '$reptgt'" ;
|
||||
$error = execCommand_nchan_clone($cmdstr,$target,$refcmd) ;
|
||||
if (!$error) { write("addLog\0".htmlspecialchars("Image copied failed.")); return( false) ; }
|
||||
}
|
||||
}
|
||||
|
||||
write("<p class='logLine'></p>","addLog\0<fieldset class='docker'><legend>"._("Completing Clone").": </legend><p class='logLine'></p><span id='wait-$waitID'></span></fieldset>");
|
||||
write("addLog\0".htmlspecialchars("Creating new XML $clone"));
|
||||
@@ -1868,7 +1945,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
function vm_snapshot($vm,$snapshotname, $snapshotdescinput, $free = "yes", $method = "QEMU", $memorysnap = "yes") {
|
||||
global $lv ;
|
||||
|
||||
$logging = true;
|
||||
#Get State
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
@@ -1876,6 +1953,8 @@ private static $encoding = 'UTF-8';
|
||||
$storage = $lv->_get_single_xpath_result($vm, '//domain/metadata/*[local-name()=\'vmtemplate\']/@storage');
|
||||
if (empty($storage)) $storage = "default" ;
|
||||
|
||||
if ($method == "ZFS" && $state == "running" && $memorysnap == "no") {$arrResponse = ['error' => _("ZFS snapshot without memory dump not supported at this time.") ] ;return $arrResponse ;}
|
||||
|
||||
#Get disks for --diskspec
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
$diskspec = "" ;
|
||||
@@ -1929,19 +2008,22 @@ private static $encoding = 'UTF-8';
|
||||
if ($free == "yes" && $dirfree < $capacity) { $arrResponse = ['error' => _("Insufficent Storage for Snapshot")]; return $arrResponse ;}
|
||||
|
||||
#Copy nvram
|
||||
if ($logging) qemu_log($vm,"Copy NVRAM");
|
||||
if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_create_snapshot($lv->domain_get_uuid($vm),$name) ;
|
||||
|
||||
$xmlfile = $dirpath."/".$name.".running" ;
|
||||
if ($logging) qemu_log($vm,"Save XML if state is running current $state");
|
||||
if ($state == "running") exec("virsh dumpxml '$vm' > ".escapeshellarg($xmlfile),$outxml,$rtnxml) ;
|
||||
|
||||
$output= [] ;
|
||||
|
||||
if ($logging) qemu_log($vm,"snap method $method");
|
||||
switch ($method) {
|
||||
case "ZFS":
|
||||
# Create ZFS Snapshot
|
||||
if ($state == "running") exec($cmdstr." 2>&1",$output,$return);
|
||||
$zfsdataset = trim(shell_exec("zfs list -H -o name -r $dirpath")) ;
|
||||
$fssnapcmd = " zfs snapshot $zfsdataset@$name";
|
||||
if ($logging) qemu_log($vm,"zfs snap: $fssnapcmd");
|
||||
shell_exec($fssnapcmd);
|
||||
# if running resume.
|
||||
if ($state == "running") $lv->domain_resume($vm);
|
||||
@@ -1951,13 +2033,16 @@ private static $encoding = 'UTF-8';
|
||||
break;
|
||||
default:
|
||||
# No Action
|
||||
if ($logging) qemu_log($vm,"Cmd: $cmdstr");
|
||||
exec($cmdstr." 2>&1",$output,$return);
|
||||
}
|
||||
|
||||
if (strpos(" ".$output[0],"error") ) {
|
||||
$arrResponse = ['error' => substr($output[0],6) ] ;
|
||||
if ($logging) qemu_log($vm,"Error");
|
||||
} else {
|
||||
$arrResponse = ['success' => true] ;
|
||||
if ($logging) qemu_log($vm,"Success write snap db");
|
||||
$ret = write_snapshots_database("$vm","$name",$state,$snapshotdescinput,$method) ;
|
||||
#remove meta data
|
||||
if ($ret != "noxml") $ret = $lv->domain_snapshot_delete($vm, "$name" ,2) ;
|
||||
@@ -1968,6 +2053,7 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
function vm_revert($vm, $snap="--current",$action="no",$actionmeta = 'yes',$dryrun = false) {
|
||||
global $lv ;
|
||||
$logging = true;
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
#$disks =$lv->get_disk_stats($vm) ;
|
||||
$snapstate = $snapslist[$snap]['state'];
|
||||
@@ -2015,6 +2101,7 @@ private static $encoding = 'UTF-8';
|
||||
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
|
||||
if (!$dryrun) $new = $lv->domain_define($xml);
|
||||
if ($new) $arrResponse = ['success' => true] ; else $arrResponse = ['error' => $lv->get_last_error()] ;
|
||||
if ($logging) qemu_log($vm,"Create XML $new");
|
||||
}
|
||||
|
||||
# remove snapshot meta data, images, memory, runxml and NVRAM. for all snapshots.
|
||||
@@ -2024,6 +2111,7 @@ private static $encoding = 'UTF-8';
|
||||
if ($diskname == "hda" || $diskname == "hdb") continue ;
|
||||
$path = $disk["source"]["@attributes"]["file"] ;
|
||||
if (is_file($path) && $action == "yes") if (!$dryrun) unlink("$path") ;else echo "unlink $path\n";
|
||||
if ($logging) qemu_log($vm,"unlink $path");
|
||||
$item = array_search($path,$snapslist[$snap]['backing']["r".$diskname]) ;
|
||||
$item++ ;
|
||||
while($item > 0)
|
||||
@@ -2031,6 +2119,7 @@ private static $encoding = 'UTF-8';
|
||||
if (!isset($snapslist[$snap]['backing']["r".$diskname][$item])) break ;
|
||||
$newpath = $snapslist[$snap]['backing']["r".$diskname][$item] ;
|
||||
if (is_file($newpath) && $action == "yes") if (!$dryrun) unlink("$newpath"); else echo "unlink $newpath\n";
|
||||
if ($logging) qemu_log($vm,"unlink $newpath");
|
||||
$item++ ;
|
||||
}
|
||||
}
|
||||
@@ -2043,6 +2132,7 @@ private static $encoding = 'UTF-8';
|
||||
$name = $s['name'] ;
|
||||
$oldmethod = $s['method'];
|
||||
if ($dryrun) echo "$name $oldmethod\n";
|
||||
if ($logging) qemu_log($vm,"$name $oldmethod");
|
||||
if (!isset($primarypath)) $primarypath = $s['primarypath'];
|
||||
$xmlfile = $primarypath."/$name.running" ;
|
||||
$memoryfile = $primarypath."/memory$name.mem" ;
|
||||
@@ -2054,6 +2144,7 @@ private static $encoding = 'UTF-8';
|
||||
if ($olddiskname == "hda" || $olddiskname == "hdb") continue ;
|
||||
$oldpath = $olddisk["source"]["@attributes"]["file"] ;
|
||||
if (is_file($oldpath) && $action == "yes") if (!$dryrun) unlink("$oldpath"); else echo "$oldpath\n";
|
||||
if ($logging) qemu_log($vm,"unlink $oldpath");
|
||||
}
|
||||
}
|
||||
if ($oldmethod == "ZFS") {
|
||||
@@ -2061,6 +2152,7 @@ private static $encoding = 'UTF-8';
|
||||
$zfsdataset = trim(shell_exec("zfs list -H -o name -r ".transpose_user_path($primarypath))) ;
|
||||
$fssnapcmd = " zfs destroy $zfsdataset@$name";
|
||||
if (!$dryrun) shell_exec($fssnapcmd); else echo "old $fssnapcmd\n";
|
||||
if ($logging) qemu_log($vm,"old $fssnapcmd");
|
||||
}
|
||||
|
||||
#Delete Metadata
|
||||
@@ -2068,10 +2160,12 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
if (is_file($memoryfile) && $action == "yes") if (!$dryrun) unlink($memoryfile); else echo ("$memoryfile \n") ;
|
||||
if (is_file($xmlfile) && $action == "yes") if (!$dryrun) unlink($xmlfile); else echo ("$xmlfile \n") ;
|
||||
if ($logging) qemu_log($vm,"mem $memoryfile xml $xmlfile");
|
||||
# Delete NVRAM
|
||||
if (!empty($lv->domain_get_ovmf($res)) && $action == "yes") if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$name) ; else echo "Remove old NV\n";
|
||||
if ($actionmeta == "yes") {
|
||||
if (!$dryrun) $ret = delete_snapshots_database("$vm","$name"); else echo "Old Delete snapshot meta\n";
|
||||
if ($logging) qemu_log($vm,"Old Delete snapshot meta");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2084,8 +2178,10 @@ private static $encoding = 'UTF-8';
|
||||
}
|
||||
$fssnapcmd = " zfs rollback $zfsdataset@$snap";
|
||||
if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
|
||||
if ($logging) qemu_log($vm,"$fssnapcmd");
|
||||
$fssnapcmd = " zfs destroy $zfsdataset@$snap";
|
||||
if (!$dryrun) shell_exec($fssnapcmd); else echo "$fssnapcmd\n";
|
||||
if ($logging) qemu_log($vm,"$fssnapcmd");
|
||||
}
|
||||
|
||||
if ($snapslist[$snap]['state'] == "running" || $snapslist[$snap]['state'] == "disk-snapshot") {
|
||||
@@ -2097,13 +2193,17 @@ private static $encoding = 'UTF-8';
|
||||
$xml = custom::createXML('domain',$xmlobj)->saveXML();
|
||||
if (!strpos($xml,'<vmtemplate xmlns="unraid"')) $xml=str_replace('<vmtemplate','<vmtemplate xmlns="unraid"',$xml);
|
||||
if (!$dryrun) $rtn = $lv->domain_define($xml) ;
|
||||
if ($logging) qemu_log($vm,"Define XML");
|
||||
|
||||
|
||||
# Restore Memory.
|
||||
if ($snapslist[$snap]['state'] == "running") {
|
||||
if (!$dryrun) $cmdrtn = exec("virsh restore --running ".escapeshellarg($memoryfile)) ;
|
||||
if ($logging) qemu_log($vm,"Restore");
|
||||
if (!$dryrun && !$cmdrtn) unlink($xmlfile);
|
||||
if ($logging) qemu_log($vm,"Unlink XML");
|
||||
if (!$dryrun && !$cmdrtn) unlink($memoryfile);
|
||||
if ($logging) qemu_log($vm,"Unlink memoryfile");
|
||||
}
|
||||
if ($snapslist[$snap]['state'] == "disk-snapshot") if (!$dryrun) unlink($xmlfile);
|
||||
}
|
||||
@@ -2116,12 +2216,14 @@ private static $encoding = 'UTF-8';
|
||||
|
||||
if ($actionmeta == "yes") {
|
||||
if (!$dryrun) $ret = delete_snapshots_database("$vm","$snap"); else echo "Delete snapshot meta\n";
|
||||
if ($logging) qemu_log($vm,"Delete Snapshot DB entry");
|
||||
}
|
||||
|
||||
if (!$dryrun) if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_revert_snapshot($lv->domain_get_uuid($vm),$snap) ; else echo "Delete NV $vm,$snap\n";
|
||||
|
||||
$arrResponse = ['success' => true] ;
|
||||
if ($dryrun) var_dump($arrResponse);
|
||||
if ($logging) qemu_log($vm, "Success");
|
||||
return($arrResponse) ;
|
||||
}
|
||||
|
||||
@@ -2192,7 +2294,7 @@ private static $encoding = 'UTF-8';
|
||||
foreach($disks as $disk) {
|
||||
$file = $disk["file"] ;
|
||||
$output = array() ;
|
||||
exec("qemu-img info --backing-chain -U $file | grep image:",$output) ;
|
||||
exec("qemu-img info --backing-chain -U \"$file\" | grep image:",$output) ;
|
||||
foreach($output as $key => $line) {
|
||||
$line=str_replace("image: ","",$line) ;
|
||||
$output[$key] = $line ;
|
||||
@@ -2280,7 +2382,14 @@ OPTIONS
|
||||
|
||||
blockcommit Debian --path /mnt/user/domains/Debian/vdisk1.S20230513120410qcow2 --verbose --pivot --delete
|
||||
*/
|
||||
# Error if VM Not running.
|
||||
#Get VM State. If shutdown start as paused.
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
if ($state == "shutoff") {
|
||||
$lv->domain_start($res);
|
||||
$lv->domain_suspend($res);
|
||||
}
|
||||
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
@@ -2326,6 +2435,9 @@ OPTIONS
|
||||
}
|
||||
# Delete NVRAM
|
||||
#if (!empty($lv->domain_get_ovmf($res))) $nvram = $lv->nvram_delete_snapshot($lv->domain_get_uuid($vm),$snap) ;
|
||||
if ($state == "shutoff") {
|
||||
$lv->domain_destroy($res);
|
||||
}
|
||||
|
||||
refresh_snapshots_database($vm) ;
|
||||
$ret = $ret = delete_snapshots_database("$vm","$snap") ; ;
|
||||
@@ -2363,6 +2475,15 @@ OPTIONS
|
||||
|
||||
|
||||
*/
|
||||
#Get VM State. If shutdown start as paused.
|
||||
$res = $lv->get_domain_by_name($vm);
|
||||
$dom = $lv->domain_get_info($res);
|
||||
$state = $lv->domain_state_translate($dom['state']);
|
||||
if ($state == "shutoff") {
|
||||
$lv->domain_start($res);
|
||||
$lv->domain_suspend($res);
|
||||
}
|
||||
|
||||
$snapslist= getvmsnapshots($vm) ;
|
||||
$disks =$lv->get_disk_stats($vm) ;
|
||||
foreach($disks as $disk) {
|
||||
@@ -2381,41 +2502,45 @@ OPTIONS
|
||||
$snaps_json=json_encode($snaps,JSON_PRETTY_PRINT) ;
|
||||
$pathinfo = pathinfo($file) ;
|
||||
$dirpath = $pathinfo["dirname"] ;
|
||||
file_put_contents("$dirpath/image.tracker",$snaps_json) ;
|
||||
#file_put_contents("$dirpath/image.tracker",$snaps_json) ;
|
||||
|
||||
foreach($disks as $disk) {
|
||||
$path = $disk['file'] ;
|
||||
$cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --pivot --delete" ;
|
||||
$cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --wait " ;
|
||||
# Process disks and update path.
|
||||
$snapdisks=($snapslist[$snap]['disks']) ;
|
||||
if ($base != "--base" && $base != "") {
|
||||
#get file name from snapshot.
|
||||
$snapdisks=($snapslist[$base]['disks']) ;
|
||||
$basepath = "" ;
|
||||
foreach ($snapdisks as $snapdisk) {
|
||||
$diskname = $snapdisk["@attributes"]["name"] ;
|
||||
if ($diskname != $disk['device']) continue ;
|
||||
$basepath = $snapdisk["source"]["@attributes"]["file"] ;
|
||||
}
|
||||
if ($basepath != "") $cmdstr .= " --base '$basepath' ";
|
||||
$path = $disk['file'] ;
|
||||
$cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --pivot --delete" ;
|
||||
$cmdstr = "virsh blockpull '$vm' --path '$path' --verbose --wait " ;
|
||||
# Process disks and update path.
|
||||
$snapdisks=($snapslist[$snap]['disks']) ;
|
||||
if ($base != "--base" && $base != "") {
|
||||
#get file name from snapshot.
|
||||
$snapdisks=($snapslist[$base]['disks']) ;
|
||||
$basepath = "" ;
|
||||
foreach ($snapdisks as $snapdisk) {
|
||||
$diskname = $snapdisk["@attributes"]["name"] ;
|
||||
if ($diskname != $disk['device']) continue ;
|
||||
$basepath = $snapdisk["source"]["@attributes"]["file"] ;
|
||||
}
|
||||
if ($basepath != "") $cmdstr .= " --base '$basepath' ";
|
||||
}
|
||||
|
||||
if ($action) $cmdstr .= " $action ";
|
||||
|
||||
|
||||
$error = execCommand_nchan($cmdstr,$path) ;
|
||||
|
||||
if (!$error) {
|
||||
$arrResponse = ['error' => "Process Failed" ] ;
|
||||
return($arrResponse) ;
|
||||
} else {
|
||||
# Remove nvram snapshot
|
||||
$arrResponse = ['success' => true] ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ($state == "shutoff") {
|
||||
$lv->domain_destroy($res);
|
||||
}
|
||||
|
||||
if ($action) $cmdstr .= " $action ";
|
||||
|
||||
|
||||
$error = execCommand_nchan($cmdstr,$path) ;
|
||||
|
||||
if (!$error) {
|
||||
$arrResponse = ['error' => "Process Failed" ] ;
|
||||
return($arrResponse) ;
|
||||
} else {
|
||||
# Remove nvram snapshot
|
||||
$arrResponse = ['success' => true] ;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
refresh_snapshots_database($vm) ;
|
||||
$ret = $ret = delete_snapshots_database("$vm","$snap") ;
|
||||
if($ret)
|
||||
@@ -2533,6 +2658,7 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co
|
||||
$allstats=$lv->domain_get_all_domain_stats();
|
||||
|
||||
foreach ($allstats as $vm => $data) {
|
||||
$rd=$wr=$tx=$rx=null;
|
||||
$state = $data["state.state"];
|
||||
# CPU Metrics
|
||||
$cpuTime = 0;
|
||||
@@ -2552,8 +2678,8 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co
|
||||
# Memory Metrics
|
||||
if ($state == 1 && $collectmemstats) {
|
||||
$currentmem = $data["balloon.current"];
|
||||
$unusedmem = $data["balloon.unused"];
|
||||
$meminuse = $currentmem - $unusedmem;
|
||||
$maximummem = $data["balloon.maximum"];
|
||||
$meminuse = min($data["balloon.rss"],$data["balloon.current"]);
|
||||
} else $currentmem = $meminuse = 0;
|
||||
|
||||
# Disk
|
||||
@@ -2588,7 +2714,8 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co
|
||||
"cpuguest" => $cpuGuestPercent,
|
||||
"timestamp" => $timestamp,
|
||||
"mem" => $meminuse,
|
||||
"maxmem" => $currentmem,
|
||||
"curmem" => $currentmem,
|
||||
"maxmem" => $maximummem,
|
||||
"rxrate" => $rxrate,
|
||||
"rxp" => $rx,
|
||||
"txrate" => $txrate,
|
||||
@@ -2602,4 +2729,137 @@ function get_vm_usage_stats($collectcpustats = true,$collectdiskstats = true,$co
|
||||
}
|
||||
}
|
||||
|
||||
function build_xml_templates($strXML) {
|
||||
global $arrValidPCIDevices,$arrValidUSBDevices;
|
||||
|
||||
$xmldevsections = $xmlsections = [];
|
||||
$xml = new SimpleXMLElement($strXML) ;
|
||||
$x = $xml->children();
|
||||
foreach($x as $key=>$y) {
|
||||
$xmlsections[] = $key;
|
||||
}
|
||||
|
||||
$ns= $xml->getNamespaces(true);
|
||||
foreach($ns as $namekey=>$namespace) foreach($xml->children($namespace) as $key=>$y) $xmlsections[] = "$namekey:$key";
|
||||
|
||||
$v = $xml->devices->children();
|
||||
$keys = [];
|
||||
foreach($v as $key=>$y) $keys[] = $key;
|
||||
foreach(array_count_values($keys) as $key=>$number) $xmldevsections[]= $key;
|
||||
|
||||
$endpos = 0;
|
||||
foreach($xmlsections as $xmlsection) {
|
||||
$strpos = strpos($strXML,"<$xmlsection",$endpos);
|
||||
if ($strpos === false) continue ;
|
||||
$endcheck = "</$xmlsection>";
|
||||
$endpos = strpos($strXML,$endcheck,$strpos);
|
||||
$xml2[$xmlsection] = trim(substr($strXML,$strpos,$endpos-$strpos+strlen($endcheck)),'/0') ;
|
||||
}
|
||||
|
||||
$xml = $xml2['devices'];
|
||||
$endpos = 0;
|
||||
foreach($xmldevsections as $xmlsection ) {
|
||||
$strpos = $count = 0;
|
||||
while (true) {
|
||||
|
||||
$strpos = strpos($xml,"<$xmlsection",$endpos);
|
||||
if ($strpos === false) continue 2;
|
||||
$endcheck = "</$xmlsection>";
|
||||
$endpos = strpos($xml,$endcheck,$strpos);
|
||||
#echo $xmlsection." ".$strpos." ".$endpos."\n";
|
||||
if ($endpos === false) {
|
||||
$endcheck = "/>";
|
||||
$endpos = strpos($xml,$endcheck,$strpos);
|
||||
}
|
||||
# echo substr($xml,$strpos,$endpos-$strpos+strlen($endcheck)) ;
|
||||
if ($xmlsection == "disk") {
|
||||
$disk = substr($xml,$strpos,$endpos-$strpos+strlen($endcheck));
|
||||
$xmldiskdoc = new SimpleXMLElement($disk);
|
||||
$devxml[$xmlsection][$xmldiskdoc->target->attributes()->dev->__toString()] = $disk;
|
||||
|
||||
} else {
|
||||
$devxml[$xmlsection][$count] = substr($xml,$strpos,$endpos-$strpos+strlen($endcheck)) ;
|
||||
}
|
||||
$count++;
|
||||
}
|
||||
}
|
||||
$xml2["devices"] = $devxml;
|
||||
$xml2["devices"]["allusb"] = "";
|
||||
foreach ($xml2['devices']["hostdev"] as $xmlhostdev) {
|
||||
$xmlhostdevdoc = new SimpleXMLElement($xmlhostdev);
|
||||
switch ($xmlhostdevdoc->attributes()->type) {
|
||||
case 'pci' :
|
||||
$pciaddr = $xmlhostdevdoc->source->address->attributes()->bus.":".$xmlhostdevdoc->source->address->attributes()->slot.".".$xmlhostdevdoc->source->address->attributes()->function;
|
||||
$pciaddr = str_replace("0x","",$pciaddr);
|
||||
$xml2["devices"][$arrValidPCIDevices[$pciaddr]["class"]][$pciaddr] = $xmlhostdev;
|
||||
break;
|
||||
case "usb":
|
||||
$usbaddr = $xmlhostdevdoc->source->vendor->attributes()->id.":".$xmlhostdevdoc->source->product->attributes()->id;
|
||||
$usbaddr = str_replace("0x","",$usbaddr);
|
||||
$xml2["devices"]["usb"][$usbaddr] = $xmlhostdev;
|
||||
$xml2["devices"]["allusb"] .= $xmlhostdev;
|
||||
break;
|
||||
}
|
||||
}
|
||||
foreach($xml2["devices"]["input"] as $input) $xml2["devices"]["allinput"] .= "$input\n";
|
||||
|
||||
return $xml2;
|
||||
}
|
||||
|
||||
function qemu_log($vm,$m) {
|
||||
$m = print_r($m,true);
|
||||
$m = date("YmdHis")." ".$m;
|
||||
$m = str_replace("\n", " ", $m);
|
||||
$m = str_replace('"', "'", $m);
|
||||
file_put_contents("/var/log/libvirt/qemu/$vm.log",$m."\n",FILE_APPEND);
|
||||
}
|
||||
|
||||
function get_vm_ip($dom) {
|
||||
global $lv;
|
||||
$myIP=null;
|
||||
$gastate = getgastate($dom);
|
||||
if ($gastate == "connected") {
|
||||
$ip = $lv->domain_interface_addresses($dom, 1);
|
||||
$gastate = getgastate($dom);
|
||||
if ($gastate == "connected") {
|
||||
$ip = $lv->domain_interface_addresses($dom, 1);
|
||||
if ($ip != false) {
|
||||
foreach ($ip as $arrIP) {
|
||||
$ipname = $arrIP["name"];
|
||||
if (preg_match('/^(lo|Loopback)/',$ipname)) continue; // omit loopback interface
|
||||
$iplist = $arrIP["addrs"];
|
||||
foreach ($iplist as $arraddr) {
|
||||
$myIP= $arraddr["addr"];
|
||||
if (preg_match('/^f[c-f]/',$myIP)) continue; // omit ipv6 private addresses
|
||||
break 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return $myIP;
|
||||
}
|
||||
|
||||
function check_zfs_name($zfsname, $storage="default") {
|
||||
global $lv,$domain_cfg;
|
||||
if ($storage == "default") $storage = $domain_cfg['DOMAINDIR']; else $storage = "/mnt/$storage/";
|
||||
$storage=transpose_user_path($storage);
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' $storage"));
|
||||
#Check if ZFS.
|
||||
$allowed_chars = "/^[A-Za-z0-9][A-Za-z0-9\-_.: ]*$/";
|
||||
if ($fstype == "zfs" && !preg_match($allowed_chars, $zfsname)) {
|
||||
return false;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function get_storage_fstype($storage="default") {
|
||||
global $domain_cfg;
|
||||
if ($storage == "default") $storage = $domain_cfg['DOMAINDIR']; else $storage = "/mnt/$storage/";
|
||||
$storage=transpose_user_path($storage);
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' $storage"));
|
||||
return $fstype;
|
||||
}
|
||||
|
||||
?>
|
||||
|
@@ -2,6 +2,10 @@ function displayconsole(url) {
|
||||
window.open(url, '_blank', 'scrollbars=yes,resizable=yes');
|
||||
}
|
||||
|
||||
function displayWebUI(url) {
|
||||
window.open(url, '_blank').focus();
|
||||
}
|
||||
|
||||
function downloadFile(source) {
|
||||
var a = document.createElement('a');
|
||||
a.setAttribute('href',source);
|
||||
@@ -62,10 +66,31 @@ function ajaxVMDispatchconsoleRV(params, spin){
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, fstype="QEMU",console="web",usage=false){
|
||||
function ajaxVMDispatchWebUI(params, spin){
|
||||
if (spin) $('#vm-'+params['uuid']).parent().find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", params, function(data) {
|
||||
if (data.error) {
|
||||
swal({
|
||||
title:_("Execution error"), html:true,
|
||||
text:data.error, type:"error",
|
||||
confirmButtonText:_('Ok')
|
||||
},function(){
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
});
|
||||
} else {
|
||||
if (spin) setTimeout(spin+'()',500); else location=window.location.href;
|
||||
setTimeout('displayWebUI("'+data.vmrcurl+'")',500) ;
|
||||
}
|
||||
},'json');
|
||||
}
|
||||
function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, fstype="QEMU",consolein="web;no",usage=false,webui=""){
|
||||
var opts = [];
|
||||
var path = location.pathname;
|
||||
var x = path.indexOf("?");
|
||||
var consolesplit = consolein.split(";");
|
||||
var console = consolesplit[0];
|
||||
var rdpopt = consolesplit[1];
|
||||
var rundivider = false;
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
if (vmrcurl !== "" && state == "running") {
|
||||
if (console == "web" || console == "both") {
|
||||
@@ -80,10 +105,26 @@ function addVMContext(name, uuid, template, state, vmrcurl, vmrcprotocol, log, f
|
||||
e.preventDefault();
|
||||
ajaxVMDispatchconsoleRV({action:"domain-consoleRV", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
|
||||
}});
|
||||
}
|
||||
|
||||
opts.push({divider:true});
|
||||
}
|
||||
rundivider = true;
|
||||
}
|
||||
if (state == "running") {
|
||||
if (webui != "") {
|
||||
opts.push({text:_("Open WebUI") , icon:"fa-globe", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatchWebUI({action:"domain-openWebUI", uuid:uuid, vmrcurl:webui}, "loadlist") ;
|
||||
}});
|
||||
rundivider = true;
|
||||
}
|
||||
if (rdpopt == "yes") {
|
||||
opts.push({text:_("VM Remote Desktop Protocol(RDP)"), icon:"fa-desktop", action:function(e) {
|
||||
e.preventDefault();
|
||||
ajaxVMDispatchconsoleRV({action:"domain-consoleRDP", uuid:uuid, vmrcurl:vmrcurl}, "loadlist") ;
|
||||
}});
|
||||
rundivider = true;
|
||||
}
|
||||
}
|
||||
if (rundivider) opts.push({divider:true});
|
||||
context.settings({right:false,above:false});
|
||||
if (state == "running") {
|
||||
opts.push({text:_("Stop"), icon:"fa-stop", action:function(e) {
|
||||
@@ -207,7 +248,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, method){
|
||||
if (x!=-1) path = path.substring(0,x);
|
||||
|
||||
context.settings({right:false,above:false});
|
||||
if (state == "running") {
|
||||
|
||||
|
||||
opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) {
|
||||
e.preventDefault();
|
||||
@@ -226,13 +267,7 @@ function addVMSnapContext(name, uuid, template, state, snapshotname, method){
|
||||
selectblock(uuid, name, snapshotname, "pull",true) ;
|
||||
}});
|
||||
}
|
||||
} else {
|
||||
opts.push({text:_("Revert snapshot"), icon:"fa-fast-backward", action:function(e) {
|
||||
e.preventDefault();
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
selectsnapshot(uuid, name, snapshotname, "revert",true,state,method) ;
|
||||
}});
|
||||
}
|
||||
|
||||
opts.push({text:_("Remove snapshot"), icon:"fa-trash", action:function(e) {
|
||||
e.preventDefault();
|
||||
$('#vm-'+uuid).find('i').removeClass('fa-play fa-square fa-pause').addClass('fa-refresh fa-spin');
|
||||
|
@@ -67,7 +67,6 @@ while (true) {
|
||||
get_vm_usage_stats();
|
||||
$echo = [];
|
||||
$echo = [];
|
||||
$ts = $time1 - $time0;
|
||||
$echodata = "";
|
||||
$running = 0;
|
||||
ksort($vmusagestats);
|
||||
@@ -76,11 +75,13 @@ while (true) {
|
||||
if ($vmdata['state'] == 1) {
|
||||
$running++;
|
||||
$echodata .= "<tr><td>$vm</td>" ;
|
||||
$echodata .= "<td class='advanced'><span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'></span><span></span></div></td>";
|
||||
$echodata .= "<td class='advanced'><span class='cpuh-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpuh-".$vm."' style='width:".$vmdata['cpuhost']."%;'></span><span></span></div></td><td>";
|
||||
$echodata .= my_scale($vmdata['mem'],$unit)."$unit / ".my_scale($vmdata['maxmem'],$unit)."$unit</td><td>";
|
||||
$echodata .= _("Read").": ".my_scale($vmdata['rdrate'],$unit)."$unit/s<br>"._("Write").": ".my_scale($vmdata['wrrate'],$unit)."$unit/s</td><td>";
|
||||
$echodata .= _("RX").": ".my_scale($vmdata['rxrate'],$unit)."$unit/s<br>"._("TX").": ".my_scale($vmdata['txrate'],$unit)."$unit/s</td></tr>";
|
||||
$echodata .= "<td class='advanced'><span class='cpug-".$vm."'>".$vmdata['cpuguest']."%</span><div class='usage-disk mm'><span id='cpug-".$vm."' style='width:".$vmdata['cpuguest']."%;'> </span><span></span></div></td>";
|
||||
$echodata .= "<td class='advanced'><span class='cpuh-".$vm."'>".$vmdata['cpuhost']."%</span><div class='usage-disk mm'><span id='cpuh-".$vm."' style='width:".$vmdata['cpuhost']."%;'> </span><span></span></div></td><td>";
|
||||
$echodata .= my_scale($vmdata['mem']*1024,$unit)."$unit / ".my_scale($vmdata['curmem']*1024,$unit)."$unit";
|
||||
if ($vmdata['curmem'] === $vmdata['maxmem']) $echodata .= " </td><td>";
|
||||
else $echodata .= " / " .my_scale($vmdata['maxmem']*1024,$unit)."$unit </td><td>";
|
||||
$echodata .= _("Read").": ".my_scale($vmdata['rdrate']/$timer,$unit)."$unit/s<br>"._("Write").": ".my_scale($vmdata['wrrate']/$timer,$unit)."$unit/s</td><td>";
|
||||
$echodata .= _("RX").": ".my_scale($vmdata['rxrate']/$timer,$unit)."$unit/s<br>"._("TX").": ".my_scale($vmdata['txrate']/$timer,$unit)."$unit/s</td></tr>";
|
||||
}
|
||||
$echo = $echodata ;
|
||||
}
|
||||
@@ -90,7 +91,6 @@ while (true) {
|
||||
$md5_new = md5($echo,true);
|
||||
if ($md5_new !== $md5_old) {
|
||||
$md5_old = publish('vm_usage',$echo)!==false ? $md5_new : -1;
|
||||
$time0 = $time1;
|
||||
}
|
||||
|
||||
sleep($timer);
|
||||
|
@@ -27,7 +27,9 @@
|
||||
}
|
||||
# Check if options file exists. Each option should be on a new line.
|
||||
if (is_file($file)) $options = explode("\n",file_get_contents($file)) ; else $options = ['--syslog','--inode-file-handles=mandatory','--announce-submounts'];
|
||||
$options[] = "--fd=".$argoptions['fd'];
|
||||
if (isset($argoptions['fd'])) {
|
||||
$options[] = "--fd=".$argoptions['fd'];
|
||||
}
|
||||
|
||||
if (isset($argoptions['o'])) {
|
||||
$virtiofsoptions = explode(',',$argoptions["o"]);
|
||||
|
@@ -23,14 +23,15 @@ span.advancedview_panel{display:none;line-height:16px;margin-top:1px}
|
||||
.basic{display:none}
|
||||
.advanced{/*Empty placeholder*/}
|
||||
.switch-button-label.off{color:inherit}
|
||||
.template_img_parent{position:relative}
|
||||
#template_img{cursor:pointer}
|
||||
#template_img:hover{opacity:0.5}
|
||||
#template_img:hover i{opacity:1.0}
|
||||
.template_img_chooser_inner{display:inline-block;width:80px;margin-bottom:15px;margin-right:10px;text-align:center;}
|
||||
.template_img_chooser_inner img{width:48px;height:48px}
|
||||
.template_img_chooser_inner p{text-align:center;line-height:8px;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative}
|
||||
#template_img_chooser div:hover{background-color:#eee;cursor:pointer;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative;display:grid;grid-template-columns: repeat(6, minmax(0, 1fr));}
|
||||
#template_img_chooser div:hover{color:#ff8c2f;cursor:pointer;}
|
||||
#form_content{display:none}
|
||||
#vmform .four{overflow:hidden}
|
||||
#vmform .four label{float:left;display:table-cell;width:15%;}
|
||||
|
@@ -23,14 +23,15 @@ span.advancedview_panel{display:none;line-height:16px;margin-top:1px}
|
||||
.basic{display:none}
|
||||
.advanced{/*Empty placeholder*/}
|
||||
.switch-button-label.off{color:inherit}
|
||||
.template_img_parent{position:relative}
|
||||
#template_img{cursor:pointer}
|
||||
#template_img:hover{opacity:0.5}
|
||||
#template_img:hover i{opacity:1.0}
|
||||
.template_img_chooser_inner{display:inline-block;width:80px;margin-bottom:15px;margin-right:10px;text-align:center;}
|
||||
.template_img_chooser_inner img{width:48px;height:48px}
|
||||
.template_img_chooser_inner p{text-align:center;line-height:8px;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative}
|
||||
#template_img_chooser div:hover{background-color:#eee;cursor:pointer;}
|
||||
#template_img_chooser{width:560px;height:300px;overflow-y:scroll;position:relative;display:grid;grid-template-columns: repeat(6, minmax(0, 1fr));}
|
||||
#template_img_chooser div:hover{color:#ff8c2f;cursor:pointer;}
|
||||
#form_content{display:none}
|
||||
#vmform .four{overflow:hidden}
|
||||
#vmform .four label{float:left;display:table-cell;width:15%;}
|
||||
|
@@ -13,6 +13,11 @@ textarea {
|
||||
overflow: auto;
|
||||
width: 475px;
|
||||
}
|
||||
textarea.xml {
|
||||
overflow:scroll;
|
||||
white-space: pre;
|
||||
width: 600px;
|
||||
}
|
||||
* {
|
||||
-webkit-box-sizing: inherit;
|
||||
-moz-box-sizing: inherit;
|
||||
|
@@ -24,12 +24,14 @@
|
||||
}
|
||||
|
||||
$arrValidMachineTypes = getValidMachineTypes();
|
||||
$arrValidPCIDevices = getValidPCIDevices();
|
||||
$arrValidGPUDevices = getValidGPUDevices();
|
||||
$arrValidAudioDevices = getValidAudioDevices();
|
||||
$arrValidOtherDevices = getValidOtherDevices();
|
||||
$arrValidUSBDevices = getValidUSBDevices();
|
||||
$arrValidDiskDrivers = getValidDiskDrivers();
|
||||
$arrValidDiskBuses = getValidDiskBuses();
|
||||
$arrValidDiskDiscard = getValidDiskDiscard();
|
||||
$arrValidCdromBuses = getValidCdromBuses();
|
||||
$arrValidVNCModels = getValidVNCModels();
|
||||
$arrValidProtocols = getValidVMRCProtocols();
|
||||
@@ -87,7 +89,8 @@
|
||||
'select' => $domain_cfg['VMSTORAGEMODE'],
|
||||
'bus' => 'virtio' ,
|
||||
'boot' => 1,
|
||||
'serial' => 'vdisk1'
|
||||
'serial' => 'vdisk1',
|
||||
'discard' => 'unmap'
|
||||
]
|
||||
],
|
||||
'gpu' => [
|
||||
@@ -96,7 +99,7 @@
|
||||
'protocol' => 'vnc',
|
||||
'autoport' => 'yes',
|
||||
'model' => 'qxl',
|
||||
'keymap' => 'en-us',
|
||||
'keymap' => 'none',
|
||||
'port' => -1 ,
|
||||
'wsport' => -1,
|
||||
'copypaste' => 'no'
|
||||
@@ -290,6 +293,12 @@
|
||||
$boolNew = true;
|
||||
$arrConfig = $arrConfigDefaults;
|
||||
$arrVMUSBs = getVMUSBs($strXML) ;
|
||||
$strXML = $lv->config_to_xml($arrConfig);
|
||||
$domXML = new DOMDocument();
|
||||
$domXML->preserveWhiteSpace = false;
|
||||
$domXML->formatOutput = true;
|
||||
$domXML->loadXML($strXML);
|
||||
$strXML= $domXML->saveXML();
|
||||
}
|
||||
// Add any custom metadata field defaults (e.g. os)
|
||||
if (!$arrConfig['template']['os']) {
|
||||
@@ -302,7 +311,24 @@
|
||||
} else $arrClocks = $arrDefaultClocks['other'] ;
|
||||
}
|
||||
|
||||
if (strpos($arrConfig['template']['name'],"User-") !== false) $arrConfig['template']['name'] = str_replace("User-","",$arrConfig['template']['name']);
|
||||
if (strpos($arrConfig['template']['name'],"User-") !== false) {
|
||||
$arrConfig['template']['name'] = str_replace("User-","",$arrConfig['template']['name']);
|
||||
unset($arrConfig['domain']['uuid']);
|
||||
}
|
||||
if ($usertemplate == 1) unset($arrConfig['domain']['uuid']);
|
||||
$xml2 = build_xml_templates($strXML);
|
||||
#disable rename if snapshots exist
|
||||
$snapshots = getvmsnapshots($arrConfig['domain']['name']) ;
|
||||
if ($snapshots != null && count($snapshots) && !$boolNew)
|
||||
{
|
||||
$snaprenamehidden = "";
|
||||
$namedisable = "disabled";
|
||||
$snapcount = count($snapshots);
|
||||
} else {
|
||||
$snaprenamehidden = "hidden";
|
||||
$namedisable = "";
|
||||
$snapcount = "0";
|
||||
};
|
||||
?>
|
||||
|
||||
<link rel="stylesheet" href="<?autov('/plugins/dynamix.vm.manager/scripts/codemirror/lib/codemirror.css')?>">
|
||||
@@ -322,9 +348,13 @@
|
||||
<input type="hidden" name="domain[memoryBacking]" id="domain_memorybacking" value="<?=htmlspecialchars($arrConfig['domain']['memoryBacking'])?>">
|
||||
|
||||
<table>
|
||||
<tr><td></td><td>
|
||||
<span <?=$snaprenamehidden?> id="snap-rename" class="orange-text"><i class="fa fa-warning"></i> _(Rename disabled, <?=$snapcount?> snapshot(s) exists.)_</span>
|
||||
<span hidden id="zfs-name" class="orange-text"><i class="fa fa-warning"></i> _(Name contains invalid characters or does not start with an alphanumberic for a ZFS storage location<br>Only these special characters are valid Underscore (_) Hyphen (-) Colon (:) Period (.))_</span></td></tr>
|
||||
<tr>
|
||||
<td>_(Name)_:</td>
|
||||
<td><input type="text" name="domain[name]" id="domain_name" class="textTemplate" title="_(Name of virtual machine)_" placeholder="_(e.g.)_ _(My Workstation)_" value="<?=htmlspecialchars($arrConfig['domain']['name'])?>" required /></td>
|
||||
<td><input <?=$namedisable?> type="text" name="domain[name]" id="domain_name" oninput="checkName(this.value)" class="textTemplate" title="_(Name of virtual machine)_" placeholder="_(e.g.)_ _(My Workstation)_" value="<?=htmlspecialchars($arrConfig['domain']['name'])?>" required /></td>
|
||||
<td><textarea class="xml" id="xmlname" rows=1 disabled ><?=htmlspecialchars($xml2['name'])."\n".htmlspecialchars($xml2['uuid'])."\n".htmlspecialchars($xml2['metadata'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<blockquote class="inline_help">
|
||||
@@ -335,6 +365,7 @@
|
||||
<tr class="advanced">
|
||||
<td>_(Description)_:</td>
|
||||
<td><input type="text" name="domain[desc]" title="_(description of virtual machine)_" placeholder="_(description of virtual machine)_ (_(optional)_)" value="<?=htmlspecialchars($arrConfig['domain']['desc'])?>" /></td>
|
||||
<td><textarea class="xml" id="xmldesription" rows=1 disabled ><?=htmlspecialchars($xml2['description'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="advanced">
|
||||
@@ -343,11 +374,27 @@
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr class="advanced">
|
||||
<td>_(WebUI)_:</td>
|
||||
<td><input type="url" name="template[webui]" title="_(Web UI to start)_" placeholder="_(Web UI to start from menu)_ (_(optional)_)" value="<?=htmlspecialchars($arrConfig['template']['webui'])?>" /></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="advanced">
|
||||
<blockquote class="inline_help">
|
||||
<p>Specify a URL that for menu to start. Substitution variables are
|
||||
<br>[IP] IP address, this will take the first IP on the VM. Guest Agent must be installed for this to work.
|
||||
<br>[PORT:XX] Port Number in XX.
|
||||
<br>[VMNAME] VM Name will have spaces replaced with -
|
||||
</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<?if (!$boolNew) $disablestorage = " disabled "; else $disablestorage = "";?>
|
||||
<td>_(Override Storage Location)_:</td><td>
|
||||
<select <?=$disablestorage?> name="template[storage]" class="disk_select narrow" id="storage_location" title="_(Location of virtual machine files)_">
|
||||
<select <?=$disablestorage?> name="template[storage]" onchange="get_storage_fstype(this)" class="disk_select narrow" id="storage_location" title="_(Location of virtual machine files)_">
|
||||
<?
|
||||
$default_storage=htmlspecialchars($arrConfig['template']['storage']);
|
||||
echo mk_option($default_storage, 'default', _('Default'));
|
||||
@@ -374,6 +421,7 @@
|
||||
|
||||
// Available cache pools
|
||||
foreach ($pools as $pool) {
|
||||
if (isSubpool($pool)) continue;
|
||||
$strLabel = $pool.' - '.my_scale($disks[$pool]['fsFree']*1024, $strUnit).' '.$strUnit.' '._('free');
|
||||
echo mk_option($default_storage, $pool, $strLabel);
|
||||
}
|
||||
@@ -424,6 +472,7 @@
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlcpu" rows=1 disabled ><?=htmlspecialchars($xml2['cpu'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="advanced">
|
||||
@@ -466,6 +515,7 @@
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlvcpu" rows=5 disabled ><?=htmlspecialchars($xml2['vcpu'])."\n".htmlspecialchars($xml2['cputune'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<blockquote class="inline_help">
|
||||
@@ -502,6 +552,7 @@
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlmem" rows=2 disabled ><?=htmlspecialchars($xml2['memory'])."\n".htmlspecialchars($xml2['currentMemory'])."\n".htmlspecialchars($xml2['memoryBacking'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="basic">
|
||||
@@ -529,6 +580,7 @@
|
||||
<?mk_dropdown_options($arrValidMachineTypes, $arrConfig['domain']['machine']);?>
|
||||
</select>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlos" rows=5 cols=200 disabled ><?=htmlspecialchars($xml2['os'])."\n".htmlspecialchars($xml2['features'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="advanced">
|
||||
@@ -647,6 +699,7 @@
|
||||
<td>
|
||||
<input type="text" name="media[cdrom]" autocomplete="off" spellcheck="false" data-pickcloseonfile="true" data-pickfilter="iso" data-pickmatch="^[^.].*" data-pickroot="<?=htmlspecialchars($domain_cfg['MEDIADIR'])?>" class="cdrom" value="<?=htmlspecialchars($arrConfig['media']['cdrom'])?>" placeholder="_(Click and Select cdrom image to install operating system)_">
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlvdiskhda" rows=1 disabled wrap="soft"><?=htmlspecialchars($xml2['devices']['disk']['hda'])?></textarea></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>_(OS Install CDRom Bus)_:</td>
|
||||
@@ -682,6 +735,7 @@
|
||||
<?mk_dropdown_options($arrValidCdromBuses, $arrConfig['media']['driversbus']);?>
|
||||
</select>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlvdiskhdb" rows=1 disabled wrap="soft"><?=htmlspecialchars($xml2['devices']['disk']['hdb'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<div class="domain_os windows">
|
||||
@@ -721,7 +775,6 @@
|
||||
if (strpos($domain_cfg['DOMAINDIR'], dirname(dirname($arrDisk['new']))) === false ||
|
||||
basename(dirname($arrDisk['new'])) != $arrConfig['domain']['name'] || (
|
||||
basename($arrDisk['new']) != 'vdisk'.($i+1).'.img') && basename($arrDisk['new']) != 'vdisk'.($i+1).'.qcow2') {
|
||||
if ($arrDisk['driver'] == "qcow2" && (basename($arrDisk['new']) == 'vdisk'.($i+1).'.qcow2')) $default_option = "auto"; else
|
||||
$default_option = 'manual';
|
||||
}
|
||||
if (file_exists(dirname(dirname($arrDisk['new'])).'/'.$arrConfig['domain']['name'].'/vdisk'.($i+1).'.img') || file_exists(dirname(dirname($arrDisk['new'])).'/'.$arrConfig['domain']['name'].'/vdisk'.($i+1).'.qcow2')) {
|
||||
@@ -755,6 +808,7 @@
|
||||
|
||||
// Available cache pools
|
||||
foreach ($pools as $pool) {
|
||||
if (isSubpool($pool)) continue;
|
||||
$strLabel = $pool.' - '.my_scale($disks[$pool]['fsFree']*1024, $strUnit).' '.$strUnit.' '._('free');
|
||||
echo mk_option($default_option, $pool, $strLabel);
|
||||
}
|
||||
@@ -781,6 +835,7 @@
|
||||
?>
|
||||
</select><input type="text" name="disk[<?=$i?>][new]" autocomplete="off" spellcheck="false" data-pickcloseonfile="true" data-pickfolders="true" data-pickfilter="img,qcow,qcow2" data-pickmatch="^[^.].*" data-pickroot="/mnt/" class="disk" id="disk_<?=$i?>" value="<?=htmlspecialchars($arrDisk['new'])?>" placeholder="_(Separate sub-folder and image will be created based on Name)_"><div class="disk_preview"></div>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlvdisk<?=$i?>" rows=4 disabled wrap="soft"><?=htmlspecialchars($xml2['devices']['disk'][$arrDisk['dev']])?></textarea></td>
|
||||
</tr>
|
||||
|
||||
<input type="hidden" name="disk[<?=$i?>][storage]" id="disk[<?=$i?>][storage]" value="<?=htmlspecialchars($arrConfig['template']['storage'])?>">
|
||||
@@ -809,6 +864,10 @@
|
||||
</select>
|
||||
_(Boot Order)_:
|
||||
<input type="number" size="5" maxlength="5" id="disk[<?=$i?>][boot]" class="narrow bootorder" style="width: 50px;" name="disk[<?=$i?>][boot]" title="_(Boot order)_" value="<?=$arrDisk['boot']?>" >
|
||||
_(Discard)_:
|
||||
<select name="disk[<?=$i?>][discard]" class="disk_driver narrow" title="_(Set discard option)_">
|
||||
<?mk_dropdown_options($arrValidDiskDiscard, $arrDisk['discard']);?>
|
||||
</select>
|
||||
<? if ($arrDisk['bus'] == "virtio" || $arrDisk['bus'] == "usb") $ssddisabled = "hidden "; else $ssddisabled = " ";?>
|
||||
<span id="disk[<?=$i?>][rotatetext]" <?=$ssddisabled?>>_(SSD)_:</span>
|
||||
<input type="checkbox" id="disk[<?=$i?>][rotation]" class="narrow rotation" onchange="updateSSDCheck(this)"style="width: 50px;" name="disk[<?=$i?>][rotation]" <?=$ssddisabled ?> <?=$arrDisk['rotation'] ? "checked ":"";?> title="_(Set SDD flag)_" value="<?=$arrDisk['rotation']?>" >
|
||||
@@ -859,6 +918,11 @@
|
||||
Specify the order the devices are used for booting.
|
||||
</p>
|
||||
|
||||
<p class="advanced">
|
||||
<b>vDisk Discard</b><br>
|
||||
Specify if unmap(Trim) requests are sent to underlaying filesystem.
|
||||
</p>
|
||||
|
||||
<p class="advanced">
|
||||
<b>vDisk SSD Flag</b><br>
|
||||
Specify the vdisk shows as SSD within the guest, only supported on SCSI, SATA and IDE bus types.
|
||||
@@ -909,6 +973,7 @@
|
||||
|
||||
// Available cache pools
|
||||
foreach ($pools as $pool) {
|
||||
if (isSubpool($pool)) continue;
|
||||
$strLabel = $pool.' - '.my_scale($disks[$pool]['fsFree']*1024, $strUnit).' '.$strUnit.' '._('free');
|
||||
echo mk_option($default_option, $pool, $strLabel);
|
||||
}
|
||||
@@ -962,6 +1027,10 @@
|
||||
|
||||
_(Boot Order)_:
|
||||
<input type="number" size="5" maxlength="5" id="disk[{{INDEX}}][boot]" class="narrow bootorder" style="width: 50px;" name="disk[{{INDEX}}][boot]" title="_(Boot order)_" value="" >
|
||||
_(Discard)_:
|
||||
<select name="disk[{{INDEX}}][discard]" class="disk_driver narrow" title="_(Set discard option)_">
|
||||
<?mk_dropdown_options($arrValidDiskDiscard, "unmap");?>
|
||||
</select>
|
||||
<span id="disk[{{INDEX}}][rotatetext]" hidden>_(SSD)_:</span>
|
||||
<input type="checkbox" id="disk[{{INDEX}}][rotation]" class="narrow rotation" onchange="updateSSDCheck(this)"style="width: 50px;" name="disk[{{INDEX}}[rotation]" hidden title="_(Set SSD flag)_" value='0' >
|
||||
</td>
|
||||
@@ -1000,6 +1069,7 @@
|
||||
mk_dropdown_options($arrUnraidShares, $arrUnraidIndex);?>
|
||||
</select>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlshare<?=$i?>" rows=4 wrap="soft" disabled ><?=htmlspecialchars($xml2['devices']['filesystem'][$i])?></textarea></td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced">
|
||||
@@ -1103,14 +1173,14 @@
|
||||
} else {
|
||||
echo mk_option($arrGPU['id'], '', _('None'));
|
||||
}
|
||||
|
||||
echo mk_option($arrGPU['id'], 'nogpu', _('No GPU'));
|
||||
foreach($arrValidGPUDevices as $arrDev) {
|
||||
echo mk_option($arrGPU['id'], $arrDev['id'], $arrDev['name'].' ('.$arrDev['id'].')');
|
||||
}
|
||||
?>
|
||||
</select>
|
||||
<?
|
||||
if ($arrGPU['id'] != 'virtual') $multifunction = "" ; else $multifunction = " disabled " ;
|
||||
if ($arrGPU['id'] != 'virtual' && $arrGPU['id'] != 'nogpu') $multifunction = "" ; else $multifunction = " disabled " ;
|
||||
?>
|
||||
<span id="GPUMulti<?=$i?>" name="gpu[<?=$i?>][multi]" class="<?if ($arrGPU['id'] != 'virtual') echo 'was';?>advanced gpumultiline<?=$i?>" >_(Multifunction)_:</span>
|
||||
|
||||
@@ -1121,6 +1191,13 @@
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
<?
|
||||
if ($arrGPU['id'] == 'virtual') {
|
||||
?>
|
||||
<td><textarea class="xml" id="xmlgraphics<?=$i?>" rows=5 disabled ><?=htmlspecialchars($xml2['devices']['graphics'][0])."\n".htmlspecialchars($xml2['devices']['video'][0])."\n".htmlspecialchars($xml2['devices']['audio'][0])?></textarea></td>
|
||||
<?} else {?>
|
||||
<td><textarea class="xml" id="xmlgraphics<?=$i?>" rows=5 disabled ><?=htmlspecialchars($xml2['devices']['vga'][$arrGPU['id']])?></textarea></td>
|
||||
<?}?>
|
||||
</tr>
|
||||
|
||||
<?if ($i == 0) {
|
||||
@@ -1190,7 +1267,7 @@
|
||||
</td>
|
||||
</tr>
|
||||
<?}?>
|
||||
<tr class="<?if ($arrGPU['id'] == 'virtual') echo 'was';?>advanced romfile">
|
||||
<tr class="<?if ($arrGPU['id'] == 'virtual' || $arrGPU['id'] == 'nogpu') echo 'was';?>advanced romfile">
|
||||
<td>_(Graphics ROM BIOS)_:</td>
|
||||
<td>
|
||||
<input type="text" name="gpu[<?=$i?>][rom]" autocomplete="off" spellcheck="false" data-pickcloseonfile="true" data-pickfilter="rom,bin" data-pickmatch="^[^.].*" data-pickroot="/mnt/" value="<?=htmlspecialchars($arrGPU['rom'])?>" placeholder="_(Path to ROM BIOS file)_ (_(optional)_)" title="_(Path to ROM BIOS file)_ (_(optional)_)" />
|
||||
@@ -1293,6 +1370,7 @@
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlaudio<?=$i?>" rows=5 disabled ><?=htmlspecialchars($xml2['devices']['audio'][$arrAudio['id']])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<?if ($i == 0) {?>
|
||||
@@ -1337,6 +1415,7 @@
|
||||
<td>
|
||||
<input type="text" name="nic[<?=$i?>][mac]" class="narrow" value="<?=htmlspecialchars($arrNic['mac'])?>" title="_(random mac, you can supply your own)_" /> <i class="fa fa-refresh mac_generate" title="_(re-generate random mac address)_"></i>
|
||||
</td>
|
||||
<td><textarea class="xml" id="xmlnet<?=$i?>" rows=5 disabled ><?=htmlspecialchars($xml2['devices']['interface'][$i])?></textarea></td>
|
||||
</tr>
|
||||
<tr class="advanced">
|
||||
<td>_(Network Source)_:</td>
|
||||
@@ -1454,7 +1533,7 @@
|
||||
<tr>
|
||||
<td>_(USB Devices)_:</td>
|
||||
<td>
|
||||
<div class="textarea" style="width: 850px">
|
||||
<div class="textarea" style="width: 780px">
|
||||
<?
|
||||
if (!empty($arrVMUSBs)) {
|
||||
foreach($arrVMUSBs as $i => $arrDev) {
|
||||
@@ -1462,7 +1541,8 @@
|
||||
<label for="usb<?=$i?>">    <input type="checkbox" name="usb[]" id="usb<?=$i?>" value="<?=htmlspecialchars($arrDev['id'])?>" <?if (count(array_filter($arrConfig['usb'], function($arr) use ($arrDev) { return ($arr['id'] == $arrDev['id']); }))) echo 'checked="checked"';?>
|
||||
/>         <input type="checkbox" name="usbopt[<?=htmlspecialchars($arrDev['id'])?>]" id="usbopt<?=$i?>" value="<?=htmlspecialchars($arrDev['id'])?>" <?if ($arrDev["startupPolicy"] =="optional") echo 'checked="checked"';?>/>     
|
||||
<input type="number" size="5" maxlength="5" id="usbboot<?=$i?>" class="narrow bootorder" <?=$bootdisable?> style="width: 50px;" name="usbboot[<?=htmlspecialchars($arrDev['id'])?>]" title="_(Boot order)_" value="<?=$arrDev['usbboot']?>" >
|
||||
<?=htmlspecialchars(substr($arrDev['name'],0,100))?> (<?=htmlspecialchars($arrDev['id'])?>)</label><br/>
|
||||
<?=htmlspecialchars(substr($arrDev['name'],0,90))?> (<?=htmlspecialchars($arrDev['id'])?>)</label><br/>
|
||||
|
||||
<?
|
||||
}
|
||||
} else {
|
||||
@@ -1470,7 +1550,7 @@
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
</td>
|
||||
</td><td><textarea class="xml" id="xmlusb<?=$i?>" rows=5 disabled ><?=htmlspecialchars($xml2['devices']['allusb'])?></textarea></td>
|
||||
</tr>
|
||||
</table>
|
||||
<blockquote class="inline_help">
|
||||
@@ -1482,11 +1562,10 @@
|
||||
<table>
|
||||
<tr><td></td>
|
||||
<td>_(Select)_  _(Boot Order)_</td></tr></div>
|
||||
<tr>
|
||||
<tr>
|
||||
<td>_(Other PCI Devices)_:</td>
|
||||
<td>
|
||||
<div class="textarea" style="width: 850px">
|
||||
<div class="textarea" style="width: 780px">
|
||||
<?
|
||||
$intAvailableOtherPCIDevices = 0;
|
||||
|
||||
@@ -1516,7 +1595,9 @@
|
||||
}
|
||||
?>
|
||||
</div>
|
||||
<td><textarea class="xml" id="xmlpci<?=$i?>" rows=2 disabled ><?=htmlspecialchars($xml2['devices']['other']["allotherpci"])?></textarea></td>
|
||||
</td>
|
||||
|
||||
</tr>
|
||||
</table>
|
||||
<blockquote class="inline_help">
|
||||
@@ -1558,7 +1639,7 @@
|
||||
if ($arrConfig['qemucmdline'] == "") $qemurows = 2 ; else $qemurows = 15 ;
|
||||
?>
|
||||
<td>
|
||||
<textarea id="qemucmdline" name="qemucmdline" rows=<?=$qemurows?> style="width: 850px" onchange="QEMUChgCmd(this)"><?=htmlspecialchars($arrConfig['qemucmdline'])?> </textarea></td></tr>
|
||||
<textarea id="qemucmdline" name="qemucmdline" class="xmlqemu" rows=<?=$qemurows?> style="width: 780px" onchange="QEMUChgCmd(this)"><?=htmlspecialchars($arrConfig['qemucmdline'])."\n".htmlspecialchars($arrConfig['qemuoverride'])?> </textarea></td></tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
@@ -1580,6 +1661,7 @@
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
<td></td><td></td><td><textarea class="xml" id="xmlclock" rows=5 disabled ><?=htmlspecialchars($xml2['clock'])."\n".htmlspecialchars($xml2['on_poweroff'])."\n".htmlspecialchars($xml2['on_reboot'])."\n".htmlspecialchars($xml2['on_crash'])?></textarea></td>
|
||||
</tr>
|
||||
<?$clockcount = 0 ;
|
||||
if (!empty($arrClocks)) {
|
||||
@@ -1627,6 +1709,135 @@
|
||||
<p>Windows and Hyperv Hpet:no Hypervclock: yes Pit no rtc no. </p>
|
||||
</p>
|
||||
</blockquote>
|
||||
<?
|
||||
if (!isset($arrConfig['evdev'])) $arrConfig['evdev'][0] = ['dev'=>"",'grab'=>"",'repeat'=>"",'grabToggle'=>""];
|
||||
foreach ($arrConfig['evdev'] as $i => $arrEvdev) {
|
||||
$strLabel = ($i > 0) ? appendOrdinalSuffix($i + 1) : '';
|
||||
?>
|
||||
<table data-category="evdev" data-multiple="true" data-minimum="1" data-index="<?=$i?>" data-prefix="<?=$strLabel?>">
|
||||
<tr>
|
||||
<td>_(Evdev Device)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][dev]" class="dev narrow">
|
||||
<?
|
||||
echo mk_option($arrEvdev['dev'], '', _('None'));
|
||||
foreach(getValidevDev() as $line) echo mk_option($arrEvdev['dev'], $line , $line);
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
<td></td><td></td><td><textarea class="xml" id="xmlclock" rows=5 disabled ><?=htmlspecialchars($xml2['devices']['allinput'])?></textarea></td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][grab]" class="evdev_grab" title="_(grab options)_">
|
||||
<?echo mk_option($arrEvdev['grab'], '', _('None'));
|
||||
foreach(["all"] as $line) echo mk_option($arrEvdev['grab'],$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Repeat)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][repeat]" class="evdev_repeat narrow" title="_(grab options)_">
|
||||
<?echo mk_option($arrEvdev['repeat'], '', _('None'));
|
||||
foreach(["on","off"] as $line) echo mk_option($arrEvdev['repeat'],$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab Toggle)_:</td>
|
||||
<td>
|
||||
<select name="evdev[<?=$i?>][grabToggle]" class="evdev_grabtoggle narrow" title="_(grab options)_">
|
||||
<?echo mk_option($arrEvdev['grabToggle'], '', _('None'));
|
||||
foreach(["ctrl-ctrl", "alt-alt", "shift-shift", "meta-meta", "scrolllock" , "ctrl-scrolllock"] as $line) echo mk_option($arrEvdev['grabToggle'],$line,$line);?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
|
||||
|
||||
</table>
|
||||
<?if ($i == 0) {?>
|
||||
<div class="advanced">
|
||||
<blockquote class="inline_help">
|
||||
<p>
|
||||
<b> Event Devices</b><br>
|
||||
Evdev is an input interface built into the Linux kernel. QEMU’s evdev passthrough support allows a user to redirect evdev events to a guest. These events can include mouse movements and key presses. By hitting both Ctrl keys at the same time, QEMU can toggle the input recipient. QEMU’s evdev passthrough also features almost no latency, making it perfect for gaming. The main downside to evdev passthrough is the lack of button rebinding – and in some cases, macro keys won’t even work at all.
|
||||
Optional items are normally only used for keyboards.
|
||||
</p>
|
||||
<p>
|
||||
<b>Device</b><br>
|
||||
Host device to passthrough to guest.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Grab</b><br>
|
||||
All grabs all input devices instead of just one
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Repeat</b><br>
|
||||
Repeat with value 'on'/'off' to enable/disable auto-repeat events
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>GrabToggle</b><br>
|
||||
GrabToggle with values ctrl-ctrl, alt-alt, shift-shift, meta-meta, scrolllock or ctrl-scrolllock to change the grab key combination</p>
|
||||
|
||||
<p>Additional devices can be added/removed by clicking the symbols to the left.</p>
|
||||
</blockquote>
|
||||
</div>
|
||||
<?}?>
|
||||
<?}?>
|
||||
<script type="text/html" id="tmplevdev">
|
||||
<table data-category="evdev" data-multiple="true" data-minimum="1" data-index="<?=$i?>" data-prefix="<?=$strLabel?>">
|
||||
<tr>
|
||||
<td>_(Evdev Device)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][dev]" class="dev narrow">
|
||||
<?
|
||||
echo mk_option("", '', _('None'));
|
||||
foreach(getValidevDev() as $line) echo mk_option("", $line , $line);
|
||||
?>
|
||||
</select>
|
||||
</td>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][grab]" class="evdev_grab" title="_(grab options)_">
|
||||
<?echo mk_option("" , '', _('None'));
|
||||
foreach(["all"] as $line) echo mk_option("",$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Repeat)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][repeat]" class="evdev_repeat narrow" title="_(grab options)_">
|
||||
<?echo mk_option("", '', _('None'));
|
||||
foreach(["on","off"] as $line) echo mk_option("",$line,ucfirst($line));?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
<tr class="advanced disk_file_options">
|
||||
<td>_(Grab Toggle)_:</td>
|
||||
<td>
|
||||
<select name="evdev[{{INDEX}}][grabToggle]" class="evdev_grabtoggle narrow" title="_(grab options)_">
|
||||
<?echo mk_option("", '', _('None'));
|
||||
foreach(["ctrl-ctrl", "alt-alt", "shift-shift", "meta-meta", "scrolllock" , "ctrl-scrolllock"] as $line) echo mk_option("",$line,$line);?>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</script>
|
||||
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
@@ -1649,6 +1860,19 @@
|
||||
<p>Click Create to generate the vDisks and return to the Virtual Machines page where your new VM will be created.</p>
|
||||
</blockquote>
|
||||
<?}?>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<tr>
|
||||
<td class="xml">_(Other XML)_:</td>
|
||||
<?
|
||||
if ($arrConfig['qemucmdline'] == "") $qemurows = 2 ; else $qemurows = 15 ;
|
||||
?>
|
||||
<td>
|
||||
<textarea id="xmlother" name="xmlother" disabled class="xml" rows=10 style="width: 780px"> <?=htmlspecialchars($xml2['devices']['emulator'][0])."\n".htmlspecialchars($xml2['devices']['console'][0])."\n".htmlspecialchars($xml2['devices']['serial'][0])."\n".htmlspecialchars($xml2['devices']['channel'][0])."\n"?> </textarea></td></tr>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="xmlview">
|
||||
@@ -1686,6 +1910,8 @@
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/scripts/codemirror/addon/hint/libvirt-schema.js')?>"></script>
|
||||
<script src="<?autov('/plugins/dynamix.vm.manager/scripts/codemirror/mode/xml/xml.js')?>"></script>
|
||||
<script type="text/javascript">
|
||||
var storageType = "<?=get_storage_fstype($arrConfig['template']['storage']);?>";
|
||||
var storageLoc = "<?=$arrConfig['template']['storage']?>";
|
||||
|
||||
function ShareChange(share) {
|
||||
var value = share.value;
|
||||
@@ -1797,6 +2023,36 @@ function SetBootorderfields(usbbootvalue) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/* Remove characters not allowed in share name. */
|
||||
function checkName(name) {
|
||||
/* Declare variables at the function scope */
|
||||
var isValidName
|
||||
$('#zfs-name').hide();
|
||||
isValidName = /^[A-Za-z0-9][A-Za-z0-9\-_.: ]*$/.test(name);
|
||||
if (isValidName) {
|
||||
$('#btnSubmit').prop("disabled", false);
|
||||
} else {
|
||||
if (storageType == "zfs")
|
||||
{ $('#btnSubmit').prop("disabled", true); $('#zfs-name').show(); }
|
||||
else $('#btnSubmit').prop("disabled", false);
|
||||
}
|
||||
}
|
||||
|
||||
function get_storage_fstype(item) {
|
||||
storageLoc = item.value;
|
||||
$.post("/plugins/dynamix.vm.manager/include/VMajax.php", {action:"get_storage_fstype", storage:item.value}, function( data ) {
|
||||
if (data.success) {
|
||||
if (data.fstype) {
|
||||
storageType=data.fstype;
|
||||
checkName(document.getElementById("domain_name").value);
|
||||
}}
|
||||
|
||||
if (data.error) {
|
||||
}
|
||||
}, "json");
|
||||
}
|
||||
|
||||
function USBBootChange(usbboot) {
|
||||
// Remove all boot orders if changed to Yes
|
||||
var value = usbboot.value ;
|
||||
@@ -1913,6 +2169,9 @@ $(function() {
|
||||
$('.advancedview').change(function () {
|
||||
if ($(this).is(':checked')) {
|
||||
setTimeout(function() {
|
||||
var xmlPanelHeight = window.outerHeight;
|
||||
if (xmlPanelHeight > 1024) xmlPanelHeight = xmlPanelHeight-550;
|
||||
editor.setSize(null,xmlPanelHeight);
|
||||
editor.refresh();
|
||||
}, 100);
|
||||
}
|
||||
@@ -2146,12 +2405,12 @@ $(function() {
|
||||
slideUpRows($vnc_sections);
|
||||
$vnc_sections.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
|
||||
var MultiSel = document.getElementById("GPUMultiSel0") ;
|
||||
MultiSel.disabled = false ;
|
||||
if (myvalue=="nogpu") MultiSel.disabled = true ; else MultiSel.disabled = false ;
|
||||
}
|
||||
}
|
||||
|
||||
$romfile = $(this).closest('table').find('.romfile');
|
||||
if (myvalue == 'virtual' || myvalue == '') {
|
||||
if (myvalue == 'virtual' || myvalue == '' || myvalue =="nogpu") {
|
||||
slideUpRows($romfile.not(isVMAdvancedMode() ? '.basic' : '.advanced'));
|
||||
$romfile.filter('.advanced').removeClass('advanced').addClass('wasadvanced');
|
||||
} else {
|
||||
@@ -2201,7 +2460,7 @@ $(function() {
|
||||
} while (gpu);
|
||||
form.find('select[name="gpu[0][id]"] option').each(function(){
|
||||
var gpu = $(this).val();
|
||||
if (gpu != 'virtual' && !gpus.includes(gpu)) form.append('<input type="hidden" name="pci[]" value="'+gpu+'#remove">');
|
||||
if ((gpu != 'virtual' && gpu != 'nogpu') && !gpus.includes(gpu)) form.append('<input type="hidden" name="pci[]" value="'+gpu+'#remove">');
|
||||
});
|
||||
// remove unused sound cards
|
||||
var sound = [], i = 0;
|
||||
@@ -2276,7 +2535,7 @@ $(function() {
|
||||
} while (gpu);
|
||||
form.find('select[name="gpu[0][id]"] option').each(function(){
|
||||
var gpu = $(this).val();
|
||||
if (gpu != 'virtual' && !gpus.includes(gpu)) form.append('<input type="hidden" name="pci[]" value="'+gpu+'#remove">');
|
||||
if ((gpu != 'virtual' && gpu != 'nogpu') && !gpus.includes(gpu)) form.append('<input type="hidden" name="pci[]" value="'+gpu+'#remove">');
|
||||
});
|
||||
// remove unused sound cards
|
||||
var sound = [], i = 0;
|
||||
@@ -2421,7 +2680,8 @@ $(function() {
|
||||
$('#vmform #domain_clock').val('localtime');
|
||||
$("#vmform #domain_machine option").each(function(){
|
||||
if ($(this).val().indexOf('i440fx') != -1) {
|
||||
$('#vmform #domain_machine').val($(this).val()).change();
|
||||
var usertemplate = <?=$usertemplate?>;
|
||||
if (usertemplate = 0) $('#vmform #domain_machine').val($(this).val()).change();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
@@ -2430,7 +2690,8 @@ $(function() {
|
||||
$('#vmform #clockoffset').val('utc');
|
||||
$("#vmform #domain_machine option").each(function(){
|
||||
if ($(this).val().indexOf('q35') != -1) {
|
||||
$('#vmform #domain_machine').val($(this).val()).change();
|
||||
var usertemplate = <?=$usertemplate?>;
|
||||
if (usertemplate = 0) $('#vmform #domain_machine').val($(this).val()).change();
|
||||
return false;
|
||||
}
|
||||
});
|
||||
|
2591
emhttp/plugins/dynamix.vm.manager/templates/Custom.formold.php
Normal file
2591
emhttp/plugins/dynamix.vm.manager/templates/Custom.formold.php
Normal file
File diff suppressed because it is too large
Load Diff
@@ -23,7 +23,7 @@ function installPlugin(file) {
|
||||
<div class="notice">_(Click **Install** to download and install the **Community Applications** plugin)_</div>
|
||||
|
||||
<form markdown="1" name="ca_install" method="POST" target="progressFrame">
|
||||
<input type="hidden" name="file" value="https://raw.githubusercontent.com/Squidly271/community.applications/master/plugins/community.applications.plg">
|
||||
<input type="hidden" name="file" value="https://ca.unraid.net/dl/https://raw.githubusercontent.com/Squidly271/community.applications/master/plugins/community.applications.plg">
|
||||
|
||||
|
||||
: <input type="button" value="_(Install)_" onclick="installPlugin(this.form.file.value)">
|
||||
|
@@ -1,6 +1,7 @@
|
||||
Menu="Main:1"
|
||||
Title="Array Devices"
|
||||
Tag="database"
|
||||
Cond="(_var($var,'SYS_ARRAY_SLOTS') > 0 || $var['fsState']=='Stopped')"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology
|
||||
@@ -18,53 +19,6 @@ Tag="database"
|
||||
$power = _var($display,'power') && in_array('nvme',array_column(main_filter($disks),'transport')) ? _('Power').' / ' : '';
|
||||
?>
|
||||
<script>
|
||||
String.prototype.no_tilde = function(){return this.replace('<?=$_tilde_?>','<?=$_proxy_?>');}
|
||||
String.prototype.master = function(){return this.split('<?=$_tilde_?>')[0];}
|
||||
|
||||
function toggle_state(device,name,action) {
|
||||
var button = null;
|
||||
if (name) {
|
||||
var group = name.replace(/(\d+|\*)$/,'');
|
||||
if (name.slice(-1)!='*') {
|
||||
// single device
|
||||
$('#dev-'+name.no_tilde()).removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
} else {
|
||||
if (group=='disk') {
|
||||
// array devices
|
||||
$('[id^="dev-parity"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
$('[id^="dev-disk"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
} else {
|
||||
// pool devices
|
||||
$('[id^="dev-'+group.master()+'"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
}
|
||||
}
|
||||
} else if (device!='Clear') {
|
||||
// all devices
|
||||
$('[id^="dev-"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
button = '[id^=button-]';
|
||||
}
|
||||
devices.stop();
|
||||
$.post('/webGui/include/ToggleState.php',{device:device,name:name,action:action},function(){setTimeout(function(){devices.start();},1000);if (button) $(button).prop('disabled',false);});
|
||||
}
|
||||
function display_diskio() {
|
||||
if ($.cookie('diskio')===undefined) {
|
||||
$('span.number').show(); $('span.diskio').hide();
|
||||
} else {
|
||||
$('span.diskio').show(); $('span.number').hide();
|
||||
}
|
||||
}
|
||||
function toggle_diskio(init) {
|
||||
if (!init) {
|
||||
if ($.cookie('diskio')===undefined) $.cookie('diskio','diskio',{expires:3650}); else $.removeCookie('diskio');
|
||||
}
|
||||
if ($.cookie('diskio')===undefined) {
|
||||
$('i.toggle').removeClass('fa-tachometer').addClass('fa-list');
|
||||
} else {
|
||||
$('i.toggle').removeClass('fa-list').addClass('fa-tachometer');
|
||||
}
|
||||
display_diskio();
|
||||
}
|
||||
|
||||
<?if (_var($var,'fsState')=="Started"):?>
|
||||
$('#tab1').bind({click:function() {$('i.toggle').show('slow');}});
|
||||
<?endif;?>
|
||||
|
@@ -18,21 +18,18 @@ Nchan="device_list,disk_load,parity_list"
|
||||
<?
|
||||
$keyfile = file_exists(_var($var,'luksKeyfile'));
|
||||
$missing = file_exists('/var/tmp/missing.tmp');
|
||||
$encrypt = false;
|
||||
$spot = _var($var,'mdResyncPos',0)>0;
|
||||
$poolsOnly = (_var($var,'SYS_ARRAY_SLOTS') == 0 ) ? true : false;
|
||||
|
||||
/* only one of $present, $missing, or $wrong will be true, or all will be false */
|
||||
$forced = $present = $wrong = false;
|
||||
foreach ($disks as $disk) {
|
||||
if (!isset($disk['fsType'])) continue;
|
||||
if (strpos(_var($disk,'fsType'),'luks:')!==false || (_var($disk,'fsType')=='auto' && (strpos(_var($var,'defaultFsType'),'luks:')!==false || _var($disk,'luksState',0)==2 || _var($disk,'luksState',0)==3))) {
|
||||
$encrypt = true;
|
||||
if (_var($disk,'luksState',0)==0) $forced = true;
|
||||
if (_var($disk,'luksState',0)==1) $present = true;
|
||||
if (_var($disk,'luksState',0)==2) $missing = true;
|
||||
if (_var($disk,'luksState',0)==3) $wrong = true;
|
||||
}
|
||||
if (strpos(_var($disk,'fsType'),'luks:')!==false || (_var($disk,'fsType')=='auto' && strpos(_var($var,'defaultFsType'),'luks:')!==false)) $forced = true;
|
||||
if (_var($disk,'luksState',0)==1) $present = true;
|
||||
if (_var($disk,'luksState',0)==2) $missing = true;
|
||||
if (_var($disk,'luksState',0)==3) $wrong = true;
|
||||
}
|
||||
$encrypt = $forced || $present || $missing || $wrong;
|
||||
if ($forced && ($present || $missing || $wrong)) $forced = false;
|
||||
|
||||
function check_encryption() {
|
||||
@@ -86,6 +83,52 @@ var ctrl = '<span class="status <?=$tabbed?"":"vhshift"?>"><a style="cursor:poin
|
||||
var recall = null;
|
||||
var recover = null;
|
||||
|
||||
String.prototype.no_tilde = function(){return this.replace('<?=$_tilde_?>','<?=$_proxy_?>');}
|
||||
String.prototype.master = function(){return this.split('<?=$_tilde_?>')[0];}
|
||||
|
||||
function toggle_state(device,name,action) {
|
||||
var button = null;
|
||||
if (name) {
|
||||
var group = name.replace(/(\d+|\*)$/,'');
|
||||
if (name.slice(-1)!='*') {
|
||||
// single device
|
||||
$('#dev-'+name.no_tilde()).removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
} else {
|
||||
if (group=='disk') {
|
||||
// array devices
|
||||
$('[id^="dev-parity"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
$('[id^="dev-disk"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
} else {
|
||||
// pool devices
|
||||
$('[id^="dev-'+group.master()+'"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
}
|
||||
}
|
||||
} else if (device!='Clear') {
|
||||
// all devices
|
||||
$('[id^="dev-"]').removeClass('fa-circle fa-square fa-warning fa-times').addClass('fa-refresh fa-spin');
|
||||
button = '[id^=button-]';
|
||||
}
|
||||
devices.stop();
|
||||
$.post('/webGui/include/ToggleState.php',{device:device,name:name,action:action},function(){setTimeout(function(){devices.start();},1000);if (button) $(button).prop('disabled',false);});
|
||||
}
|
||||
function display_diskio() {
|
||||
if ($.cookie('diskio')===undefined) {
|
||||
$('span.number').show(); $('span.diskio').hide();
|
||||
} else {
|
||||
$('span.diskio').show(); $('span.number').hide();
|
||||
}
|
||||
}
|
||||
function toggle_diskio(init) {
|
||||
if (!init) {
|
||||
if ($.cookie('diskio')===undefined) $.cookie('diskio','diskio',{expires:3650}); else $.removeCookie('diskio');
|
||||
}
|
||||
if ($.cookie('diskio')===undefined) {
|
||||
$('i.toggle').removeClass('fa-tachometer').addClass('fa-list');
|
||||
} else {
|
||||
$('i.toggle').removeClass('fa-list').addClass('fa-tachometer');
|
||||
}
|
||||
display_diskio();
|
||||
}
|
||||
function base64(str) {
|
||||
return window.btoa(unescape(encodeURIComponent(str)));
|
||||
}
|
||||
@@ -336,7 +379,7 @@ devices.on('message', function(msg,meta) {
|
||||
$.each(get,function(k,v) {if ($('#line'+k).length>0) $('#line'+k).html(v);});
|
||||
// button control
|
||||
if ($('#pauseButton').length>0 && $('#pauseButton').prop('disabled')==false) {
|
||||
if (!msg && $('#cancelButton').length>0 && $('#cancelButton').val()=="_(Cancel)_") {
|
||||
if ((get === "") && $('#cancelButton').val()=="_(Cancel)_") {
|
||||
$('#cancelButton').val("_(Done)_").prop('onclick',null).off('click').click(function(){refresh();});
|
||||
$('#pauseButton').prop('disabled',true);
|
||||
$('#cancelText').html('');
|
||||
@@ -417,7 +460,7 @@ window.onunload = function(){
|
||||
<? if (_var($var,'fsNumUnmountable',0)>0):?>
|
||||
<tr><td>**<?=_('Unmountable disk'.(_var($var,'fsNumUnmountable',0)==1?'':'s').' present')?>:**<br>
|
||||
<? $cache = [];
|
||||
foreach ($disks as $disk) if (substr(_var($disk,'fsStatus'),0,11)=='Unmountable' || in_array(prefix(_var($disk,'name')),$cache)) {
|
||||
foreach ($disks as $disk) if (substr(_var($disk,'fsStatus'),0,11)=='Unmountable' || in_array(pool_name(_var($disk,'name')),$cache)) {
|
||||
if (strlen(_var($disk,'id'))) echo "<span class='blue-text'>".my_disk(_var($disk,'name'))."</span> • ".my_id(_var($disk,'id'))." ("._var($disk,'device').")<br>";
|
||||
if (in_array(_var($disk,'name'),$pools)) $cache[] = $disk['name'];
|
||||
}
|
||||
@@ -441,6 +484,7 @@ window.onunload = function(){
|
||||
<? elseif ($action[0]=="check"):?>
|
||||
<tr><td></td><td><input type="submit" name="cmdCheck" value="_(Check)_"></td><td>**_(Check)_** _(will start **Read-Check** of all array disks)_.</td></tr>
|
||||
<? endif;?>
|
||||
<? if (!$poolsOnly):?>
|
||||
<tr><td></td><td><input type="button" value="_(History)_" onclick="parityHistory()"></td>
|
||||
<? [$date,$duration,$speed,$status,$error,$action,$size] = last_parity_log();
|
||||
if (_var($var,'sbSyncExit',0)!=0):?>
|
||||
@@ -472,6 +516,7 @@ window.onunload = function(){
|
||||
<br><i class="fa fa-fw fa-clock-o"></i> _(Duration)_: <?=my_check($duration,$speed)?>
|
||||
<br><i class="fa fa-fw fa-search"></i> <?=print_error(_var($var,'sbSyncErrs',0))?></td></tr>
|
||||
<? endif;
|
||||
endif; // end check for poolsOnly
|
||||
endif;
|
||||
else:
|
||||
if ($action[0]=="recon"):
|
||||
@@ -650,7 +695,11 @@ window.onunload = function(){
|
||||
<tr><td><?status_indicator()?>**_(Stopped)_**. _(No data disks)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
|
||||
<td>_(No array data disks have been assigned)_!</td></tr>
|
||||
<? break;
|
||||
endswitch;
|
||||
case "ERROR:NO_DEVICES":?>
|
||||
<tr><td><?status_indicator()?>**_(Stopped)_**. _(No devices)_.</td><td><input type="submit" name="cmdStart" value="_(Start)_" disabled></td>
|
||||
<td>_(No array devices have been assigned)_!</td></tr>
|
||||
<? break;
|
||||
endswitch;
|
||||
endif;
|
||||
endswitch;?>
|
||||
<tr><td></td><td class="line" colspan="2"></td></tr>
|
||||
|
@@ -24,165 +24,228 @@ $spinner = "<tr><td colspan='".($total+2)."'><div class='spinner'></div></td></t
|
||||
$cpuset = implode(';',$cpus);
|
||||
|
||||
function create() {
|
||||
// create the table header. Make multiple rows when CPU cores are many ;)
|
||||
global $total,$cpus;
|
||||
$loop = floor(($total-1)/32)+1;
|
||||
$text = [];
|
||||
for ($c = 0; $c < $loop; $c++) {
|
||||
$max = ($c==$loop-1 ? ($total%32?:32) : 32);
|
||||
for ($n = 0; $n < $max; $n++) {
|
||||
[$cpu1, $cpu2] = my_preg_split('/[,-]/',$cpus[$c*32+$n]);
|
||||
if (empty($text[$n])) $text[$n] = '';
|
||||
$text[$n] .= "$cpu1<br>";
|
||||
if ($cpu2) $text[$n] .= "$cpu2<br>";
|
||||
}
|
||||
}
|
||||
$label = implode('<br>',array_fill(0,$loop,'CPU:'.($cpu2 ? '<br>HT:':'')));
|
||||
echo "<th>$label</th>".implode(array_map(function($t){return "<th>$t</th>";},$text));
|
||||
// create the table header. Make multiple rows when CPU cores are many ;)
|
||||
global $total, $cpus;
|
||||
$loop = floor(($total-1)/32) + 1;
|
||||
$text = [];
|
||||
for ($c = 0; $c < $loop; $c++) {
|
||||
$max = ($c == $loop-1 ? ($total % 32 ?: 32) : 32);
|
||||
for ($n = 0; $n < $max; $n++) {
|
||||
[$cpu1, $cpu2] = my_preg_split('/[,-]/', $cpus[$c * 32 + $n]);
|
||||
if (empty($text[$n])) $text[$n] = '';
|
||||
$text[$n] .= "$cpu1<br />";
|
||||
if ($cpu2) $text[$n] .= "$cpu2<br />";
|
||||
}
|
||||
}
|
||||
$label = implode('<br />', array_fill(0, $loop, 'CPU:' . ($cpu2 ? '<br />CPU:' : '')));
|
||||
echo "<th>$label</th>" . implode(array_map(function($t) {
|
||||
return "<th>$t</th>";
|
||||
}, $text));
|
||||
}
|
||||
?>
|
||||
|
||||
<script>
|
||||
String.prototype.strip = function(){return this.replace(/ |\(|\)|\[|\]/g,'');}
|
||||
String.prototype.encode = function(){return this.replace(/\./g,'%2e');}
|
||||
|
||||
function apply(form) {
|
||||
// disable buttons
|
||||
$(form).find('input[value="_(Apply)_"]').prop('disabled',true);
|
||||
$(form).find('input[value="_(Reset)_"]').val("_(Done)_").prop('onclick',null).off('click').click(function(){done();});
|
||||
$('input[value="_(Done)_"]').prop('disabled',true);
|
||||
var wait = 0;
|
||||
var id = $(form).prop('name');
|
||||
var args = {};
|
||||
args['id'] = id;
|
||||
args['names'] = form.names.value.encode();
|
||||
// get the 'checked' cpus
|
||||
$(form).find('input[type=checkbox]').each(function(){
|
||||
if ($(this).prop('checked')) args[$(this).prop('name').encode()] = 'on';
|
||||
});
|
||||
// show the instant wait message
|
||||
$('#wait-'+id).show();
|
||||
// step 1: prepare the update and report back the changes
|
||||
$.post('/webGui/include/UpdateOne.php',args,function(reply){
|
||||
if (reply.error) {
|
||||
swal({type:'error',title:"_(Assignment error)_",text:reply.error,html:true,confirmButtonText:"_(Ok)_"},function(){
|
||||
$('#wait-'+id).hide();
|
||||
$(form).find('input[value="_(Done)_"]').val("_(Reset)_").prop('disabled',false).prop('onclick',null).off('click').click(function(){reset($('form[name="'+id+'"]'));});
|
||||
});
|
||||
} else if (reply.success) {
|
||||
var data = reply.success.split(';');
|
||||
wait = data.length;
|
||||
for (var i=0; i < data.length; i++) {
|
||||
var name = data[i];
|
||||
$('#'+id+'-'+name.strip()).show('slow');
|
||||
// step 2: apply the changes by updating the vm or container
|
||||
$.post('/webGui/include/UpdateTwo.php',{id:id,name:encodeURIComponent(name)},function(reply){
|
||||
if (reply.error) {
|
||||
// report error and reload table
|
||||
swal({type:'error',title:"_(Execution error)_",text:reply.error,html:true,confirmButtonText:"_(Ok)_"},function(){
|
||||
$('#wait-'+id).hide();
|
||||
$('input[value="_(Done)_"]').prop('disabled',false);
|
||||
reset($('form[name="'+id+'"]'));
|
||||
});
|
||||
} else {
|
||||
$('#'+id+'-'+reply.success.strip()).hide('slow');
|
||||
// cleanup when all is done
|
||||
if (!--wait) {
|
||||
setTimeout(function(){$('#wait-'+id).hide();},500);
|
||||
$('input[value="_(Done)_"]').prop('disabled',false);
|
||||
// isolated cpus, need reboot notice?
|
||||
if (id == 'is') notice();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$('#wait-'+id).hide();
|
||||
$('input[value="_(Done)_"]').prop('disabled',false);
|
||||
if (id == 'is') notice();
|
||||
}
|
||||
});
|
||||
}
|
||||
function vm() {
|
||||
// fetch the current vm assignments
|
||||
$.post('/webGui/include/CPUset.php',{id:'vm',cpus:'<?=$cpuset?>'},function(d){
|
||||
var data = d.split('\0');
|
||||
$('#table-vm').html(data[0]);
|
||||
$('#names-vm').val(data[1]);
|
||||
buttons(document.vm);
|
||||
});
|
||||
}
|
||||
function thread2containers(n) {
|
||||
const selector = $('form[name=ct]').find(`[name$=":${n}"]`);
|
||||
const checkboxes = selector.length;
|
||||
const checked = selector.filter(':checked').length;
|
||||
selector.prop('checked', (checkboxes - checked > checked ? true : false)).change();
|
||||
}
|
||||
function ct() {
|
||||
// fetch the current container assignments
|
||||
$.post('/webGui/include/CPUset.php',{id:'ct',cpus:'<?=$cpuset?>'},function(d){
|
||||
var data = d.split('\0');
|
||||
$('#table-ct').html(data[0]);
|
||||
$('#names-ct').val(data[1]);
|
||||
buttons(document.ct);
|
||||
// inject thread to containers toggles
|
||||
if($('a[onclick^="thread2containers"]').length === 0) {
|
||||
$('form[name=ct]').find('thead tr th:gt(1)').each((i, elem) => {
|
||||
elem.innerHTML = elem.innerHTML.replace(/(\d+)/g, '<a href="#" onclick="thread2containers(this.innerText);return false;" title="_(Toggle thread to containers)_">$1</a>');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
function is() {
|
||||
// fetch the current isolcpu assignments
|
||||
$.post('/webGui/include/CPUset.php',{id:'is',cpus:'<?=$cpuset?>'},function(d){
|
||||
$('#table-is').html(d);
|
||||
buttons(document.is);
|
||||
<?if ($safemode):?>
|
||||
$('#table-is').find('input[type=checkbox]').prop('disabled',true);
|
||||
<?endif;?>
|
||||
});
|
||||
}
|
||||
function notice() {
|
||||
// notice to reboot system after changes
|
||||
var message = "_(CPU Isolation: A reboot is required to apply changes)_";
|
||||
/* disable buttons */
|
||||
$(form).find('input[value="_(Apply)_"]').prop('disabled', true);
|
||||
$(form).find('input[value="_(Reset)_"]').val("_(Done)_").prop('onclick', null).off('click').click(function() {
|
||||
done();
|
||||
});
|
||||
$('input[value="_(Done)_"]').prop('disabled', true);
|
||||
var id = $(form).prop('name');
|
||||
var args = {
|
||||
'id': id,
|
||||
'names': form.names.value.encode(),
|
||||
'cpus': {}
|
||||
};
|
||||
|
||||
$.post('/webGui/include/CPUset.php',{id:'cmd'},function(d){
|
||||
if (d==1) addRebootNotice(message); else removeRebootNotice(message);
|
||||
});
|
||||
/* get the 'checked' cpus */
|
||||
$(form).find('input[type=checkbox]').each(function() {
|
||||
if ($(this).prop('checked')) {
|
||||
args['cpus'][$(this).prop('name').encode()] = 'on';
|
||||
}
|
||||
});
|
||||
|
||||
/* show the instant wait message */
|
||||
$('#wait-' + id).show();
|
||||
|
||||
/* step 1: prepare the update and report back the changes */
|
||||
$.post('/webGui/include/UpdateOne.php', {
|
||||
data: JSON.stringify(args)
|
||||
}, function(reply) {
|
||||
if (reply.error) {
|
||||
swal({
|
||||
type: 'error',
|
||||
title: "_(Assignment error)_",
|
||||
text: reply.error,
|
||||
html: true,
|
||||
confirmButtonText: "_(Ok)_"
|
||||
}, function() {
|
||||
$('#wait-' + id).hide();
|
||||
$(form).find('input[value="_(Done)_"]').val("_(Reset)_").prop('disabled', false).prop('onclick', null).off('click').click(function() {
|
||||
reset($('form[name="' + id + '"]'));
|
||||
});
|
||||
});
|
||||
} else if (reply.success) {
|
||||
var data = reply.success.split(';');
|
||||
wait = data.length;
|
||||
for (var i = 0; i < data.length; i++) {
|
||||
var name = data[i];
|
||||
$('#' + id + '-' + name.strip()).show('slow');
|
||||
|
||||
/* step 2: apply the changes by updating the vm or container */
|
||||
$.post('/webGui/include/UpdateTwo.php', {
|
||||
id: id,
|
||||
name: encodeURIComponent(name)
|
||||
}, function(reply) {
|
||||
if (reply.error) {
|
||||
/* report error and reload table */
|
||||
swal({
|
||||
type: 'error',
|
||||
title: "_(Execution error)_",
|
||||
text: reply.error,
|
||||
html: true,
|
||||
confirmButtonText: "_(Ok)_"
|
||||
}, function() {
|
||||
$('#wait-' + id).hide();
|
||||
$('input[value="_(Done)_"]').prop('disabled', false);
|
||||
reset($('form[name="' + id + '"]'));
|
||||
});
|
||||
} else {
|
||||
$('#' + id + '-' + reply.success.strip()).hide('slow');
|
||||
/* cleanup when all is done */
|
||||
if (!--wait) {
|
||||
setTimeout(function() {
|
||||
$('#wait-' + id).hide();
|
||||
}, 500);
|
||||
$('input[value="_(Done)_"]').prop('disabled', false);
|
||||
/* isolated cpus, need reboot notice? */
|
||||
if (id == 'is') notice();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
$('#wait-' + id).hide();
|
||||
$('input[value="_(Done)_"]').prop('disabled', false);
|
||||
if (id == 'is') notice();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Function to fetch current VM assignments and update the table */
|
||||
function vm() {
|
||||
$.post('/webGui/include/CPUset.php', { id: 'vm', cpus: '<?=$cpuset?>' }, function(d) {
|
||||
var data = d.split('\0');
|
||||
$('#table-vm').html(data[0]);
|
||||
$('#names-vm').val(data[1]);
|
||||
buttons(document.vm);
|
||||
});
|
||||
}
|
||||
|
||||
/* Function to toggle thread assignment for containers */
|
||||
function thread2containers(n) {
|
||||
const selector = $('form[name=ct]').find(`[name$=":${n}"]`);
|
||||
const checkboxes = selector.length;
|
||||
const checked = selector.filter(':checked').length;
|
||||
selector.prop('checked', (checkboxes - checked > checked ? true : false)).change();
|
||||
}
|
||||
|
||||
/* Function to fetch current container assignments and update the table */
|
||||
function ct() {
|
||||
$.post('/webGui/include/CPUset.php', { id: 'ct', cpus: '<?=$cpuset?>' }, function(d) {
|
||||
var data = d.split('\0');
|
||||
$('#table-ct').html(data[0]);
|
||||
$('#names-ct').val(data[1]);
|
||||
buttons(document.ct);
|
||||
|
||||
/* Inject thread to containers toggles */
|
||||
if ($('a[onclick^="thread2containers"]').length === 0) {
|
||||
$('form[name=ct]').find('thead tr th:gt(1)').each((i, elem) => {
|
||||
elem.innerHTML = elem.innerHTML.replace(/(\d+)/g, '<a href="#" onclick="thread2containers(this.innerText);return false;" title="_(Toggle thread to containers)_">$1</a>');
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/* Function to fetch current isolated CPU assignments and update the table */
|
||||
function is() {
|
||||
$.post('/webGui/include/CPUset.php', { id: 'is', cpus: '<?=$cpuset?>' }, function(d) {
|
||||
$('#table-is').html(d);
|
||||
buttons(document.is);
|
||||
<?if ($safemode):?>
|
||||
$('#table-is').find('input[type=checkbox]').prop('disabled', true);
|
||||
<?endif;?>
|
||||
});
|
||||
}
|
||||
|
||||
/* Function to display a notice to reboot the system after changes */
|
||||
function notice() {
|
||||
var message = "_(CPU Isolation: A reboot is required to apply changes)_";
|
||||
|
||||
$.post('/webGui/include/CPUset.php', { id: 'cmd' }, function(d) {
|
||||
if (d == 1) addRebootNotice(message); else removeRebootNotice(message);
|
||||
});
|
||||
}
|
||||
|
||||
/* Function to reset form changes without a complete page refresh */
|
||||
function reset(form) {
|
||||
// undo changes without a complete refresh of the page
|
||||
$(form).find('input[value="_(Apply)_"]').prop('disabled',true);
|
||||
$(form).find('input[value="_(Reset)_"]').val("_(Done)_").prop('onclick',null).off('click').click(function(){done();});
|
||||
switch ($(form).prop('name')) {
|
||||
case 'vm': $('#table-vm').html("<?=$spinner?>"); $('div.spinner').html(unraid_logo); vm(); break;
|
||||
case 'ct': $('#table-ct').html("<?=$spinner?>"); $('div.spinner').html(unraid_logo); ct(); break;
|
||||
case 'is': $('#table-is').html("<?=$spinner?>"); $('div.spinner').html(unraid_logo); is(); break;
|
||||
}
|
||||
$(form).find('input[value="_(Apply)_"]').prop('disabled', true);
|
||||
$(form).find('input[value="_(Reset)_"]').val("_(Done)_").prop('onclick', null).off('click').click(function() { done(); });
|
||||
|
||||
switch ($(form).prop('name')) {
|
||||
case 'vm':
|
||||
$('#table-vm').html("<?=$spinner?>");
|
||||
$('div.spinner').html(unraid_logo);
|
||||
vm();
|
||||
break;
|
||||
case 'ct':
|
||||
$('#table-ct').html("<?=$spinner?>");
|
||||
$('div.spinner').html(unraid_logo);
|
||||
ct();
|
||||
break;
|
||||
case 'is':
|
||||
$('#table-is').html("<?=$spinner?>");
|
||||
$('div.spinner').html(unraid_logo);
|
||||
is();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Function to handle checkbox interactions and enable/disable form buttons */
|
||||
function buttons(form) {
|
||||
$(form).find('input[type=checkbox]').each(function(){$(this).on('change',function(){
|
||||
var total = $(form).find('input[type=checkbox]').length;
|
||||
var checked = 'input[name^="'+$(this).prop('name').split(':')[0]+':'+'"]:checked';
|
||||
var cores = $(form).find(checked).length;
|
||||
// vms must have at least one core selected
|
||||
if ($(form).prop('name')=='vm') $(form).find(checked).prop('disabled',cores<2);
|
||||
// isolation may not have all cores selected
|
||||
if ($(form).prop('name')=='is' && $(this).prop('checked')) $(this).prop('checked',cores<total);
|
||||
// we need the Apply and Done buttons react on checkbox changes
|
||||
$(form).find('input[value="_(Apply)_"]').prop('disabled',false);
|
||||
$(form).find('input[value="_(Done)_"]').val("_(Reset)_").prop('onclick',null).off('click').click(function(){reset(form);});
|
||||
});});
|
||||
$(form).find('input[type=checkbox]').each(function() {
|
||||
$(this).on('change', function() {
|
||||
var total = $(form).find('input[type=checkbox]').length;
|
||||
var checked = 'input[name^="' + $(this).prop('name').split(':')[0] + ':' + '"]:checked';
|
||||
var cores = $(form).find(checked).length;
|
||||
|
||||
/* Ensure VMs have at least one core selected */
|
||||
if ($(form).prop('name') == 'vm') $(form).find(checked).prop('disabled', cores < 2);
|
||||
|
||||
/* Ensure isolation does not have all cores selected */
|
||||
if ($(form).prop('name') == 'is' && $(this).prop('checked')) $(this).prop('checked', cores < total);
|
||||
|
||||
/* Enable Apply and Done buttons when changes are made */
|
||||
$(form).find('input[value="_(Apply)_"]').prop('disabled', false);
|
||||
$(form).find('input[value="_(Done)_"]').val("_(Reset)_").prop('onclick', null).off('click').click(function() { reset(form); });
|
||||
});
|
||||
});
|
||||
}
|
||||
$(function(){
|
||||
|
||||
/* Initialize the functions on document ready */
|
||||
$(function() {
|
||||
<?if ($libvirtd):?>
|
||||
vm();
|
||||
vm();
|
||||
<?endif;?>
|
||||
<?if ($dockerd):?>
|
||||
ct();
|
||||
ct();
|
||||
<?endif;?>
|
||||
is();
|
||||
notice();
|
||||
is();
|
||||
notice();
|
||||
});
|
||||
</script>
|
||||
<?if ($libvirtd):?>
|
||||
|
@@ -189,10 +189,10 @@ _(Slots)_:
|
||||
<input type="hidden" name="poolName" value="">
|
||||
_(Name)_:
|
||||
: <select name="subpool">
|
||||
<?=mk_option("","special",_("special - Special Allocation Class"))?>
|
||||
<?=mk_option("","logs",_("logs - ZFS Intent Log"))?>
|
||||
<?=mk_option("","special",_("special - Metadata storage"))?>
|
||||
<?=mk_option("","logs",_("logs - Separate Intent Log (SLOG)"))?>
|
||||
<?=mk_option("","dedup",_("dedup - Deduplication Tables"))?>
|
||||
<?=mk_option("","cache",_("cache - Cache Devices"))?>
|
||||
<?=mk_option("","cache",_("cache - L2ARC"))?>
|
||||
<?=mk_option("","spares",_("spares - Hot Spares"))?>
|
||||
</select>
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
Menu="Dashboard"
|
||||
Nchan="wg_poller,update_1,update_2,update_3,ups_status:stop,vm_dashusage:stop"
|
||||
Nchan="wg_poller,update_1,update_2,update_3,ups_status:stop,vm_dashusage"
|
||||
---
|
||||
<?PHP
|
||||
/* Copyright 2005-2023, Lime Technology * Copyright 2012-2023, Bergware International.
|
||||
@@ -61,6 +61,7 @@ $conf = glob('/etc/wireguard/wg*.conf');
|
||||
$wireguard = is_executable('/usr/bin/wg') && count($conf);
|
||||
$started = _var($var,'fsState')=='Started';
|
||||
$sleep = isset($display['sleep']);
|
||||
$poolsOnly = (_var($var,'SYS_ARRAY_SLOTS') == 0 ) ? true : false;
|
||||
$array_size = $array_used = 0;
|
||||
$extra_size = $extra_used = 0;
|
||||
$cache_size = $cache_used = [];
|
||||
@@ -68,7 +69,7 @@ $cache_type = $cache_rate = [];
|
||||
|
||||
$parity = _var($var,'mdResync');
|
||||
$mover = file_exists('/var/run/mover.pid');
|
||||
$btrfs = exec('pgrep -cf /sbin/btrfs');
|
||||
$btrfs = exec('pgrep --ns $$ -cf /sbin/btrfs');
|
||||
$vdisk = exec("grep -Pom1 '^DOCKER_IMAGE_TYPE=\"\\K[^\"]+' /boot/config/docker.cfg 2>/dev/null")!='folder' ? _('Docker vdisk') : _('Docker folder');
|
||||
$dot = _var($display,'number','.,')[0];
|
||||
$zfs = count(array_filter(array_column($disks,'fsType'),function($fs){return str_replace('luks:','',$fs??'')=='zfs';}));
|
||||
@@ -78,7 +79,7 @@ $domain_cfg = parse_ini_file($domain_cfgfile);
|
||||
if (!isset($domain_cfg['USAGE'])) $vmusage = "N" ; else $vmusage = $domain_cfg['USAGE'];
|
||||
|
||||
// enable/disable graph elements by making hook script executable or not
|
||||
chmod("$docroot/webGui/system/VM_usage",$libvirtd ? 0755 : 0644);
|
||||
chmod("$docroot/webGui/system/VM",$libvirtd ? 0755 : 0644);
|
||||
chmod("$docroot/webGui/system/ZFS_cache",$zfs ? 0755 : 0644);
|
||||
|
||||
foreach ($disks as $disk) {
|
||||
@@ -573,6 +574,7 @@ if (!$group) {
|
||||
|
||||
<div class='tile' id='tile3'>
|
||||
<table id='db-box3' class='dashboard'>
|
||||
<?if (!$poolsOnly):?>
|
||||
<tbody title="_(Parity Information)_">
|
||||
<tr><td><i class='icon-health f32'></i><div class='section'>_(Parity)_<br>
|
||||
<span class='parity'></span><br></div>
|
||||
@@ -592,6 +594,7 @@ if (!$group) {
|
||||
<tr><td id='array_info'></td></tr>
|
||||
<tr class='header'><td><span class='w26'>_(Device)_</span><span class='w18'>_(Status)_</span><span class='w18'>_(Temp)_<?=$power?></span><span class='w18'>_(SMART)_</span><span class='w18'>_(Utilization)_</span></td></tr>
|
||||
</tbody>
|
||||
<?endif;?>
|
||||
|
||||
<?$i=0?>
|
||||
<?foreach ($pools as $pool):
|
||||
@@ -626,7 +629,6 @@ if (!$group) {
|
||||
</div>
|
||||
|
||||
<form name='boot' method='POST' action='/webGui/include/Boot.php'>
|
||||
<input type='hidden' name='csrf_token' value='<?=_var($var,'csrf_token')?>'>
|
||||
<input type='hidden' name='cmd' value=''>
|
||||
</form>
|
||||
|
||||
@@ -645,11 +647,11 @@ table.find('tbody').not('.system').each(function(){
|
||||
sort.push($(this).attr('sort'));
|
||||
checked.push($(this).is(':visible') ? 'checked' : '');
|
||||
});
|
||||
for (var n=0,x; x=index[n]; n++) {
|
||||
for (let n=0,x; x=index[n]; n++) {
|
||||
$('div#list').append("<span class='item'><input class='checker' type='checkbox' "+checked[n]+">"+x+"</span>");
|
||||
}
|
||||
function hideShow() {
|
||||
var n = 0, inactive = [];
|
||||
let n = 0, inactive = [];
|
||||
var count = {'db-box1':0, 'db-box2':0, 'db-box3':0};
|
||||
$('input.checker').each(function(){
|
||||
var tbody = $('table.dashboard').find('tbody[sort="'+sort[n]+'"]');
|
||||
@@ -772,7 +774,7 @@ jQuery.prototype.mixedView = function(s) {
|
||||
select = parseInt(select.val())+1;
|
||||
this.find('tr:gt(0)').each(function(){
|
||||
var names = ($(this).attr('class')||'').split(' ');
|
||||
for (var n=0,name; name=names[n]; n++) if (/[0-9]/.test(name.slice(-1)) && name.slice(-1)!=select) $(this).hide();
|
||||
for (let n=0,name; name=names[n]; n++) if (/[0-9]/.test(name.slice(-1)) && name.slice(-1)!=select) $(this).hide();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -1047,7 +1049,7 @@ function portSelect(name) {
|
||||
}
|
||||
function moreInfo(data,table) {
|
||||
var info = [];
|
||||
if (data[1]>0) info.push(data[1]+' '+(data[1]==1 ? "_(failed device)_" : "_(failed devices)_"));
|
||||
if (data[1]>0) info.push(data[1]+' '+(data[1]==1 ? "_(device warning)_" : "_(device warnings)_"));
|
||||
if (data[2]>0) info.push(data[2]+' '+(data[2]==1 ? "_(heat warning)_" : "_(heat warnings)_"));
|
||||
if (data[3]>0) info.push(data[3]+' '+(data[3]==1 ? "_(SMART error)_" : "_(SMART errors)_"));
|
||||
if (data[4]>0) info.push(data[4]+' '+(data[4]==1 ? "_(utilization warning)_" : "_(utilization warnings)_"));
|
||||
@@ -1135,7 +1137,7 @@ function StopArray() {
|
||||
}
|
||||
function StopArrayNow() {
|
||||
$('span.hand').prop('onclick',null).off('click').addClass('busy').css({'cursor':'default'});
|
||||
$.post('/update.htm',{cmdStop:'Stop'},function(){refresh();});
|
||||
$.post('/update.htm',{startState:'<?=htmlspecialchars(_var($var,'mdState'))?>', cmdStop:'Stop'},function(){refresh();});
|
||||
}
|
||||
function StartArray() {
|
||||
<?if ($confirm['stop']):?>
|
||||
@@ -1146,7 +1148,7 @@ function StartArray() {
|
||||
}
|
||||
function StartArrayNow() {
|
||||
$('span.hand').prop('onclick',null).off('click').addClass('busy').css({'cursor':'default'});
|
||||
$.post('/update.htm',{cmdStart:'Start'},function(){refresh();});
|
||||
$.post('/update.htm',{startState:'<?=htmlspecialchars(_var($var,'mdState'))?>', cmdStart:'Start'},function(){refresh();});
|
||||
}
|
||||
function Reboot() {
|
||||
<?if ($confirm['down']):?>
|
||||
@@ -1186,6 +1188,7 @@ function SleepNow() {
|
||||
function sortTables() {
|
||||
$('table.dashboard').each(function(){
|
||||
var table = $(this);
|
||||
sanitizeMultiCookie(table.prop('id'), ';', true);
|
||||
var index = $.cookie(table.prop('id'));
|
||||
// sorting list exists
|
||||
if (index != null) {
|
||||
@@ -1234,7 +1237,7 @@ function showContent() {
|
||||
var inactive = $.cookie('inactive_content');
|
||||
if (inactive) {
|
||||
inactive = inactive.split(';');
|
||||
for (var n=0,md5; md5=inactive[n]; n++) {
|
||||
for (let n=0,md5; md5=inactive[n]; n++) {
|
||||
var tbody = $('table.dashboard tbody[sort="'+md5+'"]');
|
||||
var id = tbody.parent().prop('id');
|
||||
count[id]--;
|
||||
@@ -1246,7 +1249,7 @@ function showContent() {
|
||||
var hidden = $.cookie('hidden_content');
|
||||
if (hidden) {
|
||||
hidden = hidden.split(';');
|
||||
for (var n=0,md5; md5=hidden[n]; n++) {
|
||||
for (let n=0,md5; md5=hidden[n]; n++) {
|
||||
var tbody = $('div.frame tbody[sort="'+md5+'"]');
|
||||
tbody.find('.openclose').removeClass('fa-chevron-up fa-chevron-down').addClass('fa-chevron-down');
|
||||
tbody.find('tr:gt(0)').hide();
|
||||
@@ -1605,21 +1608,25 @@ dashboard.on('message',function(msg,meta) {
|
||||
case 2:
|
||||
if (!update2) break;
|
||||
var get = JSON.parse(msg);
|
||||
var info = moreInfo(get.disk,"_(Array)_");
|
||||
// array devices
|
||||
$('#array_list tr.updated').remove();
|
||||
$('#array_list').append(get.disk[0]).hideMe();
|
||||
$('#array_info').parent().css({'display':info?'':'none'});
|
||||
$('#array_info').html(info);
|
||||
smartMenu('#array_list');
|
||||
if(document.getElementById('array_list') != null) {
|
||||
var info = moreInfo(get.disk,"_(Array)_");
|
||||
// array devices
|
||||
$('#array_list tr.updated').remove();
|
||||
$('#array_list').append(get.disk[0]).hideMe();
|
||||
$('#array_info').parent().css({'display':info?'':'none'});
|
||||
$('#array_info').html(info);
|
||||
smartMenu('#array_list');
|
||||
}
|
||||
// pool devices
|
||||
for (let i=0; i < get.pool.length; i++) {
|
||||
var info = moreInfo(get.pool[i],"_(Pool)_");
|
||||
$('#pool_list'+i+' tr.updated').remove();
|
||||
$('#pool_list'+i).append(get.pool[i][0]).hideMe();
|
||||
$('#pool_info'+i).parent().css({'display':info?'':'none'});
|
||||
$('#pool_info'+i).html(info);
|
||||
smartMenu('#pool_list'+i);
|
||||
if(get.pool) {
|
||||
for (let i=0; i < get.pool.length; i++) {
|
||||
var info = moreInfo(get.pool[i],"_(Pool)_");
|
||||
$('#pool_list'+i+' tr.updated').remove();
|
||||
$('#pool_list'+i).append(get.pool[i][0]).hideMe();
|
||||
$('#pool_info'+i).parent().css({'display':info?'':'none'});
|
||||
$('#pool_info'+i).html(info);
|
||||
smartMenu('#pool_list'+i);
|
||||
}
|
||||
}
|
||||
<?if ($devs):?>
|
||||
// unassigned devices
|
||||
@@ -1660,7 +1667,7 @@ dashboard.on('message',function(msg,meta) {
|
||||
case 4:
|
||||
// wireguard tunnels
|
||||
var get = JSON.parse(msg);
|
||||
var n = {};
|
||||
let n = {};
|
||||
for (var i=0,info; info=get[i]; i++) {
|
||||
var vtun = info[0];
|
||||
if (typeof n[vtun]=='undefined') n[vtun]=0; else n[vtun]++;
|
||||
|
@@ -20,13 +20,28 @@ require_once "$docroot/webGui/include/Preselect.php";
|
||||
$unassigned = array_key_exists($name,$devs);
|
||||
$disk = $disks[$name] ?? $devs[$name] ?? [];
|
||||
$dev = _var($disk,'device');
|
||||
$disk['id'] = _var($disk,'id');
|
||||
$events = explode('|',$disk['smEvents'] ?? $var['smEvents'] ?? $numbers);
|
||||
$mode = ['Disabled','Hourly','Daily','Weekly','Monthly'];
|
||||
$days = ['Sunday','Monday','Tuesday','Wednesday','Thursday','Friday','Saturday'];
|
||||
$sheets = [];
|
||||
$i = $n = 0;
|
||||
|
||||
function hasSubpools($name) {
|
||||
global $disks, $subpools;
|
||||
foreach ($subpools as $subpool) {
|
||||
$index = "$name~$subpool";
|
||||
if (isset($disks[$index])) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!isSubpool($name)) {
|
||||
$fsTypeImmutable = !(_var($var,'fsState')=='Stopped' && !hasSubpools($name) && (empty(_var($disk,'uuid')) || _var($disk,'slots',1)==1));
|
||||
$fsProfileImmutable = $fsTypeImmutable;
|
||||
} else {
|
||||
$fsTypeImmutable = true;
|
||||
$fsProfileImmutable = !(_var($var,'fsState')=='Stopped' && empty(_var($disk,'fsGroups','1')));
|
||||
}
|
||||
|
||||
foreach ($disks as $sheet) {
|
||||
if (_var($sheet,'type')=="Flash" || _var($sheet,'color')=="grey-off" || empty($sheet['name'])) continue;
|
||||
$sheets[] = $sheet['name'];
|
||||
@@ -43,8 +58,12 @@ $tag = _var($disk,'name');
|
||||
$end = count($sheets)-1;
|
||||
$prev = $i>0 ? $sheets[$i-1] : $sheets[$end];
|
||||
$next = $i<$end ? $sheets[$i+1] : $sheets[0];
|
||||
$text = isPool($name) ? _('This will ERASE content of ALL devices in the pool') : _('This will ERASE ALL device content');
|
||||
$textErase = isPool($name) ? _('This will ERASE content of ALL devices in the pool') : _('This will ERASE ALL device content');
|
||||
$textDelete = _('This will unassign all devices from the pool but will NOT modify any device contents');
|
||||
|
||||
function disabled_if($condition) {
|
||||
if ($condition !== false) echo ' disabled';
|
||||
}
|
||||
function sanitize(&$val) {
|
||||
$data = explode('.',str_replace([' ',','],['','.'],$val));
|
||||
$last = array_pop($data);
|
||||
@@ -111,12 +130,12 @@ function isPool($name) {
|
||||
<link type="text/css" rel="stylesheet" href="<?autov("/plugins/dynamix.docker.manager/styles/style-$theme.css")?>">
|
||||
|
||||
<script>
|
||||
<?if (empty($disk)):?>
|
||||
done();
|
||||
<?endif;?>
|
||||
|
||||
String.prototype.celsius = function(){return Math.round((parseInt(this)-32)*5/9).toString();}
|
||||
|
||||
if ($.cookie('deletepool')) {
|
||||
$.removeCookie('deletepool');
|
||||
done();
|
||||
}
|
||||
function setFloor() {
|
||||
if ($('#shareFloor').length==0) return;
|
||||
const fsSize = {<?=fsSize()?>};
|
||||
@@ -195,93 +214,172 @@ function prepareZFS(form) {
|
||||
}
|
||||
<?endif;?>
|
||||
|
||||
function selectDiskFsWidth() {
|
||||
var num_slots = <?=_var($disk,'slots',0)?>;
|
||||
var selected_width = <?=_var($disk,'fsWidth',0)?>;
|
||||
$('#diskFsWidthZFS').empty();
|
||||
if (num_slots == 0) {
|
||||
$('#diskFsWidthZFS').prop('disabled',true).append($('<option>', {
|
||||
value: 0,
|
||||
text: "<?=_('no devices')?>"
|
||||
}));
|
||||
} else if ($('#diskFsProfileZFS').val() == '') {
|
||||
var label = (num_slots == 1) ? "device" : "devices";
|
||||
$('#diskFsWidthZFS').append($('<option>', {
|
||||
function setDiskFsWidth(slots) {
|
||||
$('#diskFsWidth').empty();
|
||||
$('#diskFsWidth').append($('<option>', {value: slots, text:''}));
|
||||
$('#diskFsWidth').val(slots);
|
||||
$('#diskFsProfile').off('change');
|
||||
}
|
||||
function selectDiskFsProfileAuto() {
|
||||
$('#diskFsProfile').empty();
|
||||
$('#diskFsProfile').append($('<option>', {value: '', text:''}));
|
||||
$('#diskFsProfile').val('');
|
||||
setDiskFsWidth('');
|
||||
}
|
||||
function selectDiskFsProfileXFS() {
|
||||
$('#diskFsProfile').empty();
|
||||
$('#diskFsProfile').append($('<option>', {value: '', text:''}));
|
||||
$('#diskFsProfile').val('');
|
||||
setDiskFsWidth(1);
|
||||
}
|
||||
function selectDiskFsProfileBTRFS(slots,init) {
|
||||
$('#diskFsProfile').empty();
|
||||
$('#diskFsProfile').append($('<option>', {value: 'single', text:_('single')}));
|
||||
if (slots >= 2) $('#diskFsProfile').append($('<option>', {value: 'raid0', text:_('raid0')}));
|
||||
if (slots >= 2) $('#diskFsProfile').append($('<option>', {value: 'raid1', text:_('raid1')}));
|
||||
if (slots >= 3) $('#diskFsProfile').append($('<option>', {value: 'raid1c3', text:_('raid1c3')}));
|
||||
if (slots >= 4) $('#diskFsProfile').append($('<option>', {value: 'raid1c4', text:_('raid1c4')}));
|
||||
if (slots >= 4) $('#diskFsProfile').append($('<option>', {value: 'raid10', text:_('raid10')}));
|
||||
if (slots >= 3) $('#diskFsProfile').append($('<option>', {value: 'raid5', text:_('raid5')}));
|
||||
if (slots >= 4) $('#diskFsProfile').append($('<option>', {value: 'raid6', text:_('raid6')}));
|
||||
if (init) {
|
||||
$('#diskFsProfile').val("<?=_var($disk,'fsProfile')?>");
|
||||
} else {
|
||||
if (slots == 1) $('#diskFsProfile').val('');
|
||||
if (slots >= 2) $('#diskFsProfile').val('raid1');
|
||||
}
|
||||
setDiskFsWidth(slots);
|
||||
}
|
||||
function selectDiskFsWidthZFS(slots,init) {
|
||||
var selected_width = init ? Number("<?=_var($disk,'fsWidth')?>") : 0;
|
||||
$('#diskFsWidth').empty();
|
||||
if ($('#diskFsProfile').val() == '') {
|
||||
var label = (slots == 1) ? "device" : "devices";
|
||||
$('#diskFsWidth').append($('<option>', {
|
||||
value: 1,
|
||||
text: _(sprintf('%s '+label,num_slots))
|
||||
text: _(sprintf('%s '+label,slots))
|
||||
}));
|
||||
} else if ($('#diskFsProfileZFS').val() == 'mirror') {
|
||||
if (selected_width == 0) selected_width = 1;
|
||||
} else if ($('#diskFsProfile').val() == 'mirror') {
|
||||
var width;
|
||||
for (width=2; width<=Math.min(num_slots,4); width++) {
|
||||
if ((num_slots % width) == 0) {
|
||||
var groups = num_slots / width;
|
||||
var label = (groups == 1) ? "group" : "groups";
|
||||
$('#diskFsWidthZFS').append($('<option>', {
|
||||
for (width=2; width<=Math.min(slots,4); width++) {
|
||||
if ((slots % width) == 0) {
|
||||
var groups = slots / width;
|
||||
var label = (groups == 1) ? "vdev" : "vdevs";
|
||||
$('#diskFsWidth').append($('<option>', {
|
||||
value: width,
|
||||
text: _(sprintf('%s '+label+' of %s devices',groups,width)),
|
||||
selected: (width == selected_width)
|
||||
}));
|
||||
if (selected_width == 0) selected_width = width;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
var width, min_width;
|
||||
if ($('#diskFsProfileZFS').val() == 'raidz1') min_width = 3;
|
||||
else if ($('#diskFsProfileZFS').val() == 'raidz2') min_width = 3;
|
||||
else if ($('#diskFsProfileZFS').val() == 'raidz3') min_width = 4;
|
||||
for (width=min_width; width<=num_slots; width++) {
|
||||
if ((num_slots % width) == 0) {
|
||||
var groups = num_slots / width;
|
||||
var label = (groups == 1) ? "group" : "groups";
|
||||
$('#diskFsWidthZFS').append($('<option>', {
|
||||
if ($('#diskFsProfile').val() == 'raidz1') min_width = 3;
|
||||
else if ($('#diskFsProfile').val() == 'raidz2') min_width = 3;
|
||||
else if ($('#diskFsProfile').val() == 'raidz3') min_width = 4;
|
||||
for (width=min_width; width<=slots; width++) {
|
||||
if ((slots % width) == 0) {
|
||||
var groups = slots / width;
|
||||
var label = (groups == 1) ? "vdev" : "vdevs";
|
||||
$('#diskFsWidth').append($('<option>', {
|
||||
value: width,
|
||||
text: _(sprintf('%s '+label+' of %s devices',groups,width)),
|
||||
selected: (width == selected_width)
|
||||
}));
|
||||
if (selected_width == 0) selected_width = width;
|
||||
}
|
||||
}
|
||||
}
|
||||
$('#diskFsWidth').val(selected_width);
|
||||
}
|
||||
function selectDiskFsProfileZFS(slots,init,subpool) {
|
||||
$('#diskFsProfile').empty();
|
||||
if (slots == 1) $('#diskFsProfile').append($('<option>', {value: '', text: _('single')}));
|
||||
if (slots >= 2) $('#diskFsProfile').append($('<option>', {value: '', text: _('stripe')}));
|
||||
if (subpool != 'cache' && subpool != 'spares') {
|
||||
if (slots%2 == 0 || slots%3 == 0 || slots%4 == 0) $('#diskFsProfile').append($('<option>', {value: 'mirror', text: _('mirror')}));
|
||||
if (subpool == '') {
|
||||
if (slots >= 3 && subpool == '') $('#diskFsProfile').append($('<option>', {value: 'raidz1', text: _('raidz1')}));
|
||||
if (slots >= 3 && subpool == '') $('#diskFsProfile').append($('<option>', {value: 'raidz2', text: _('raidz2')}));
|
||||
if (slots >= 4 && subpool == '') $('#diskFsProfile').append($('<option>', {value: 'raidz3', text: _('raidz3')}));
|
||||
}
|
||||
}
|
||||
if (init) {
|
||||
$('#diskFsProfile').val("<?=_var($disk,'fsProfile')?>");
|
||||
} else {
|
||||
if (slots == 1) $('#diskFsProfile').val('');
|
||||
if (slots == 2) $('#diskFsProfile').val('mirror');
|
||||
if (slots >= 3) $('#diskFsProfile').val('raidz1');
|
||||
}
|
||||
selectDiskFsWidthZFS(slots,init);
|
||||
$('#diskFsProfile').on('change', function() {
|
||||
selectDiskFsWidthZFS(slots,false);
|
||||
});
|
||||
}
|
||||
/* called upon page load (init==true) and when user changes file system type (init==false) */
|
||||
function selectDiskFsProfile(init) {
|
||||
var t = init ? null : 'slow';
|
||||
if (($('#diskFsType').val()||'').indexOf('auto') != -1) {
|
||||
$('#diskFsProfileBTRFS').prop('disabled',true).hide();
|
||||
$('#diskFsProfileZFS').prop('disabled',true).hide();
|
||||
$('#diskFsWidthZFS').prop('disabled',true).hide();;
|
||||
$('#compression').hide(t);
|
||||
$('#autotrim').hide(t);
|
||||
} else if (($('#diskFsType').val()||'').indexOf('btrfs') != -1) {
|
||||
if (!init) $('#diskFsProfileBTRFS').prop('disabled',false);
|
||||
$('#diskFsProfileBTRFS').show();
|
||||
$('#diskFsProfileZFS').prop('disabled',true).hide();
|
||||
$('#diskFsWidthZFS').prop('disabled',true).hide();
|
||||
$('#compression').show(t);
|
||||
<?if (diskType('Cache')):?>
|
||||
$('#autotrim').show(t);
|
||||
<?endif;?>
|
||||
} else if (($('#diskFsType').val()||'').indexOf('zfs') != -1) {
|
||||
$('#diskFsProfileBTRFS').prop('disabled',true).hide();
|
||||
if (!init) {
|
||||
$('#diskFsProfileZFS').prop('disabled',false);
|
||||
$('#diskFsWidthZFS').prop('disabled',false);
|
||||
}
|
||||
$('#diskFsProfileZFS').show();
|
||||
$('#diskFsWidthZFS').show();
|
||||
selectDiskFsWidth();
|
||||
$('#compression').show(t);
|
||||
<?if (diskType('Cache')):?>
|
||||
$('#autotrim').show(t);
|
||||
} else if (($('#diskFsType').val()||'').indexOf('xfs') != -1) {
|
||||
$('#autotrim').show(t);
|
||||
<?endif;?>
|
||||
|
||||
/* for array disks, 'slots', 'fsWidth', and 'fsGroups' is not defined */
|
||||
var slots = Number("<?=_var($disk,'fsWidth',1)?>") * Number("<?=_var($disk,'fsGroups',1)?>");
|
||||
if (slots == 0) slots = <?=_var($disk,'slots',1)?>;
|
||||
|
||||
var subpool = "<?=isSubpool($name) ?: ''?>";
|
||||
var fsType;
|
||||
|
||||
if (subpool == '') {
|
||||
fsType = init ? "<?=_var($disk,'fsType','')?>" : $('#diskFsType').val();
|
||||
} else {
|
||||
fsType = 'zfs';
|
||||
}
|
||||
|
||||
if (slots == 1 || fsType == 'auto') {
|
||||
$('#profile').hide(t);
|
||||
} else {
|
||||
$('#profile').show(t);
|
||||
if (fsType.indexOf('zfs') != -1) {
|
||||
if (subpool != 'cache' && subpool != 'spares') {
|
||||
$('#diskFsProfile').show();
|
||||
} else {
|
||||
$('#diskFsProfile').hide();
|
||||
}
|
||||
$('#diskFsWidth').show();
|
||||
} else {
|
||||
$('#diskFsProfile').show();
|
||||
$('#diskFsWidth').hide()
|
||||
}
|
||||
}
|
||||
|
||||
if (fsType == 'auto') {
|
||||
selectDiskFsProfileAuto();
|
||||
} else if (fsType.indexOf('btrfs') != -1) {
|
||||
selectDiskFsProfileBTRFS(slots,init);
|
||||
} else if (fsType.indexOf('zfs') != -1) {
|
||||
selectDiskFsProfileZFS(slots,init,subpool);
|
||||
} else if (fsType.indexOf('xfs') != -1) {
|
||||
selectDiskFsProfileXFS();
|
||||
}
|
||||
|
||||
if (subpool != '' || fsType == 'auto' || fsType.indexOf('xfs') != -1) {
|
||||
$('#compression').hide(t);
|
||||
$('#diskCompression').prop('disabled',true);
|
||||
} else {
|
||||
$('#compression').show(t);
|
||||
$('#diskCompression').prop('disabled',false);
|
||||
}
|
||||
|
||||
<?if (diskType('Data') || isSubpool($name)):?>
|
||||
$('#autotrim').hide(t);
|
||||
$('#diskAutotrim').prop('disabled',true);
|
||||
<?else:?>
|
||||
if (fsType == 'auto') {
|
||||
$('#autotrim').hide(t);
|
||||
$('#diskAutotrim').prop('disabled',true);
|
||||
} else {
|
||||
$('#autotrim').show(t);
|
||||
$('#diskAutotrim').prop('disabled',false);
|
||||
}
|
||||
}
|
||||
function changeFsType() {
|
||||
var fstype = ($('#diskFsType').val()||'').replace('luks:','');
|
||||
if (['btrfs', 'zfs'].includes(fstype)) $('#compression').show('slow'); else $('#compression').hide('slow');
|
||||
<?if (diskType('Cache')):?>
|
||||
if (['xfs', 'btrfs', 'zfs'].includes(fstype)) $('#autotrim').show('slow'); else $('#autotrim').hide('slow');
|
||||
<?endif;?>
|
||||
if (fstype=='reiserfs') $('#reiserfs').show(); else $('#reiserfs').hide();
|
||||
}
|
||||
function prepareDeviceInfo(form) {
|
||||
var events = [];
|
||||
@@ -516,14 +614,10 @@ function renamePoolPopup() {
|
||||
});
|
||||
dialogStyle();
|
||||
}
|
||||
function deletePool() {
|
||||
$.cookie('deletepool','deletepool');
|
||||
document.deletepool.submit();
|
||||
}
|
||||
function eraseDisk(name) {
|
||||
swal({
|
||||
title:"_(Erase Device Content)_?",
|
||||
text:"<?=$text?><p style='font-weight:bold;color:red;margin:8px 0'>_(Existing content is permanently lost)_</p>",
|
||||
text:"<?=$textErase?><p style='font-weight:bold;color:red;margin:8px 0'>_(Existing content is permanently lost)_</p>",
|
||||
html:true,
|
||||
type:'input',
|
||||
inputPlaceholder:"<?=sprintf(_('To confirm your action type: %s'),$name)?>",
|
||||
@@ -537,8 +631,37 @@ function eraseDisk(name) {
|
||||
swal.close();
|
||||
$('#doneButton').prop('disabled',true);
|
||||
$('#eraseButton').prop('disabled',true);
|
||||
$('#removeButton').prop('disabled',true);
|
||||
$('div.spinner.fixed').show();
|
||||
$.get("/update.htm",{cmdWipefs:name,csrf_token:"<?=_var($var,'csrf_token')?>"},function(){
|
||||
$.post("/update.htm",{cmdWipefs:name},function(){
|
||||
$('div.spinner.fixed').hide();
|
||||
refresh();
|
||||
});
|
||||
} else {
|
||||
if (confirm.length) swal({title:"_(Incorrect confirmation)_",text:"_(Please try again)_!",type:'error',html:true,confirmButtonText:"_(Ok)_"});
|
||||
}
|
||||
});
|
||||
}
|
||||
function removePool(name) {
|
||||
swal({
|
||||
title:"_(Remove pool)_?",
|
||||
text:"<?=$textDelete?>",
|
||||
html:true,
|
||||
type:'input',
|
||||
inputPlaceholder:"<?=sprintf(_('To confirm your action type: %s'),$name)?>",
|
||||
showCancelButton:true,
|
||||
closeOnConfirm:false,
|
||||
confirmButtonText:"_(Proceed)_",
|
||||
cancelButtonText:"_(Cancel)_"
|
||||
},
|
||||
function(confirm){
|
||||
if (confirm == "<?=$name?>") {
|
||||
swal.close();
|
||||
$('#doneButton').prop('disabled',true);
|
||||
$('#eraseButton').prop('disabled',true);
|
||||
$('#removeButton').prop('disabled',true);
|
||||
$('div.spinner.fixed').show();
|
||||
$.post("/update.htm",{changeSlots:"apply",poolName:name,poolSlots:0},function(){
|
||||
$('div.spinner.fixed').hide();
|
||||
refresh();
|
||||
});
|
||||
@@ -588,7 +711,7 @@ _(Spinup group(s))_:
|
||||
: <input type="text" name="diskSpinupGroup.<?=_var($disk,'idx',0)?>" maxlength="256" value="<?=_var($disk,'spinupGroup')?>">
|
||||
|
||||
<?endif;?>
|
||||
<?if (diskType('Data') || isPool($tag)):?>
|
||||
<?if (diskType('Data','Parity') || isPool($tag)):?>
|
||||
_(Spin down delay)_:
|
||||
: <select name="diskSpindownDelay.<?=_var($disk,'idx',0)?>">
|
||||
<?=mk_option(_var($disk,'spindownDelay'), "-1", _('Use default'))?>
|
||||
@@ -607,111 +730,57 @@ _(Spin down delay)_:
|
||||
<?=mk_option(_var($disk,'spindownDelay'), "9", "9 "._('hours'))?>
|
||||
</select><span id="smart_selftest" class='orange-text'></span>
|
||||
|
||||
<?endif;?>
|
||||
<?if (diskType('Data') || (isPool($tag) && !isSubpool($tag))):?>
|
||||
_(File system status)_:
|
||||
: <?=_(_var($disk,'fsStatus'))?>
|
||||
|
||||
<?$disabled = (_var($var,'fsState')=="Stopped" && !empty(_var($disk,'uuid'))) || (_var($var,'fsState')=="Started" && _var($disk,'fsType')!='auto') ? "disabled" : ""?>
|
||||
<?if (diskType('Data') || (!isSubpool($name) && _var($disk,'slots',0)==1)):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="changeFsType()" <?=$disabled?>>
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx')?>" onchange="selectDiskFsProfile(false)" <?=disabled_if($fsTypeImmutable)?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "auto", _('auto'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "xfs", _('xfs'))?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "xfs", _('xfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "btrfs", _('btrfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "reiserfs", _('reiserfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:xfs", _('xfs')." - "._('encrypted'))?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "reiserfs", _('reiserfs'), "disabled")?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "luks:xfs", _('xfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:zfs", _('zfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:btrfs", _('btrfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:reiserfs", _('reiserfs')." - "._('encrypted'))?>
|
||||
<?if (_var($disk,'slots',1) == 1) echo mk_option(_var($disk,'fsType'), "luks:reiserfs", _('reiserfs')." - "._('encrypted'), "disabled")?>
|
||||
</select><span id="reiserfs" class="warning"<?if (!fsType('reiserfs')):?> style="display:none"<?endif;?>><i class="fa fa-warning"></i> _(ReiserFS is deprecated, please use another file system)_!</span>
|
||||
|
||||
:info_file_system_help:
|
||||
|
||||
<?elseif (!isSubpool($name) && _var($disk,'slots',0)>1):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "auto", _('auto'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "btrfs", _('btrfs'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:zfs", _('zfs')." - "._('encrypted'))?>
|
||||
<?=mk_option(_var($disk,'fsType'), "luks:btrfs", _('btrfs')." - "._('encrypted'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileBTRFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
<?=mk_option(_var($disk,'fsProfile'),"single", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"raid0", _('raid0'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"raid1", _('raid1'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raid1c3", _('raid1c3'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raid1c4", _('raid1c4'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raid10", _('raid10'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raid5", _('raid5'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raid6", _('raid6'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" style="display:none" onchange="selectDiskFsWidth()" <?=$disabled?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('raid0'))?>
|
||||
<?if ((_var($disk,'slots',0)%2)==0 || (_var($disk,'slots',0)%3)==0 || (_var($disk,'slots',0)%4)==0) echo mk_option(_var($disk,'fsProfile'),"mirror", _('mirror'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raidz1", _('raidz'))?>
|
||||
<?if (_var($disk,'slots',0)>=3) echo mk_option(_var($disk,'fsProfile'),"raidz2", _('raidz2'))?>
|
||||
<?if (_var($disk,'slots',0)>=4) echo mk_option(_var($disk,'fsProfile'),"raidz3", _('raidz3'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="special" || isSubpool($name)=="logs" || isSubpool($name)=="dedup"):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=$disabled?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('raid0'))?>
|
||||
<?if ((_var($disk,'slots',0)%2)==0 || (_var($disk,'slots',0)%3)==0 || (_var($disk,'slots',0)%4)==0) echo mk_option(_var($disk,'fsProfile'),"mirror", _('mirror'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="cache"):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<select id="diskFsProfileZFS" name="diskFsProfile.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsWidth()" <?=$disabled?>>
|
||||
<?if (_var($disk,'slots',0)==1) echo mk_option(_var($disk,'fsProfile'),"", _('single'))?>
|
||||
<?if (_var($disk,'slots',0)>=2) echo mk_option(_var($disk,'fsProfile'),"", _('raid0'))?>
|
||||
</select>
|
||||
<select id="diskFsWidthZFS" name="diskFsWidth.<?=_var($disk,'idx',0)?>" style="display:none" <?=$disabled?>>
|
||||
</select>
|
||||
<?elseif (isSubpool($name)=="spares"):?>
|
||||
_(File system type)_:
|
||||
: <select id="diskFsType" name="diskFsType.<?=_var($disk,'idx',0)?>" onchange="selectDiskFsProfile(false)" <?=$disabled?>>
|
||||
<?=mk_option(_var($disk,'fsType'), "zfs", _('zfs'))?>
|
||||
</select>
|
||||
<?endif;?>
|
||||
|
||||
<?if (isSubpool($name)===false):?>
|
||||
<div markdown="1" id="compression" style="display:none">
|
||||
<?if (diskType('Data') || isPool($tag)):?>
|
||||
<div markdown="1" id="profile">
|
||||
_(Allocation profile)_:
|
||||
: <select id="diskFsProfile" name="diskFsProfile.<?=_var($disk,'idx')?>" <?=disabled_if($fsProfileImmutable)?>>
|
||||
</select>
|
||||
<select id="diskFsWidth" name="diskFsWidth.<?=_var($disk,'idx')?>" <?=disabled_if($fsProfileImmutable)?>>
|
||||
</select>
|
||||
|
||||
:info_profile_help:
|
||||
</div>
|
||||
<div markdown="1" id="compression">
|
||||
_(Compression)_:
|
||||
<?$disabled = _var($disk,'fsStatus')=='Mounted' ? "disabled" : ""?>
|
||||
: <select id="diskCompression" name="diskCompression.<?=_var($disk,'idx',0)?>" <?=$disabled?>>
|
||||
: <select id="diskCompression" name="diskCompression.<?=_var($disk,'idx',0)?>" <?=disabled_if(_var($disk,'fsStatus')=='Mounted')?>>
|
||||
<?=mk_option(_var($disk,'compression'), "off", _('Off'))?>
|
||||
<?=mk_option(_var($disk,'compression'), "on", _('On'))?>
|
||||
</select>
|
||||
|
||||
:info_compression_help:
|
||||
</div>
|
||||
|
||||
<div markdown="1" id="autotrim" style="display:none">
|
||||
<div markdown="1" id="autotrim">
|
||||
_(Autotrim)_:
|
||||
<?$disabled = _var($disk,'fsStatus')=='Mounted' ? "disabled" : ""?>
|
||||
: <select id="diskAutotrim" name="diskAutotrim.<?=_var($disk,'idx',0)?>" <?=$disabled?>>
|
||||
: <select id="diskAutotrim" name="diskAutotrim.<?=_var($disk,'idx',0)?>" <?=disabled_if(_var($disk,'fsStatus')=='Mounted')?>>
|
||||
<?=mk_option(_var($disk,'autotrim'), "on", _('On'))?>
|
||||
<?=mk_option(_var($disk,'autotrim'), "off", _('Off'))?>
|
||||
</select>
|
||||
|
||||
:info_autotrim_help:
|
||||
</div>
|
||||
<?if (isPool($name)):?>
|
||||
<?endif;?>
|
||||
<?if (isPool($tag) && !isSubpool($tag)):?>
|
||||
_(Enable user share assignment)_:
|
||||
<?$disabled = _var($var,'fsState')!="Stopped" ? "disabled" : ""?>
|
||||
: <select id="shareEnabled" name="diskShareEnabled.<?=_var($disk,'idx',0)?>" onchange="freeSpace(this.value)" <?=$disabled?>>
|
||||
: <select id="shareEnabled" name="diskShareEnabled.<?=_var($disk,'idx',0)?>" onchange="freeSpace(this.value)" <?=disabled_if(_var($var,'fsState')!="Stopped")?>>
|
||||
<?=mk_option(_var($disk,'shareEnabled'), "yes", _('Yes'))?>
|
||||
<?=mk_option(_var($disk,'shareEnabled'), "no", _('No'))?>
|
||||
</select>
|
||||
@@ -725,6 +794,7 @@ _(Minimum free space)_:
|
||||
:info_free_space_help:
|
||||
|
||||
<?endif;?>
|
||||
<?if (diskType('Data') || (isPool($tag) && !isSubpool($tag))):?>
|
||||
_(Warning disk utilization threshold)_ (%):
|
||||
: <input type="number" min="0" max="100" name="diskWarning.<?=_var($disk,'idx',0)?>" autocomplete="off" spellcheck="false" class="narrow" value="<?=_var($disk,'warning')?>" placeholder="<?=_var($display,'warning')?>">
|
||||
|
||||
@@ -735,35 +805,49 @@ _(Critical disk utilization threshold)_ (%):
|
||||
|
||||
:info_critical_utilization_help:
|
||||
|
||||
<?endif;?>
|
||||
<?endif;?>
|
||||
|
||||
: <input type="submit" name="changeDisk" value="_(Apply)_" disabled><input type="button" id="doneButton" value="_(Done)_" onclick="done()">
|
||||
<?$erasable=false?>
|
||||
<?$removeable=false?>
|
||||
<?if (diskType('Parity','Data')):?>
|
||||
<?if (_var($var,'fsState')=="Stopped" && diskStatus('_NEW')): $erasable=true; endif;?>
|
||||
<?if (_var($var,'fsState')=="Started" && _var($var,'startMode')!="Normal" && diskType('Data')): $erasable=true; endif;?>
|
||||
<input type="button" id="eraseButton" value="_(Erase)_" onclick="eraseDisk('<?=$name?>')"<?=$erasable?'':' disabled'?>>
|
||||
<?endif;?>
|
||||
<?if (isPool($name) && strpos($name,$_tilde_)===false):?>
|
||||
<?if (isPool($name) && isSubpool($name)===false):?>
|
||||
<?if (_var($var,'fsState')=="Stopped" || (_var($var,'fsState')=="Started" && _var($var,'startMode')!="Normal")): $erasable=true; endif;?>
|
||||
<input type="button" id="eraseButton" value="_(Erase)_" onclick="eraseDisk('<?=$name?>')"<?=$erasable?'':' disabled'?>>
|
||||
<?endif;?>
|
||||
<?if (_var($var,'fsState')=="Stopped" && isPool($name)):?>
|
||||
<?$empty = _var($disk,'devices',0)==0?>
|
||||
<input type="button" value="_(Delete Pool)_" onclick="deletePool()"<?=$empty?'':' disabled'?>><?if (!$empty):?>_(Unassign **ALL** devices to delete this pool)_<?endif;?>
|
||||
<input type="button" id="eraseButton" value="_(Erase Pool)_" onclick="eraseDisk('<?=$name?>')"<?=$erasable?'':' disabled'?>>
|
||||
<?if (_var($var,'fsState')=="Stopped"): $removeable=true; endif;?>
|
||||
<input type="button" id="removeButton" value="_(Remove Pool)_" onclick="removePool('<?=$name?>')"<?=$removeable?'':' disabled'?>>
|
||||
<?endif;?>
|
||||
</form>
|
||||
|
||||
<?if (fsType('btrfs')):?>
|
||||
<?if (!maintenance_mode()):?>
|
||||
<div class="title nocontrol"><span class="left"><i class="title fa fa-hdd-o"></i>_(Pool Device Status)_</span></div>
|
||||
<form markdown="1" method="POST" action="/update.php" target="progressFrame">
|
||||
|
||||
_(pool device stats)_:
|
||||
: <?echo "<pre>" . htmlspecialchars(shell_exec("/sbin/btrfs dev stats -T ".escapeshellarg("/mnt/$tag")), ENT_QUOTES, 'UTF-8') . "</pre>"; ?>
|
||||
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_check">
|
||||
<input type="hidden" name="#arg[1]" value="reset">
|
||||
<input type="hidden" name="#arg[2]" value="/mnt/<?=$tag?>">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Reset)_">
|
||||
</form>
|
||||
<?endif;?>
|
||||
|
||||
<div class="title nocontrol"><span class="left"><i class="title fa fa-balance-scale"></i>_(Balance Status)_</span></div>
|
||||
<form markdown="1" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareFS(this,'btrfs-balance-<?=$tag?>','/mnt/<?=$tag?>')">
|
||||
<?if (_var($disk,'fsStatus')=="Mounted"):?>
|
||||
<?exec("$docroot/webGui/scripts/btrfs_balance status /mnt/$tag", $balance_status, $retval)?>
|
||||
<?$usage = exec("/sbin/btrfs fi usage /mnt/$tag|grep -Pom1 '^Data,.+ \\(\\K[^%]+'");?>
|
||||
<?exec("$docroot/webGui/scripts/btrfs_balance status ".escapeshellarg("/mnt/$tag"), $balance_status, $retval)?>
|
||||
<?$usage = exec("/sbin/btrfs fi usage ".escapeshellarg("/mnt/$tag")." | grep -Pom1 '^Data,.+ \\(\\K[^%]+'");?>
|
||||
|
||||
_(btrfs filesystem df)_:
|
||||
: <?echo "<pre>".shell_exec("/sbin/btrfs filesystem df /mnt/$tag")."</pre>"?>
|
||||
_(btrfs filesystem usage)_:
|
||||
: <?echo "<pre>" . htmlspecialchars(shell_exec("/sbin/btrfs fi usage -T ".escapeshellarg("/mnt/$tag")), ENT_QUOTES, 'UTF-8') . "</pre>"; ?>
|
||||
|
||||
_(btrfs balance status)_:
|
||||
: <?echo "<pre id='btrfs-balance'>".implode("\n", $balance_status)."</pre>"?>
|
||||
@@ -803,7 +887,7 @@ _(btrfs balance status)_:
|
||||
<input type="hidden" name="#arg[2]" value="/mnt/<?=$tag?>">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Cancel)_">
|
||||
: <input type="submit" value="_(Cancel)_"> *_(Running)_*
|
||||
|
||||
:info_balance_cancel_help:
|
||||
|
||||
@@ -811,7 +895,7 @@ _(btrfs balance status)_:
|
||||
<?else:?>
|
||||
|
||||
|
||||
: <input type="submit" value="_(Balance)_" disabled><?=$tag==prefix($tag) ? "<b>"._('Balance')."</b> "._('is only available when array is Started') : sprintf(_('See %s Settings'),ucfirst(prefix($tag)))?>
|
||||
: <input type="submit" value="_(Balance)_" disabled><b>_(Balance)_</b> _(is only available when the filesyestem is mounted)_
|
||||
|
||||
<?endif;?>
|
||||
</form>
|
||||
@@ -876,7 +960,7 @@ _(Block group usage)_ (%):
|
||||
<div class="title nocontrol"><span class="left"><i class="title fa fa-paint-brush"></i>_(Scrub Status)_</span></div>
|
||||
<form markdown="1" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareFS(this,'btrfs-scrub-<?=$tag?>','/mnt/<?=$tag?>')">
|
||||
<?if (_var($disk,'fsStatus')=="Mounted"):?>
|
||||
<?exec("$docroot/webGui/scripts/btrfs_scrub status /mnt/$tag", $scrub_status, $retval)?>
|
||||
<?exec("$docroot/webGui/scripts/btrfs_scrub status ".escapeshellarg("/mnt/$tag"), $scrub_status, $retval)?>
|
||||
|
||||
_(btrfs scrub status)_:
|
||||
: <?echo "<pre id='btrfs-scrub'>".implode("\n", $scrub_status)."</pre>"?>
|
||||
@@ -885,10 +969,10 @@ _(btrfs scrub status)_:
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_scrub">
|
||||
<input type="hidden" name="#arg[1]" value="start">
|
||||
<input type="hidden" name="#arg[2]" value="/mnt/<?=$tag?>">
|
||||
<input type="hidden" name="#arg[3]" value="-r">
|
||||
<input type="hidden" name="#arg[3]" value="">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Scrub)_"><label><input type="checkbox" name="#arg[3]" value=""> _(Repair corrupted blocks)_</label>
|
||||
: <input type="submit" value="_(Scrub)_">
|
||||
|
||||
:info_btrfs_scrub_help:
|
||||
|
||||
@@ -898,7 +982,7 @@ _(btrfs scrub status)_:
|
||||
<input type="hidden" name="#arg[2]" value="/mnt/<?=$tag?>">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Cancel)_">
|
||||
: <input type="submit" value="_(Cancel)_"> *_(Running)_*
|
||||
|
||||
:info_scrub_cancel_help:
|
||||
|
||||
@@ -906,7 +990,7 @@ _(btrfs scrub status)_:
|
||||
<?else:?>
|
||||
|
||||
|
||||
: <input type="submit" value="_(Scrub)_" disabled><?=$tag==prefix($tag) ? "<b>"._('Scrub')."</b> "._('is only available when array is Started') : sprintf(_('See %s Settings'),ucfirst(prefix($tag)))?>
|
||||
: <input type="submit" value="_(Scrub)_" disabled><b>_(Scrub)_</b> _(is only available when the filesyestem is mounted)_
|
||||
|
||||
<?endif;?>
|
||||
</form>
|
||||
@@ -918,6 +1002,7 @@ _(btrfs scrub status)_:
|
||||
<input type="hidden" name="#include" value="/webGui/include/update.btrfs.php">
|
||||
<input type="hidden" name="#job" value="scrub_<?=$tag?>;<?=$docroot?>/plugins/dynamix/scripts/btrfs_scrub start /mnt/<?=$tag?> -r">
|
||||
<input type="hidden" name="hour" value="">
|
||||
|
||||
_(Scrub schedule)_:
|
||||
: <select name="mode" onchange="presetBTRFS(this.form,'#scrub-hour')">
|
||||
<?for ($m=0; $m<count($mode); $m++):?>
|
||||
@@ -973,7 +1058,7 @@ _(Time of the day)_:
|
||||
_(btrfs check status)_:
|
||||
: <?echo "<pre id='btrfs-check'>".implode("\n", $check_status)."</pre>"?>
|
||||
|
||||
<?if ($retval != 0):?>
|
||||
<?if ($retval == 0):?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/btrfs_check">
|
||||
<input type="hidden" name="#arg[1]" value="start">
|
||||
<input type="hidden" name="#arg[2]" value="/dev/<?=_var($disk,'deviceSb')?>">
|
||||
@@ -998,23 +1083,17 @@ _(btrfs check status)_:
|
||||
<?endif;?>
|
||||
<?else:?>
|
||||
|
||||
<?if ($tag==prefix($tag)):?>
|
||||
|
||||
: <input type="submit" value="_(Check)_" disabled> **_(Check)_** _(is only available when array is Started in **Maintenance** mode)_.
|
||||
|
||||
<?else:?>
|
||||
|
||||
: <input type="submit" value="_(Check)_" disabled> <?=sprintf(_('See %s Settings'),ucfirst(prefix($tag)))?>.
|
||||
<?endif;?>
|
||||
|
||||
<?endif;?>
|
||||
</form>
|
||||
<?endif;?>
|
||||
<?if (fsType('zfs') && !isSubpool($name)):?>
|
||||
<div class="title nocontrol"><span class="left"><i class="title fa fa-info"></i>_(Pool Status)_</span></div>
|
||||
<div class="title nocontrol"><span class="left"><i class="title fa fa-hdd-o"></i>_(Pool Status)_</span></div>
|
||||
<form markdown="1" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareFS(this,'zfs-scrub-<?=$tag?>','<?=$tag?>')">
|
||||
<?if (_var($disk,'fsStatus')=="Mounted"):?>
|
||||
<?exec("$docroot/webGui/scripts/zfs_scrub status $tag", $zfs_status, $retval); $zfs_status = implode("\n",$zfs_status)?>
|
||||
<?exec("$docroot/webGui/scripts/zfs_scrub status ".escapeshellarg($tag), $zfs_status, $retval); $zfs_status = implode("\n",$zfs_status)?>
|
||||
<?$zfs_cmd = strpos($zfs_status,"'zpool clear'")===false ? 'start' : 'clear'?>
|
||||
|
||||
_(zfs pool status)_:
|
||||
@@ -1036,7 +1115,7 @@ _(zfs pool status)_:
|
||||
<input type="hidden" name="#arg[2]" value="<?=$tag?>">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Cancel)_">
|
||||
: <input type="submit" value="_(Cancel)_"> *_(Running)_*
|
||||
|
||||
:info_scrub_cancel_help:
|
||||
|
||||
@@ -1044,18 +1123,28 @@ _(zfs pool status)_:
|
||||
<?else:?>
|
||||
|
||||
|
||||
: <input type="submit" value="_(Scrub)_" disabled><?=$tag==prefix($tag) ? "<b>"._('Scrub')."</b> "._('is only available when array is Started') : sprintf(_('See %s Settings'),ucfirst(prefix($tag)))?>
|
||||
: <input type="submit" value="_(Scrub)_" disabled><?=!$tag||$tag==prefix($tag) ? "<b>"._('Scrub')."</b> "._('is only available when the filesyestem is mounted') : sprintf(_('See %s Settings'),ucfirst(prefix($tag)))?>
|
||||
|
||||
<?endif;?>
|
||||
<?if (_var($disk,'fsStatus')=="Mounted"):?>
|
||||
<div class="title nocontrol"><span class="left"><i class="title fa fa-info"></i>_(Pool Information)_</span></div>
|
||||
<?exec("/usr/sbin/zpool list -v ".escapeshellarg($tag), $zfs_info_status, $info_retval); $zfs_info_status = implode("\n",$zfs_info_status)?>
|
||||
|
||||
_(zfs pool information)_:
|
||||
: <pre id='zfs-info'><?=$zfs_info_status?></pre>
|
||||
<?endif;?>
|
||||
|
||||
</form>
|
||||
<hr>
|
||||
<?$scrub = str_replace('-','_',"scrub_$tag")?>
|
||||
<div class="title nocontrol"><span class="left"><i class="title fa fa-paint-brush"></i>_(Scrub Schedule)_</span></div>
|
||||
<form markdown="1" name="scrub_schedule" method="POST" action="/update.php" target="progressFrame" onsubmit="prepareZFS(this)">
|
||||
<input type="hidden" name="#file" value="dynamix/dynamix.cfg">
|
||||
<input type="hidden" name="#section" value="<?=$scrub?>">
|
||||
<input type="hidden" name="#include" value="/webGui/include/update.zfs.php">
|
||||
<input type="hidden" name="#job" value="scrub_<?=$tag?>;<?=$docroot?>/plugins/dynamix/scripts/zfs_scrub start <?=$tag?>">
|
||||
<input type="hidden" name="hour" value="">
|
||||
|
||||
_(Scrub schedule)_:
|
||||
: <select name="mode" onchange="presetZFS(this.form,'#scrub-hour')">
|
||||
<?for ($m=0; $m<count($mode); $m++):?>
|
||||
@@ -1152,18 +1241,57 @@ _(reiserfsck status)_:
|
||||
_(xfs_repair status)_:
|
||||
: <?echo "<pre id='xfs-check'>".implode("\n", $check_status)."</pre>"?>
|
||||
|
||||
<?if ($retval != 0):?>
|
||||
<?if ($retval == 0 || $retval == 8):?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/xfs_check">
|
||||
<input type="hidden" name="#arg[1]" value="start">
|
||||
<input type="hidden" name="#arg[2]" value="/dev/<?=_var($disk,'deviceSb')?>">
|
||||
<input type="hidden" name="#arg[3]" value="<?=_var($disk,'id')?>">
|
||||
<input type="hidden" name="#arg[4]" value="-n">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Check)_"><input type="text" name="#arg[4]" class="narrow" maxlength="256" value="-n"> _(Options (see Help))_
|
||||
: <input type="submit" value="_(Check)_"><?if ($retval == 0): ?> _(No file system corruption detected)_.<?endif; ?>
|
||||
|
||||
:info_xfs_check_help:
|
||||
|
||||
<?else:?>
|
||||
<?elseif ($retval == 1):?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/xfs_check">
|
||||
<input type="hidden" name="#arg[1]" value="start">
|
||||
<input type="hidden" name="#arg[2]" value="/dev/<?=_var($disk,'deviceSb')?>">
|
||||
<input type="hidden" name="#arg[3]" value="<?=_var($disk,'id')?>">
|
||||
<input type="hidden" name="#arg[4]" value="-e">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Fix)_"><span style="color: red;">_(File system corruption detected)_.</span>
|
||||
|
||||
:info_xfs_check_help:
|
||||
|
||||
<?elseif ($retval == 2):?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/xfs_check">
|
||||
<input type="hidden" name="#arg[1]" value="start">
|
||||
<input type="hidden" name="#arg[2]" value="/dev/<?=_var($disk,'deviceSb')?>">
|
||||
<input type="hidden" name="#arg[3]" value="<?=_var($disk,'id')?>">
|
||||
<input type="hidden" name="#arg[4]" value="-eL">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Zero Log)_"><span style="color: red;">_(Dirty log detected)_.</span>
|
||||
|
||||
<p>_(Note)_: _(While there is some risk, if it is not possible to first mount the filesystem to clear the log, zeroing it is the only option to try and repair the filesystem, and in most cases it results in little or no data loss)_.</p>
|
||||
|
||||
:info_xfs_check_help:
|
||||
|
||||
<?elseif ($retval == 4):?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/xfs_check">
|
||||
<input type="hidden" name="#arg[1]" value="start">
|
||||
<input type="hidden" name="#arg[2]" value="/dev/<?=_var($disk,'deviceSb')?>">
|
||||
<input type="hidden" name="#arg[3]" value="<?=_var($disk,'id')?>">
|
||||
<input type="hidden" name="#arg[4]" value="-n">
|
||||
|
||||
|
||||
: <input type="submit" value="_(Check)_">_(File system corruption fixed)_
|
||||
|
||||
:info_xfs_check_help:
|
||||
|
||||
<?elseif ($retval == 9):?>
|
||||
<input type="hidden" name="#command" value="/webGui/scripts/xfs_check">
|
||||
<input type="hidden" name="#arg[1]" value="cancel">
|
||||
<input type="hidden" name="#arg[2]" value="/dev/<?=_var($disk,'deviceSb')?>">
|
||||
@@ -1289,19 +1417,13 @@ _(SMART attribute notifications)_:
|
||||
<form markdown="1" method="POST" action="/update.htm" target="progressFrame" onsubmit="return validate(this.poolName.value)">
|
||||
<input type="hidden" name="poolNameOrig" value="<?=$name?>">
|
||||
<input type="hidden" name="changeSlots" value="apply">
|
||||
<p>_(Caution)_: _(Renaming the pool will change the share storage allocations)_. _(After renaming the pool, check that your shares are assigned to the proper primary and secondary storage locations)_.</p>
|
||||
_(Name)_:
|
||||
: <input type="text" name="poolName" maxlength="40" value="<?=$name?>">
|
||||
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<form name="deletepool" method="POST" action="/update.htm" target="progressFrame" style="display:none">
|
||||
<input type="hidden" name="changeSlots" value="apply">
|
||||
<input type="hidden" name="poolName" value="<?=$name?>">
|
||||
<input type="hidden" name="poolSlots" value="0">
|
||||
<input type='hidden' name='csrf_token' value='<?=_var($var,'csrf_token')?>'>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
<?if (count($sheets)>1):?>
|
||||
|
@@ -154,16 +154,16 @@ _(Enable spinup groups)_:
|
||||
|
||||
:disk_spinup_groups_help:
|
||||
|
||||
_(Default file system)_:
|
||||
_(Default file system for Array disks)_:
|
||||
: <select name="defaultFsType">
|
||||
<?=mk_option($var['defaultFsType'], "xfs", _('xfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "zfs", _('zfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "btrfs", _('btrfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "reiserfs", _('reiserfs'));?>
|
||||
<?=mk_option($var['defaultFsType'], "reiserfs", _('reiserfs'), "disabled");?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:xfs", _('xfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:zfs", _('zfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:btrfs", _('btrfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:reiserfs", _('reiserfs')." - "._('encrypted'));?>
|
||||
<?=mk_option($var['defaultFsType'], "luks:reiserfs", _('reiserfs')." - "._('encrypted'), "disabled");?>
|
||||
</select>
|
||||
|
||||
:disk_default_file_system_help:
|
||||
|
@@ -315,6 +315,14 @@ _(Show banner background color fade)_:
|
||||
</select>
|
||||
</div>
|
||||
|
||||
_(Favorites enabled)_:
|
||||
: <select name="favorites">
|
||||
<?=mk_option($display['favorites'], "yes",_('Yes'))?>
|
||||
<?=mk_option($display['favorites'], "no",_('No'))?>
|
||||
</select>
|
||||
|
||||
:display_favorites_enabled_help:
|
||||
|
||||
<input type="submit" name="#default" value="_(Default)_" onclick="filename='reset'">
|
||||
: <input type="submit" name="#apply" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
|
||||
</form>
|
||||
|
@@ -60,8 +60,8 @@ foreach ($ports as $ethX) {
|
||||
}
|
||||
}
|
||||
// enable interface only when VMs and Docker are stopped
|
||||
$service = exec("pgrep libvirt") ? _('VM manager') : '';
|
||||
$service .= exec("pgrep docker") ? ($service ? ' '._('and').' ' : '')._('Docker service') : '';
|
||||
$service = exec('pgrep --ns $$ libvirt') ? _('VM manager') : '';
|
||||
$service .= exec('pgrep --ns $$ docker') ? ($service ? ' '._('and').' ' : '')._('Docker service') : '';
|
||||
|
||||
// eth0 port status
|
||||
$no_eth0 = exec("ip link show eth0|grep -Pom1 '(NO-CARRIER|state DOWN)'");
|
||||
|
@@ -32,22 +32,24 @@ _(Server name)_:
|
||||
:id_server_name_help:
|
||||
|
||||
_(Description)_:
|
||||
: <input type="text" name="COMMENT" value="<?=htmlspecialchars(_var($var,'COMMENT'))?>" <?=$disabled?>>
|
||||
: <input type="text" name="COMMENT" id="COMMENT" value="<?=htmlspecialchars(_var($var,'COMMENT'))?>" <?=$disabled?>>
|
||||
|
||||
:id_description_help:
|
||||
|
||||
_(Model)_:
|
||||
: <input type="text" name="SYS_MODEL" value="<?=htmlspecialchars(_var($var,'SYS_MODEL'))?>" <?=$disabled?>>
|
||||
: <input type="text" name="SYS_MODEL" id="SYS_MODEL" value="<?=htmlspecialchars(_var($var,'SYS_MODEL'))?>" <?=$disabled?>>
|
||||
|
||||
:id_model_help:
|
||||
|
||||
|
||||
: <input type="submit" name="changeNames" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()"><?if ($disabled):?>*_(Array must be **Stopped** to change)_*<?endif;?>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
$("#NAME").keypress(function(event) {
|
||||
return event.key.match(/[A-Za-z0-9\-\.]/)!==null;
|
||||
});
|
||||
|
||||
$("#NAME").on("input change", function() {
|
||||
if ($(this).val().match(/<?=$name_regex;?>/) === null) {
|
||||
$('#name_warning').fadeIn('fast');
|
||||
@@ -55,4 +57,13 @@ $("#NAME").on("input change", function() {
|
||||
$('#name_warning').fadeOut('fast');
|
||||
}
|
||||
});
|
||||
|
||||
/* Sanitize the COMMENT and SYS_MODEL fields on form submission */
|
||||
document.forms['NameSettings'].addEventListener('submit', function(event) {
|
||||
const commentField = document.getElementById('COMMENT');
|
||||
commentField.value = commentField.value.replace(/["\\]/g, '');
|
||||
|
||||
const sysModelField = document.getElementById('SYS_MODEL');
|
||||
sysModelField.value = sysModelField.value.replace(/["\\]/g, '');
|
||||
});
|
||||
</script>
|
||||
|
@@ -117,51 +117,122 @@ if ($cert2Present) {
|
||||
}
|
||||
}
|
||||
|
||||
// Tailscale LE cert
|
||||
$cert3File = "/boot/config/ssl/certs/ts_bundle.pem";
|
||||
$cert3Present = file_exists("$cert3File");
|
||||
if ($cert3Present) {
|
||||
$cert3Subject = exec("/usr/bin/openssl x509 -in $cert3File -noout -subject -nameopt multiline 2>/dev/null|sed -n 's/ *commonName *= //p'");
|
||||
$cert3Issuer = exec("/usr/bin/openssl x509 -in $cert3File -noout -text | sed -n -e 's/^.*Issuer: //p'");
|
||||
$cert3Expires = exec("/usr/bin/openssl x509 -in $cert3File -noout -text | sed -n -e 's/^.*Not After : //p'");
|
||||
}
|
||||
|
||||
// Note: this disables FQDN6 urls since they are not supported by myunraid.net DNS currently
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) unset($nginx['NGINX_LANFQDN6']);
|
||||
|
||||
$http_port = _var($var,'PORT','80') != '80' ? ":{$var['PORT']}" : '';
|
||||
$https_port = _var($var,'PORTSSL','443') != '443' ? ":{$var['PORTSSL']}" : '';
|
||||
$http_ip_url = "http://"._var($nginx,'NGINX_LANIP')."{$http_port}/";
|
||||
$https_ip_url = "https://"._var($nginx,'NGINX_LANIP')."{$https_port}/";
|
||||
$http_ip6_url = "http://["._var($nginx,'NGINX_LANIP6')."]{$http_port}/";
|
||||
$https_ip6_url = "https://["._var($nginx,'NGINX_LANIP6')."]{$https_port}/";
|
||||
$http_mdns_url = "http://"._var($nginx,'NGINX_LANMDNS')."{$http_port}/";
|
||||
$https_mdns_url = "https://"._var($nginx,'NGINX_LANMDNS')."{$https_port}/";
|
||||
$https_fqdn_url = "https://"._var($nginx,'NGINX_LANFQDN')."{$https_port}/";
|
||||
$https_fqdn6_url = "https://"._var($nginx,'NGINX_LANFQDN6')."{$https_port}/";
|
||||
$http_ip_url = 'http://'._var($nginx,'NGINX_LANIP').$http_port.'/';
|
||||
$https_ip_url = 'https://'._var($nginx,'NGINX_LANIP').$https_port.'/';
|
||||
// bare IPv6 addresses need to be surrounded in brackets
|
||||
$http_ip6_url = 'http://'.'['._var($nginx,'NGINX_LANIP6').']'.$http_port.'/';
|
||||
$https_ip6_url = 'https://'.'['._var($nginx,'NGINX_LANIP6').']'.$https_port.'/';
|
||||
$http_mdns_url = 'http://'._var($nginx,'NGINX_LANMDNS').$http_port.'/';
|
||||
$https_mdns_url = 'https://'._var($nginx,'NGINX_LANMDNS').$https_port.'/';
|
||||
$https_fqdn_url = 'https://'._var($nginx,'NGINX_LANFQDN').$https_port.'/';
|
||||
$https_fqdn6_url = 'https://'._var($nginx,'NGINX_LANFQDN6').$https_port.'/';
|
||||
|
||||
$urls = [];
|
||||
// push an array of four values into the $urls array:
|
||||
// 0 - the url
|
||||
// 1 - the url it redirects to, or null
|
||||
// 2 - the certificate file used, or null
|
||||
// 3 - self-signed certificate, or false
|
||||
// push an array of five values into the $urls array:
|
||||
// 0 - type of url ['LAN','WAN','WG','TAILSCALE']
|
||||
// 1 - the url
|
||||
// 3 - the url it redirects to, or null
|
||||
// 4 - the certificate file used, or null
|
||||
// 5 - self-signed certificate, or false
|
||||
|
||||
// define LAN access urls and redirects that change based on USE_SSL setting
|
||||
switch(_var($var,'USE_SSL','no')) {
|
||||
case 'no':
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$http_ip_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$http_ip6_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$http_mdns_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANFQDN'])) $urls[] = [$https_fqdn_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) $urls[] = [$https_fqdn6_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $http_ip_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $http_ip6_url, null, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $http_mdns_url, null, null, false];
|
||||
break;
|
||||
case 'yes':
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$http_ip_url, $https_ip_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$https_ip_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$http_ip6_url, $https_ip6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$https_ip6_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$http_mdns_url, $https_mdns_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$https_mdns_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANFQDN'])) $urls[] = [$https_fqdn_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) $urls[] = [$https_fqdn6_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $http_ip_url, $https_ip_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = ['LAN', $https_ip_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $http_ip6_url, $https_ip6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = ['LAN', $https_ip6_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $http_mdns_url, $https_mdns_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = ['LAN', $https_mdns_url, null, "{$var['NAME']}_unraid_bundle.pem", $cert1SelfSigned];
|
||||
break;
|
||||
case 'auto': // aka strict
|
||||
if (!empty($nginx['NGINX_LANIP'])) $urls[] = [$http_ip_url, $https_fqdn_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6'])) $urls[] = [$http_ip6_url, $https_fqdn6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS'])) $urls[] = [$http_mdns_url, $https_fqdn_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANFQDN'])) $urls[] = [$https_fqdn_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANFQDN6'])) $urls[] = [$https_fqdn6_url, null, "certificate_bundle.pem", false];
|
||||
if (!empty($nginx['NGINX_LANIP']) && !empty($nginx['NGINX_LANFQDN'])) $urls[] = ['LAN', $http_ip_url, $https_fqdn_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANIP6']) && !empty($nginx['NGINX_LANFQDN6'])) $urls[] = ['LAN', $http_ip6_url, $https_fqdn6_url, null, false];
|
||||
if (!empty($nginx['NGINX_LANMDNS']) && !empty($nginx['NGINX_LANFQDN'])) $urls[] = ['LAN', $http_mdns_url, $https_fqdn_url, null, false];
|
||||
break;
|
||||
}
|
||||
|
||||
// define FQDN urls for each interface
|
||||
// when multiple FQDN urls are available for a given interface, make sure they are sorted
|
||||
asort($nginx);
|
||||
foreach ($nginx as $key => $host) {
|
||||
if (!$host) continue;
|
||||
// Only process keys that include 'FQDN'
|
||||
if (strpos($key, 'FQDN') === false) continue;
|
||||
// Extract the interface from the key, e.g., 'NGINX_LANFQDN' -> 'LAN', 'NGINX_WANFQDN' -> 'WAN', NGINX_WG0FQDN -> WG, NGINX_TAILSCALE1FQDN -> TAILSCALE
|
||||
// Note: this specifically excludes FQDN6 urls since they are not supported by myunraid.net DNS currently
|
||||
if (preg_match('/^NGINX_([A-Z]+)(\d*)FQDN$/', $key, $matches)) {
|
||||
$interface = $matches[1]; // Interface type (LAN, WAN, WG, TAILSCALE, etc.)
|
||||
// ignore the WAN interface because we don't have access to the WANPORT here
|
||||
if ($interface == "WAN") continue;
|
||||
$pem = null;
|
||||
if (str_ends_with($host, '.myunraid.net')) $pem = 'certificate_bundle.pem';
|
||||
elseif (str_ends_with($host, '.ts.net')) $pem = 'ts_bundle.pem';
|
||||
$url = 'https://'.$host.$https_port."/";
|
||||
$urls[] = [$interface, $url, null, $pem, false];
|
||||
}
|
||||
}
|
||||
|
||||
// determine whether there are urls for a given interface
|
||||
function has_urls($interface) {
|
||||
global $urls;
|
||||
foreach($urls as $url) {
|
||||
if ($url[0] == $interface) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// show all urls for a given interface
|
||||
function show_urls($interface) {
|
||||
global $urls;
|
||||
// 0 - type of url ['LAN','WAN','WG','TAILSCALE']
|
||||
// 1 - the url
|
||||
// 3 - the url it redirects to, or null
|
||||
// 4 - the certificate file used, or null
|
||||
// 5 - self-signed certificate, or false
|
||||
$output = "";
|
||||
$linestart = "<dt> </dt><dd>";
|
||||
$lineend = "</dd>\n";
|
||||
$first = true;
|
||||
foreach($urls as $url) {
|
||||
if ($url[0] == $interface) {
|
||||
$msg = "<a href='$url[1]'>$url[1]</a>";
|
||||
if ($url[2]) $msg .= " "._("redirects to")." <a href='$url[2]'>$url[2]</a>";
|
||||
if ($url[3]) $msg .= " "._("uses")." ".$url[3];
|
||||
if ($url[4]) $msg .= "<span class='warning'> <i class='fa fa-warning fa-fw'></i> "._("is a self-signed certificate, ignore the browser's warning and proceed to the GUI")."</span>";
|
||||
// 2nd+ urls need leading $linestart
|
||||
$output .= ($first ? "" : $linestart).$msg.$lineend;
|
||||
$first = false;
|
||||
}
|
||||
}
|
||||
if ($first) {
|
||||
$output = "none";
|
||||
} else {
|
||||
// strip final trailing $lineend as it will be added by markdown
|
||||
$output = substr($output, 0, strlen($lineend)*-1);
|
||||
}
|
||||
echo $output;
|
||||
}
|
||||
|
||||
$cert_time_format = $display['date'].($display['date']!='%c' ? ', '.str_replace(['%M','%R'],['%M:%S','%R:%S'],$display['time']):'');
|
||||
$provisionlabel = $isWildcardCert ? _('Renew') : _('Provision');
|
||||
$disabled_provision = $keyfile===false || ($isWildcardCert && $retval_expired===0) || !$addr ? 'disabled' : '';
|
||||
@@ -334,24 +405,34 @@ _(Local TLD)_:
|
||||
|
||||
</form>
|
||||
|
||||
<hr>
|
||||
|
||||
_(Local access URLs)_:
|
||||
: <?
|
||||
// url[0] = url
|
||||
// url[1] = redirect url or null
|
||||
// url[2] = certificate used or null
|
||||
// url[3] = is certificate self-signed T/F
|
||||
$n = 0;
|
||||
foreach($urls as $url) {
|
||||
$msg = "";
|
||||
if ($url[1]) $msg .= " "._("redirects to")." <a href='$url[1]'>$url[1]</a>";
|
||||
if ($url[2]) $msg .= " "._("uses")." ".$url[2];
|
||||
if ($url[3]) $msg .= "<span class='warning'> <i class='fa fa-warning fa-fw'></i> "._("is a self-signed certificate, ignore the browser's warning and proceed to the GUI")."</span>";
|
||||
echo ($n ? "<dt> </dt><dd>" : ""),"<a href='$url[0]'>$url[0]</a>$msg",($n++ ? "</dd>" : "");
|
||||
}?>
|
||||
: <? show_urls('LAN'); ?>
|
||||
|
||||
:mgmt_local_access_urls_help:
|
||||
|
||||
<?if (has_urls('WG')): ?>
|
||||
|
||||
_(WireGuard URLs)_:
|
||||
: <? show_urls('WG'); ?>
|
||||
|
||||
:mgmt_wg_access_urls_help:
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if (has_urls('TAILSCALE')): ?>
|
||||
|
||||
_(Tailscale URLs)_:
|
||||
: <? show_urls('TAILSCALE'); ?>
|
||||
|
||||
:mgmt_tailscale_access_urls_help:
|
||||
|
||||
<?endif;?>
|
||||
|
||||
<?if ($cert1Present):?>
|
||||
<hr>
|
||||
|
||||
_(Self-signed or user-provided certificate)_:
|
||||
: <?=$cert1File?>
|
||||
|
||||
@@ -386,6 +467,9 @@ _(Self-signed certificate file)_:
|
||||
<input type="hidden" name="server_name" value="<?=strtok(_var($_SERVER,'HTTP_HOST'),":")?>">
|
||||
<input type="hidden" name="server_addr" value="<?=_var($_SERVER,'SERVER_ADDR')?>">
|
||||
<?if ($cert2Present):?>
|
||||
|
||||
<hr>
|
||||
|
||||
_(Unraid Let's Encrypt certificate)_:
|
||||
: <?=$cert2File?>
|
||||
|
||||
@@ -415,6 +499,31 @@ _(CA-signed certificate file)_:
|
||||
|
||||
: <button type="submit" name="changePorts" value="Provision" <?=$disabled_provision?>><?=$provisionlabel?></button><button type="submit" name="changePorts" value="Delete" <?=$disabled_delete?> >_(Delete)_</button><?=$disabled_provision_msg?>
|
||||
|
||||
|
||||
<?if ($cert3Present):?>
|
||||
|
||||
<hr>
|
||||
|
||||
_(Tailscale Let's Encrypt certificate)_:
|
||||
: <?=$cert3File?>
|
||||
|
||||
_(Certificate URL)_:
|
||||
: <?="<a href='https://$cert3Subject$https_port'>$cert3Subject</a>"?>
|
||||
|
||||
_(Certificate issuer)_:
|
||||
: <?=$cert3Issuer?>
|
||||
|
||||
_(Certificate expiration)_:
|
||||
: <?=_(my_date($cert_time_format, strtotime($cert3Expires)),0)?>
|
||||
|
||||
<?endif;?>
|
||||
|
||||
:mgmt_certificate_expiration_help:
|
||||
|
||||
</form>
|
||||
|
||||
<?if (has_urls('WG')): ?>
|
||||
|
||||
<small>"WireGuard" and the "WireGuard" logo are registered trademarks of Jason A. Donenfeld</small>
|
||||
|
||||
<?endif;?>
|
||||
|
@@ -91,69 +91,78 @@ function initDropdown() {
|
||||
</form>
|
||||
<?
|
||||
$fields = ['Event','Subject','Timestamp','Description','Importance','Content','Link'];
|
||||
$xml_file = "webGui/include/NotificationAgents.xml";
|
||||
$xml = @simplexml_load_file($xml_file) or die(_("Failed to open")." $xml_file");
|
||||
$xml_files = glob("/usr/local/emhttp/plugins/dynamix/agents/*.xml");
|
||||
$i = 1;
|
||||
foreach ($xml->Agent as $agent) {
|
||||
$name = str_replace(' ','_',$agent->Name);
|
||||
$enabledAgent = agent_fullname("$name.sh", "enabled");
|
||||
$disabledAgent = agent_fullname("$name.sh", "disabled");
|
||||
if (is_file($disabledAgent)) {
|
||||
$file = $disabledAgent;
|
||||
if (is_file($enabledAgent)) unlink($enabledAgent);
|
||||
} else {
|
||||
$file = $enabledAgent;
|
||||
}
|
||||
$values = [];
|
||||
$script = "";
|
||||
if (is_file($file)) {
|
||||
preg_match("/[#]{6,100}([^#]*?)[#]{6,100}/si", file_get_contents($file), $match);
|
||||
if (isset($match[1])) {
|
||||
foreach (explode(PHP_EOL, $match[1]) as $line) {
|
||||
if (strpos($line, "=")) {
|
||||
[$k, $v] = my_explode("=",str_replace("\"", "", $line),2);
|
||||
$values[$k] = $v;
|
||||
foreach ($xml_files as $xml_file) {
|
||||
$xml = @simplexml_load_file($xml_file);
|
||||
if ( ! $xml ) continue;
|
||||
|
||||
if ( isset($xml->Language) ) {
|
||||
$guiLanguage = ($locale == "" ) ? "en_US" : $locale;
|
||||
$acceptedLanguages = explode(" ",$xml->Language);
|
||||
if ( ! in_array($guiLanguage,$acceptedLanguages) )
|
||||
continue;
|
||||
}
|
||||
$name = str_replace(' ','_',$xml->Name);
|
||||
$enabledAgent = agent_fullname("$name.sh", "enabled");
|
||||
$disabledAgent = agent_fullname("$name.sh", "disabled");
|
||||
if (is_file($disabledAgent)) {
|
||||
$file = $disabledAgent;
|
||||
if (is_file($enabledAgent)) unlink($enabledAgent);
|
||||
} else {
|
||||
$file = $enabledAgent;
|
||||
}
|
||||
$values = [];
|
||||
$script = "";
|
||||
if (is_file($file)) {
|
||||
preg_match("/[#]{6,100}([^#]*?)[#]{6,100}/si", file_get_contents($file), $match);
|
||||
if (isset($match[1])) {
|
||||
foreach (explode(PHP_EOL, $match[1]) as $line) {
|
||||
if (strpos($line, "=")) {
|
||||
[$k, $v] = my_explode("=",str_replace("\"", "", $line),2);
|
||||
$values[$k] = $v;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
foreach (explode(PHP_EOL,(String) $agent->Script) as $line) if (trim($line)) $script .= trim($line)."{1}";
|
||||
echo '<div class="title shift"><span class="left"><img src="/plugins/dynamix/icons/'.strtolower(str_replace('_','',$name)).'.png" class="icon" style="height:16px;width:16px;">'.str_replace('_',' ',$name).'</span><span class="status vhshift">'.(is_file($enabledAgent) ? '<span class="green">'._("Enabled").'</span>' : '<span class="red">'._("Disabled").'</span>').'</span></div>';
|
||||
echo '<form method="POST" name="'.$name.'" action="/update.php" target="progressFrame">';
|
||||
echo '<input type="hidden" name="#include" value="/webGui/include/update.file.php">';
|
||||
echo '<input type="hidden" name="#file" value="'.$file.'">';
|
||||
echo '<input type="hidden" name="#command" value="/webGui/scripts/agent">';
|
||||
echo '<input type="hidden" name="#arg[1]" value="">';
|
||||
echo '<input type="hidden" name="#arg[2]" value="">';
|
||||
echo '<input type="hidden" name="text" value="">';
|
||||
echo '<dl><dt>'._("Agent function").':</dt><dd><select name="Enabled">';
|
||||
echo mk_option(is_file($disabledAgent), 'no', _('Disabled'));
|
||||
echo mk_option(is_file($enabledAgent), 'yes', _('Enabled'));
|
||||
echo '</select></dd></dl>';
|
||||
echo '<script>scripts["'.$name.'"]='.json_encode($script).';enabledAgents["'.$name.'"]="'.$enabledAgent.'";disabledAgents["'.$name.'"]="'.$disabledAgent.'";</script>';
|
||||
foreach ($agent->Variables->children() as $v) {
|
||||
$vName = preg_replace('#\[([^\]]*)\]#', '<$1>', (string) $v);
|
||||
$vDesc = ucfirst(strtolower(preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Desc)));
|
||||
$vDefault = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Default);
|
||||
$vHelp = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Help);
|
||||
echo "<dl><dt>$vDesc:</dt><dd>";
|
||||
if (preg_match('/title|message/', $vDesc)) {
|
||||
echo '<select id="slot_'.$i++.'" name="'.$vName.'" multiple style="display:none">';
|
||||
$value = str_replace('\n',',',isset($values[$vName]) ? $values[$vName] : $vDefault);
|
||||
foreach ($fields as $field) echo mk_option_check($value,'$'.strtoupper($field),_($field));
|
||||
echo '</select>';
|
||||
} else {
|
||||
echo '<input type="text" name="'.$vName.'" class="variable" required value="'.( isset($values[$vName]) ? $values[$vName] : $vDefault ).'">';
|
||||
foreach (explode(PHP_EOL,(String) $xml->Script) as $line) if (trim($line)) $script .= trim($line)."{1}";
|
||||
echo '<div class="title shift"><span class="left"><img src="/plugins/dynamix/icons/'.strtolower(str_replace('_','',$name)).'.png" class="icon" style="height:16px;width:16px;">'.str_replace('_',' ',$name).'</span><span class="status vhshift">'.(is_file($enabledAgent) ? '<span class="green">'._("Enabled").'</span>' : '<span class="red">'._("Disabled").'</span>').'</span></div>';
|
||||
echo '<form method="POST" name="'.$name.'" action="/update.php" target="progressFrame">';
|
||||
echo '<input type="hidden" name="#include" value="/webGui/include/update.file.php">';
|
||||
echo '<input type="hidden" name="#file" value="'.$file.'">';
|
||||
echo '<input type="hidden" name="#command" value="/webGui/scripts/agent">';
|
||||
echo '<input type="hidden" name="#arg[1]" value="">';
|
||||
echo '<input type="hidden" name="#arg[2]" value="">';
|
||||
echo '<input type="hidden" name="text" value="">';
|
||||
echo '<dl><dt>'._("Agent function").':</dt><dd><select name="Enabled">';
|
||||
echo mk_option(is_file($disabledAgent), 'no', _('Disabled'));
|
||||
echo mk_option(is_file($enabledAgent), 'yes', _('Enabled'));
|
||||
echo '</select></dd></dl>';
|
||||
echo '<script>scripts["'.$name.'"]='.json_encode($script).';enabledAgents["'.$name.'"]="'.$enabledAgent.'";disabledAgents["'.$name.'"]="'.$disabledAgent.'";</script>';
|
||||
foreach ($xml->Variables->children() as $v) {
|
||||
$vName = preg_replace('#\[([^\]]*)\]#', '<$1>', (string) $v);
|
||||
$vDesc = ucfirst(strtolower(preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Desc)));
|
||||
$vDefault = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Default);
|
||||
$vHelp = preg_replace('#\[([^\]]*)\]#', '<$1>', $v->attributes()->Help);
|
||||
echo "<dl><dt>$vDesc:</dt><dd>";
|
||||
if (preg_match('/title|message/', $vDesc)) {
|
||||
echo '<select id="slot_'.$i++.'" name="'.$vName.'" multiple style="display:none">';
|
||||
$value = str_replace('\n',',',isset($values[$vName]) ? $values[$vName] : $vDefault);
|
||||
foreach ($fields as $field) echo mk_option_check($value,'$'.strtoupper($field),_($field));
|
||||
echo '</select>';
|
||||
} else {
|
||||
echo '<input type="text" name="'.$vName.'" class="variable" required value="'.( isset($values[$vName]) ? $values[$vName] : $vDefault ).'">';
|
||||
}
|
||||
echo '</dd></dl>';
|
||||
if ($vHelp) echo '<blockquote class="inline_help">'.$vHelp.'</blockquote>';
|
||||
}
|
||||
echo '</dd></dl>';
|
||||
if ($vHelp) echo '<blockquote class="inline_help">'.$vHelp.'</blockquote>';
|
||||
}
|
||||
echo '<dl><dt> </dt><dd><input type="submit" value='._("Apply").' onclick="prepareService(this, \''.$name.'\')" disabled>';
|
||||
echo '<input type="button" value='._("Done").' onclick="done()">';
|
||||
if (is_file($file)) {
|
||||
echo '<input type="button" value='._("Delete").' onclick="execCmd(\'delete\',\''.$name.'\')">';
|
||||
echo '<input type="button" value='._("Test").' onclick="testService(\''.$name.'\')"'.($file==$enabledAgent ? '>' : ' disabled>');
|
||||
}
|
||||
echo '</dd></dl></form><div style="min-height:50px;"></div>';
|
||||
echo '<dl><dt> </dt><dd><input type="submit" value='._("Apply").' onclick="prepareService(this, \''.$name.'\')" disabled>';
|
||||
echo '<input type="button" value='._("Done").' onclick="done()">';
|
||||
if (is_file($file)) {
|
||||
echo '<input type="button" value='._("Delete").' onclick="execCmd(\'delete\',\''.$name.'\')">';
|
||||
echo '<input type="button" value='._("Test").' onclick="testService(\''.$name.'\')"'.($file==$enabledAgent ? '>' : ' disabled>');
|
||||
}
|
||||
echo '</dd></dl></form><div style="min-height:50px;"></div>';
|
||||
|
||||
}
|
||||
?>
|
||||
|
@@ -88,7 +88,7 @@ _(Select Proxy)_:
|
||||
>
|
||||
> Outgoing connections from the webgui and some system processes will use the specified http proxy. Docker container installs and updates will use the proxy, but the container itself will not, neither will any VMs.
|
||||
>
|
||||
> For a more comprehensive solution you might consider setting up <u><a href='https://docs.unraid.net/unraid-os/manual/security/vpn/#configuring-vpn-tunneled-access-for-system/' target='_blank'>_(VPN tunnel access for System)_</a></u>.
|
||||
> For a more comprehensive solution you might consider setting up <u><a href='https://docs.unraid.net/go/configuring-vpn-tunneled-access-for-system/' target='_blank'>_(VPN tunnel access for System)_</a></u>.
|
||||
:end
|
||||
|
||||
<p><strong>_(Outgoing Proxy)_ 1</strong></p>
|
||||
@@ -307,5 +307,5 @@ _(Password)_:
|
||||
});
|
||||
|
||||
/* URL for Outgoing Proxy PHP file. */
|
||||
const OPMURL = '/plugins/<?=$opmPlugin;?>/OutgoingProxy.php';
|
||||
const OPMURL = '/plugins/<?=$opmPlugin;?>/include/OutgoingProxy.php';
|
||||
</script>
|
||||
|
@@ -28,7 +28,7 @@ if (!file_exists($log)) touch($log);
|
||||
:php_settings_plug:
|
||||
This utility is used for development purposes only and allows Plugin Authors to verify their PHP code by enabling different levels of PHP error reporting.
|
||||
|
||||
By default error logging is minimum and any errors are shown on screen. Changing the **Error reporting level** will capture the selected level of errors
|
||||
By default error logging is minimum and errors are not shown on screen. Changing the **Error reporting level** will capture the selected level of errors
|
||||
into a LOG file, which can be opened in a separate window to monitor in real-time the events when visiting various GUI pages or executing background
|
||||
processes on the server.
|
||||
|
||||
@@ -46,12 +46,12 @@ under normal running conditions.
|
||||
<input type="hidden" name="log_errors" value="1">
|
||||
_(Error reporting level)_:
|
||||
: <select name="error_reporting" onchange="toggleScreen(this.selectedIndex)">
|
||||
<?=mk_option(_var($conf,'error_reporting'), "", "_(Default)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "32767", "_(All Categories)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "1", "_(Errors Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "2", "_(Warnings Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "8", "_(Notices Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), "8192", "_(Deprecated Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_ALL & ~E_NOTICE & ~E_WARNING & ~E_DEPRECATED), "_(Default)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_ALL), "_(All Categories)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_ERROR), "_(Errors Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_WARNING), "_(Warnings Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_NOTICE), "_(Notices Only)_");?>
|
||||
<?=mk_option(_var($conf,'error_reporting'), strval(E_DEPRECATED), "_(Deprecated Only)_");?>
|
||||
</select>
|
||||
|
||||
|
||||
@@ -147,11 +147,12 @@ function PHPinfo() {
|
||||
}
|
||||
function preset(form) {
|
||||
// reset to default settings
|
||||
// derived from default .ini file installed at boot
|
||||
if (form.error_reporting.selectedIndex==0) {
|
||||
form.error_log.value = "";
|
||||
form.display_startup_errors.value = "";
|
||||
form.display_errors.value = "";
|
||||
form.log_errors.value = "";
|
||||
form.error_log.value = "<?=$log?>";
|
||||
form.display_startup_errors.value = "0";
|
||||
form.display_errors.value = "0";
|
||||
form.log_errors.value = "1";
|
||||
}
|
||||
$.cookie('reload_php',1);
|
||||
}
|
||||
|
@@ -86,11 +86,11 @@ _(Enter route + gateway + metric)_:
|
||||
<input type="text" name="gateway" class="fixed" value="" list="device" placeholder="_(gateway name or address)_" required>
|
||||
<datalist id="device"><?foreach ($list as $port):?><?echo "<option value='$port'>"?><?endforeach;?></datalist>
|
||||
<input type="text" name="metric" min="1" max="9999" value="" class="trim" placeholder="1"><i class="fa fa-sort-numeric-asc"></i> *_(optional metric (lowest is preferred))_*
|
||||
|
||||
<input type="hidden" name="task" value="Add Route">
|
||||
:eth_routing_table_help:
|
||||
|
||||
|
||||
: <input type="submit" name="task" value="_(Add Route)_"><input type="button" value="_(Done)_" class="lock" onclick="done()">
|
||||
: <input type="submit" value="_(Add Route)_"><input type="button" value="_(Done)_" class="lock" onclick="done()">
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -17,6 +17,10 @@ Cond="(($var['shareNFSEnabled']!='no') && (isset($name)?array_key_exists($name,$
|
||||
?>
|
||||
<?
|
||||
$width = [123,300];
|
||||
|
||||
/* Replace spaces in NFS rule with new lines for multi line textarea. */
|
||||
$sec_nfs[$name]['hostList'] = str_replace(" ", "\n", $sec_nfs[$name]['hostList']);
|
||||
|
||||
?>
|
||||
:nfs_security_help:
|
||||
|
||||
@@ -73,10 +77,10 @@ _(Security)_:
|
||||
</form>
|
||||
|
||||
<?if ($sec_nfs[$name]['security']=='private'):?>
|
||||
<form markdown="1" method="POST" name="otherForm" action="/update.htm" target="progressFrame">
|
||||
<form id="nfsHostListForm" markdown="1" method="POST" name="otherForm" action="/update.htm" target="progressFrame">
|
||||
<input type="hidden" name="shareName" value="<?=htmlspecialchars($name)?>">
|
||||
_(Rule)_:
|
||||
: <input type="text" name="shareHostListNFS" maxlength="512" value="<?=htmlspecialchars($sec_nfs[$name]['hostList'])?>">
|
||||
: <textarea name="shareHostListNFS" cols="40" rows="5" style="width:45%" placeholder="Example: *(rw,sec=sys,insecure,anongid=100,anonuid=99,no_root_squash,lock)"><?= htmlspecialchars($sec_nfs[$name]['hostList']) ?></textarea>
|
||||
|
||||
|
||||
: <input type="submit" name="changeShareAccessNFS" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
|
||||
@@ -85,58 +89,140 @@ _(Rule)_:
|
||||
|
||||
<script>
|
||||
$(function() {
|
||||
initDropdownNFS(false);
|
||||
if ($.cookie('hostList')!=null) {
|
||||
var host = $('input[name="shareHostListNFS"]');
|
||||
host.val($.cookie('hostList'));
|
||||
setTimeout(function(){host.trigger('change');},100);
|
||||
$.removeCookie('hostList');
|
||||
}
|
||||
<?if ($tabbed):?>
|
||||
<?$path=='Shares/Share' ? $t=2 : $t=1;?>
|
||||
$('#tab<?=$t?>').bind({click:function(){initDropdownNFS(true);}});
|
||||
<?endif;?>
|
||||
/* Initialize dropdown for NFS and check for hostList cookie. */
|
||||
initDropdownNFS(false);
|
||||
if ($.cookie('hostList') != null) {
|
||||
var host = $('input[name="shareHostListNFS"]');
|
||||
host.val($.cookie('hostList'));
|
||||
setTimeout(function() {
|
||||
host.trigger('change');
|
||||
}, 100);
|
||||
$.removeCookie('hostList');
|
||||
}
|
||||
|
||||
<?if ($tabbed):?>
|
||||
/* Conditionally bind click event to tabs if tabbed interface is used. */
|
||||
<?$path=='Shares/Share' ? $t=2 : $t=1;?>
|
||||
$('#tab<?=$t?>').bind({click:function() {
|
||||
initDropdownNFS(true);
|
||||
}});
|
||||
<?endif;?>
|
||||
});
|
||||
|
||||
/* Add an event listener to update the text area to make all rules into a single line before being submitted. */
|
||||
document.addEventListener("DOMContentLoaded", function() {
|
||||
var form = document.getElementById('nfsHostListForm');
|
||||
form.addEventListener('submit', function(event) {
|
||||
var textarea = document.querySelector('textarea[name="shareHostListNFS"]');
|
||||
|
||||
/* Split the content into lines. */
|
||||
var lines = textarea.value.split('\n');
|
||||
|
||||
/* Filter out empty lines or lines that contain only whitespace, and remove carriage returns and excessive spaces within lines. */
|
||||
var cleanedLines = lines.map(function(line) {
|
||||
/* Remove carriage returns and spaces within each line. */
|
||||
return line.replace(/[\r\s]+/g, '');
|
||||
}).filter(function(line) {
|
||||
/* Keep only non-empty lines. */
|
||||
return line.length > 0;
|
||||
});
|
||||
|
||||
/* Join the remaining lines with a single space. */
|
||||
textarea.value = cleanedLines.join(' ');
|
||||
});
|
||||
});
|
||||
|
||||
/* Function to initialize or reset the NFS dropdown */
|
||||
function initDropdownNFS(reset) {
|
||||
if (reset) {
|
||||
$('#nfs1').dropdownchecklist('destroy');
|
||||
}
|
||||
$("#nfs1").dropdownchecklist({firstItemChecksAll:true, emptyText:"_(select)_...", width:<?=$width[0]?>, explicitClose:"..._(close)_"});
|
||||
/* Check if reset is required and destroy existing dropdown if true */
|
||||
if (reset) {
|
||||
$('#nfs1').dropdownchecklist('destroy');
|
||||
}
|
||||
/* Initialize or re-initialize the dropdown with specified options */
|
||||
$("#nfs1").dropdownchecklist({
|
||||
firstItemChecksAll: true,
|
||||
emptyText: "_(select)_...",
|
||||
width: <?=$width[0]?>,
|
||||
explicitClose: "..._(close)_"
|
||||
});
|
||||
}
|
||||
|
||||
/* Function to read NFS configuration based on selected options and copy to this share. */
|
||||
function readNFS() {
|
||||
var form = document.nfs_edit;
|
||||
var name = $('select[name="readnfs"]').val();
|
||||
$.get('/webGui/include/ProtocolData.php',{protocol:'nfs',name:name},function(json) {
|
||||
var data = $.parseJSON(json);
|
||||
form.shareExportNFS.value = data.export;
|
||||
form.shareSecurityNFS.value = data.security;
|
||||
if (data.hostList != '') $.cookie('hostList',data.hostList);
|
||||
$(form).find('select').trigger('change');
|
||||
});
|
||||
/* Access the form for NFS editing */
|
||||
var form = document.nfs_edit;
|
||||
|
||||
/* Retrieve selected NFS name from the dropdown */
|
||||
var name = $('select[name="readnfs"]').val();
|
||||
|
||||
/* Perform a GET request to fetch NFS configuration data */
|
||||
$.get('/webGui/include/ProtocolData.php', {protocol: 'nfs', name: name}, function(json) {
|
||||
/* Parse the JSON response */
|
||||
var data = $.parseJSON(json);
|
||||
var textarea = $('textarea[name="shareHostListNFS"]');
|
||||
|
||||
/* Update form fields with fetched data */
|
||||
form.shareExportNFS.value = data.export;
|
||||
form.shareSecurityNFS.value = data.security;
|
||||
|
||||
/* Check if hostList is not empty and save it in a cookie */
|
||||
if (data.hostList != '') {
|
||||
$.cookie('hostList', data.hostList);
|
||||
}
|
||||
|
||||
/* Replace all spaces in data.hostList with new lines. */
|
||||
var formattedHostList = data.hostList.replace(/ /g, '\n');
|
||||
|
||||
/* Update textarea content. Use data from 'hostList'. */
|
||||
textarea.val(formattedHostList);
|
||||
|
||||
/* Trigger change event on select elements to update UI */
|
||||
$(form).find('select').trigger('change');
|
||||
|
||||
/* Trigger an input event as if the user had typed in the textarea. */
|
||||
textarea.trigger('input');
|
||||
});
|
||||
}
|
||||
function writeNFS(data,n,i) {
|
||||
if (data) {
|
||||
if (n<i) {
|
||||
$.post('/update.htm',data[n], function(){setTimeout(function(){writeNFS(data,++n,i);},3000);});
|
||||
} else {
|
||||
toggleButton('writenfs',false);
|
||||
$('div.spinner.fixed').hide();
|
||||
}
|
||||
} else {
|
||||
var data = [], i = 0;
|
||||
$('select#nfs1 option').map(function(i) {
|
||||
if ($(this).prop('selected')==true && $(this).val()!='(_(All)_)') {
|
||||
data[i] = {};
|
||||
data[i]['shareName'] = $(this).val();
|
||||
data[i]['shareExportNFS'] = '<?=addslashes(htmlspecialchars($sec_nfs[$name]['export']))?>';
|
||||
data[i]['shareSecurityNFS'] = '<?=addslashes(htmlspecialchars($sec_nfs[$name]['security']))?>';
|
||||
data[i]['changeShareSecurityNFS'] = 'Apply';
|
||||
i++;
|
||||
}
|
||||
});
|
||||
toggleButton('writenfs',true);
|
||||
$('div.spinner.fixed').show('slow');
|
||||
writeNFS(data,0,i);
|
||||
}
|
||||
|
||||
/* Function to write NFS settings based on user selection to other shares. */
|
||||
function writeNFS(data, n, i) {
|
||||
if (data) {
|
||||
if (n < i) {
|
||||
$.post('/update.htm', data[n], function() {
|
||||
setTimeout(function() { writeNFS(data, ++n, i); }, 3000);
|
||||
});
|
||||
} else {
|
||||
toggleButton('writenfs', false);
|
||||
$('div.spinner.fixed').hide();
|
||||
}
|
||||
} else {
|
||||
var data = [];
|
||||
|
||||
/* Get the setting from the share config. */
|
||||
var hostList = $('textarea[name="shareHostListNFS"]').val().trim();
|
||||
|
||||
/* Replace all new lines in data.hostList with spaces. */
|
||||
var formattedHostList = <?= json_encode($sec_nfs[$name]['hostList']); ?>.replace(/\n/g, ' ');
|
||||
|
||||
$('select#nfs1 option').each(function() {
|
||||
if ($(this).prop('selected') && $(this).val() != '(_(All)_)') {
|
||||
data.push({
|
||||
shareName: $(this).val(),
|
||||
shareExportNFS: '<?=addslashes(htmlspecialchars($sec_nfs[$name]['export']))?>',
|
||||
shareSecurityNFS: '<?=addslashes(htmlspecialchars($sec_nfs[$name]['security']))?>',
|
||||
changeShareSecurityNFS: 'Apply'
|
||||
});
|
||||
|
||||
data.push({
|
||||
shareName: $(this).val(),
|
||||
shareHostListNFS: formattedHostList,
|
||||
changeShareSecurityNFS: 'Apply'
|
||||
});
|
||||
}
|
||||
});
|
||||
toggleButton('writenfs', true);
|
||||
$('div.spinner.fixed').show('slow');
|
||||
writeNFS(data, 0, data.length);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
@@ -19,6 +19,8 @@ Cond="(($var['shareSMBEnabled']!='no') && (isset($name)?array_key_exists($name,$
|
||||
require_once "$docroot/webGui/include/InputSecurity.php";
|
||||
|
||||
$width = [123,300];
|
||||
/* Sort user array by keys in natural order */
|
||||
uksort($users, 'strnatcmp');
|
||||
?>
|
||||
:smb_security_help:
|
||||
|
||||
|
@@ -3,6 +3,13 @@ Type="xmenu"
|
||||
Tabs="false"
|
||||
Code="e924"
|
||||
---
|
||||
<?PHP
|
||||
if (!isset($display['favorites'])) {
|
||||
$favorites = true;
|
||||
} else {
|
||||
$favorites = $display['favorites'] == "yes" ? true : false;
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
function addPage(page) {
|
||||
$.post('/webGui/include/MyFavorites.php',{action:'add',page:page},function(){
|
||||
@@ -13,8 +20,8 @@ function addPage(page) {
|
||||
$(function(){
|
||||
$('div.Panel').each(function(){
|
||||
var page = $(this).find('a').prop('href').split('/').pop();
|
||||
$(this).find('span').append('<i class="fa fa-heart favo" title="_(Add to favorites)_" onclick="addPage("'+page+'");return false"></i>');
|
||||
$(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});
|
||||
if (<?=$favorites?>) $(this).find('span').append('<i class="fa fa-heart favo" title="_(Add to favorites)_" onclick="addPage("'+page+'");return false"></i>');
|
||||
if (<?=$favorites?>) $(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -15,6 +15,31 @@ Cond="_var($var,'fsState')!='Stopped' && _var($var,'shareUser')=='e'"
|
||||
* all copies or substantial portions of the Software.
|
||||
*/
|
||||
?>
|
||||
<?
|
||||
/* Function to filter out unwanted disks, check if any valid disks exist, and ignore disks with a blank device. */
|
||||
function checkDisks($disks) {
|
||||
global $pools;
|
||||
|
||||
$rc = false;
|
||||
|
||||
foreach ($disks as $disk) {
|
||||
/* Check the disk type, fsStatus, and ensure the device is not blank. */
|
||||
if (!in_array($disk['name'], ['flash', 'parity', 'parity2']) && strpos($disk['fsStatus'], 'Unmountable') === false && !empty($disk['device'])) {
|
||||
/* A valid disk with a non-blank device is found. */
|
||||
$rc = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
/* Are there any array disks? */
|
||||
$disks = parse_ini_file('state/disks.ini',true) ?? [];
|
||||
$nodisks = checkDisks($disks) ? "" : "disabled";
|
||||
?>
|
||||
|
||||
<table class="unraid share_status">
|
||||
<thead><tr><td>_(Name)_</td><td>_(Comment)_</td><td>_(SMB)_</td><td>_(NFS)_</td><td>_(Storage)_</td><td>_(Size)_</td><td>_(Free)_</td></tr></thead>
|
||||
<tbody id="shareslist"></tbody>
|
||||
@@ -22,7 +47,7 @@ Cond="_var($var,'fsState')!='Stopped' && _var($var,'shareUser')=='e'"
|
||||
|
||||
<form name="share_form" method="POST" action="<?=htmlspecialchars($path)?>/Share?name=">
|
||||
<input type="button" id="compute-shares" value="_(Compute All)_" onclick="$(this).prop('disabled',true);shareList('',-1)">
|
||||
<input type="submit" value="_(Add Share)_">
|
||||
<input type="submit" value="_(Add Share)_" <?echo $nodisks;?>>
|
||||
<input type="button" value="_(Clean Up)_" onclick="cleanup()" id="cleanup-button" disabled>
|
||||
</form>
|
||||
|
||||
|
@@ -110,9 +110,9 @@ _(Local syslog folder)_:
|
||||
|
||||
_(System identifier for logfile name)_:
|
||||
: <select name="server_filename">
|
||||
<?=mk_option($syslog['server_filename'], "syslog-%FROMHOST-IP%.log", _("IP Address"))?>
|
||||
<?=mk_option($syslog['server_filename'], "syslog-%HOSTNAME%.log", _("Hostname (from syslog message)"))?>
|
||||
<?=mk_option($syslog['server_filename'], "syslog-%FROMHOST%.log", _("Hostname (from DNS reverse lookup)"))?>
|
||||
<?=mk_option(_var($syslog, 'server_filename'), "syslog-%FROMHOST-IP%.log", _("IP Address"))?>
|
||||
<?=mk_option(_var($syslog, 'server_filename'), "syslog-%HOSTNAME%.log", _("Hostname (from syslog message)"))?>
|
||||
<?=mk_option(_var($syslog, 'server_filename'), "syslog-%FROMHOST%.log", _("Hostname (from DNS reverse lookup)"))?>
|
||||
</select>
|
||||
|
||||
:syslog_remote_system_identifier_help:
|
||||
|
@@ -3,6 +3,13 @@ Type="xmenu"
|
||||
Tabs="false"
|
||||
Code="e909"
|
||||
---
|
||||
<?PHP
|
||||
if (!isset($display['favorites'])) {
|
||||
$favorites = true;
|
||||
} else {
|
||||
$favorites = $display['favorites'] == "yes" ? true : false;
|
||||
}
|
||||
?>
|
||||
<script>
|
||||
function addPage(page) {
|
||||
$.post('/webGui/include/MyFavorites.php',{action:'add',page:page},function(){
|
||||
@@ -13,8 +20,8 @@ function addPage(page) {
|
||||
$(function(){
|
||||
$('div.Panel').each(function(){
|
||||
var page = $(this).find('a').prop('href').split('/').pop();
|
||||
$(this).find('span').append('<i class="fa fa-heart favo" title="_(Add to favorites)_" onclick="addPage("'+page+'");return false"></i>');
|
||||
$(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});
|
||||
if (<?=$favorites?>) {$(this).find('span').append('<i class="fa fa-heart favo" title="_(Add to favorites)_" onclick="addPage("'+page+'");return false"></i>');}
|
||||
if (<?=$favorites?>) { $(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
@@ -103,7 +103,7 @@ function checkKey(form) {
|
||||
// check syntax of ssh keys
|
||||
var rows = form.text.value.split('\n');
|
||||
for (var i=0,row; row=rows[i]; i++) {
|
||||
if (row.search(/^(ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/)==-1) {
|
||||
if (row.search(/^(ssh-dss AAAAB3NzaC1kc3|ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNT|sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb2|ssh-ed25519 AAAAC3NzaC1lZDI1NTE5|sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t|ssh-rsa AAAAB3NzaC1yc2)[0-9A-Za-z+/]+[=]{0,3}(\s.*)?$/)==-1) {
|
||||
swal({title:"_(Invalid Key)_",text:"["+(i+1)+"] "+row.split(' ')[0]+": _(Syntax of the key is incorrect)_!",type:"error",html:true,confirmButtonText:"_(Ok)_"});
|
||||
return false;
|
||||
}
|
||||
|
@@ -1032,14 +1032,13 @@ tstate['wg0'] = "<?=$check_wg0 ? 'active' : 'passive'?>";
|
||||
|
||||
var statistics = new NchanSubscriber('/sub/wireguard',{subscriber:'websocket'});
|
||||
statistics.on('message', function(data) {
|
||||
var list = [];
|
||||
var rows = data.split('\0');
|
||||
var list = [], n = [];
|
||||
var x = 0; var vtun = '';
|
||||
// get all existing tunnels
|
||||
$('div[id^="block-wg"]').each(function(){list.push($(this).prop('id').split('-')[1]);});
|
||||
// update active tunnels
|
||||
for (var i=0,row; row=rows[i]; i++) {
|
||||
var info = row.split(';');
|
||||
var rows = JSON.parse(data);
|
||||
for (var i=0,info; info=rows[i]; i++) {
|
||||
if (info[0] != vtun) {
|
||||
vtun = info[0];
|
||||
// remove tunnel from inactive list
|
||||
@@ -1281,7 +1280,7 @@ _(Local tunnel address IPv6)_:
|
||||
</div>
|
||||
</div>
|
||||
_(Local endpoint)_:
|
||||
: <span class="input"><input type="text" id="endpoint-wg0" name="Endpoint:0" class="subnet" value="<?=$vpn_wg0?'':_var($wg0,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wg0?'(_(not used)_)':preg_replace('/^(.+?\.)[0-9a-zA-Z]+(\.(my)?unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
: <span class="input"><input type="text" class="width:10%;" id="endpoint-wg0" name="Endpoint:0" class="subnet" value="<?=$vpn_wg0?'':_var($wg0,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wg0?'(_(not used)_)':preg_replace('/^(.+?\.)[0-9a-zA-Z]+(\.(my)?unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
<input type="number" name="gui:ListenPort:0" class="port" min="1" max="65535" value="<?=$vpn_wg0?'':_var($wg0,'ListenPort:0')?>" onchange="if(quickValidate(this)) {portRemark($(document.wg0),'wg0',this.value)}" placeholder="<?=$vpn_wg0?'':_var($netport,'wg0')?>"></span>
|
||||
<span class="remark block" style="display:none">_(Remark)_: _(configure your router with port forwarding of port)_ **<span id="my-port-wg0"><?=_var($wg0,'ListenPort:0')?:_var($netport,'wg0')?></span>/_(UDP)_** _(to)_ **<?=$server?>:<?=_var($wg0,'ListenPort:0')?:_var($netport,'wg0')?>**</span><span class="upnp wg0 block"></span>
|
||||
<input type="hidden" name="ListenPort:0" value=""><dl id="endpoint4-wg0" style="display:none"></dl><dl id="endpoint6-wg0" style="display:none"></dl>
|
||||
@@ -1318,7 +1317,7 @@ _(Local tunnel firewall)_:
|
||||
:wg_local_tunnel_firewall_help:
|
||||
|
||||
_(MTU size)_:
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wg0,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(automatic)_)">_(bytes)_</span>
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wg0,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(auto)_)">_(bytes)_</span>
|
||||
|
||||
:wg_mtu_size_help:
|
||||
|
||||
@@ -1362,7 +1361,7 @@ _(Peer type of access)_:
|
||||
<?=mk_option(_var($wg0,"TYPE:$i"), "7", _("VPN tunneled access for system"),count($peer_wg0)==1?'':'disabled')?>
|
||||
<?=mk_option(_var($wg0,"TYPE:$i"), "8", _("VPN tunneled access for docker"),count($peer_wg0)==1?'':'disabled')?>
|
||||
</select></span>
|
||||
<span id="access-type-<?=$i?>" class="access-type"></span>
|
||||
<span id="access-type-<?=$i?>"</span>
|
||||
|
||||
<?if ($i==1):?>
|
||||
> ?>)
|
||||
@@ -1426,8 +1425,8 @@ _(Persistent keepalive)_:
|
||||
:wg_persistent_keepalive_help:
|
||||
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wg0-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wg0-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wg0-<?=$i?>">_(unknown)_</span></span>
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wg0-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wg0-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wg0-<?=$i?>">_(unknown)_</span></span>
|
||||
<?endforeach;?>
|
||||
|
||||
|
||||
|
@@ -224,7 +224,7 @@ _(Local tunnel address IPv6)_:
|
||||
</div>
|
||||
</div>
|
||||
_(Local endpoint)_:
|
||||
: <span class="input"><input type="text" id="endpoint-wgX" name="Endpoint:0" class="subnet" value="<?=$vpn_wgX?'':_var($wgX,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wgX?'(_(not used)_)':preg_replace('/^(www\.).+(\.unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
: <span class="input"><input type="text" class="width:10%;" id="endpoint-wgX" name="Endpoint:0" class="subnet" value="<?=$vpn_wgX?'':_var($wgX,'Endpoint:0')?>" onchange="toLC(this);quickValidate(this);" pattern="<?=$validText?>" title="_(IP address or FQDN)_" placeholder="<?=$vpn_wgX?'(_(not used)_)':preg_replace('/^(www\.).+(\.unraid.net)$/','$1<hash>$2',$public)?>">:
|
||||
<input type="number" name="gui:ListenPort:0" class="port" min="1" max="65535" value="<?=$vpn_wgX?'':_var($wgX,'ListenPort:0')?>" onchange="if(quickValidate(this)) {portRemark($(document.wgX),'wgX',this.value)}" placeholder="<?=$vpn_wgX?'':_var($netport,'wgX')?>"></span>
|
||||
<span class="remark block" style="display:none">_(Remark)_: _(configure your router with port forwarding of port)_ **<span id="my-port-wgX"><?=_var($wgX,'ListenPort:0')?:_var($netport,'wgX')?></span>/_(UDP)_** _(to)_ **<?=$server?>:<?=_var($wgX,'ListenPort:0')?:_var($netport,'wgX')?>**</span><span class="upnp wgX block"></span>
|
||||
<input type="hidden" name="ListenPort:0" value=""><dl id="endpoint4-wgX" style="display:none"></dl><dl id="endpoint6-wgX" style="display:none"></dl>
|
||||
@@ -261,7 +261,7 @@ _(Local tunnel firewall)_:
|
||||
:wg_local_tunnel_firewall_help:
|
||||
|
||||
_(MTU size)_:
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wgX,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(automatic)_)">_(bytes)_</span>
|
||||
: <span class="input"><input type="number" name="MTU:0" class="trim" min="68" max="9198" value="<?=_var($wgX,'MTU:0')?>" onchange="quickValidate(this);" placeholder="(_(auto)_)">_(bytes)_</span>
|
||||
|
||||
:wg_mtu_size_help:
|
||||
|
||||
@@ -305,7 +305,7 @@ _(Peer type of access)_:
|
||||
<?=mk_option(_var($wgX,"TYPE:$i"), "7", _("VPN tunneled access for system"),count($peer_wgX)==1?'':'disabled')?>
|
||||
<?=mk_option(_var($wgX,"TYPE:$i"), "8", _("VPN tunneled access for docker"))?>
|
||||
</select></span>
|
||||
<span id="access-type-<?=$i?>" class="access-type"></span>
|
||||
<span id="access-type-<?=$i?>"></span>
|
||||
|
||||
<?if ($i==1):?>
|
||||
> ?>)
|
||||
@@ -369,8 +369,8 @@ _(Persistent keepalive)_:
|
||||
:wg_persistent_keepalive_help:
|
||||
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wgX-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wgX-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wgX-<?=$i?>">_(unknown)_</span></span>
|
||||
</div>
|
||||
<span class="pin">_(Data received)_: <span class="rx-wgX-<?=$i?>">0 B</span>_(Data sent)_: <span class="tx-wgX-<?=$i?>">0 B</span><br>_(Last handshake)_: <span class="hs-wgX-<?=$i?>">_(unknown)_</span></span>
|
||||
<?endforeach;?>
|
||||
|
||||
|
||||
|
29
emhttp/plugins/dynamix/agents/Bark.xml
Normal file
29
emhttp/plugins/dynamix/agents/Bark.xml
Normal file
@@ -0,0 +1,29 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Bark</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your push url from Bark APP. [a href='https://bark.day.app/#/tutorial' target='_blank'][u]tutorial[/u][/a]." Desc="PushUrl" Default="https://api.day.app/your_key">PUSHURL</Variable>
|
||||
<Variable Help="Optional. Specify the message group used for this push. [b]To disable this feature, specify 'none'.[/b]" Desc="Group" Default="Unraid">GROUP</Variable>
|
||||
<Variable Help="Optional. Specify the message sound, copy the sound name from Bark APP. [b]To disable this feature, specify 'none'.[/b]" Desc="Sound" Default="none">SOUND</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${GROUP}" && "${GROUP}" == "none" ]] && GROUP=""
|
||||
[[ -n "${SOUND}" && "${SOUND}" == "none" ]] && SOUND=""
|
||||
|
||||
curl -X "POST" "$PUSHURL" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d "{\"body\": \"$MESSAGE\", \"title\": \"$TITLE\", \"sound\": \"$SOUND\", \"group\": \"$GROUP\"}" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
|
27
emhttp/plugins/dynamix/agents/Boxcar.xml
Normal file
27
emhttp/plugins/dynamix/agents/Boxcar.xml
Normal file
@@ -0,0 +1,27 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Boxcar</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your access token as explained [a href='http://help.boxcar.io/knowledgebase/articles/314474-how-to-get-my-boxcar-access-token' target='_blank'][u]here[/u].[/a]" Desc="Access Token" Default="">ACCESS_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
curl -s -k \
|
||||
-d "user_credentials=$ACCESS_TOKEN" \
|
||||
-d "notification[title]=$TITLE" \
|
||||
-d "notification[long_message]=$MESSAGE" \
|
||||
-d "notification[source_name]=Unraid" \
|
||||
-d "notification[sound]=bird-1" \
|
||||
-d "notification[icon_url]=http://i.imgur.com/u63iSL1.png" \
|
||||
https://new.boxcar.io/api/notifications 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
199
emhttp/plugins/dynamix/agents/Discord.xml
Normal file
199
emhttp/plugins/dynamix/agents/Discord.xml
Normal file
@@ -0,0 +1,199 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Discord</Name>
|
||||
<Variables>
|
||||
<Variable Help="Add an '#unraid-notifications' channel to your personal Discord server, then get a WebHook URL as explained [a href='https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks' target='_blank'][u]here[/u].[/a] Note that multiple Unraid servers can use the same Webhook." Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Provide the https URL to an icon representing this Unraid server (using different icons for each server can help distinguish between them in the list of notifications.) To disable this feature, specify 'none'." Desc="Server Icon" Default="https://craftassets.unraid.net/uploads/logos/un-mark-gradient@2x.png">SERVER_ICON</Variable>
|
||||
<Variable Help="In Discord, right-click the '#unraid-notifications' channel and choose Notification Settings -> Only @mentions. Then to receive an @mention on 'alert' priority notifications only, provide your personal Discord ID (it is a series of numbers, not letters). To find your ID, in Discord type \@yourusername. To disable this feature, specify 'none'." Desc="Discord Tag ID" Default="none">DISCORD_TAG_ID</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
|
||||
############
|
||||
# Quick test with default values:
|
||||
# bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Quick test with values set through environment (all vars are optional)
|
||||
# EVENT="My Event" SUBJECT="My Subject" DESCRIPTION="My Description" CONTENT="My Message" IMPORTANCE="alert" LINK="/Dashboard" bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Full test of notification system (at least one param is required)
|
||||
# /usr/local/emhttp/webGui/scripts/notify -e "My Event" -s "My Subject" -d "My Description" -m "My Message" -i "alert" -l "/Dashboard"
|
||||
#
|
||||
# If a notification does not go through, check the /var/log/notify_Discord file for hints
|
||||
############
|
||||
|
||||
############
|
||||
# Discord webhooks docs: https://birdie0.github.io/discord-webhooks-guide/
|
||||
#
|
||||
# Available fields from notification system
|
||||
# HOSTNAME
|
||||
# EVENT (notify -e)
|
||||
# IMPORTANCE (notify -i)
|
||||
# SUBJECT (notify -s)
|
||||
# DESCRIPTION (notify -d)
|
||||
# CONTENT (notify -m)
|
||||
# LINK (notify -l)
|
||||
# TIMESTAMP (seconds from epoch)
|
||||
|
||||
SCRIPTNAME=$(basename "$0")
|
||||
LOG="/var/log/notify_${SCRIPTNAME%.*}"
|
||||
|
||||
# for quick test, setup environment to mimic notify script
|
||||
[[ -z "${EVENT}" ]] && EVENT='Unraid Status'
|
||||
[[ -z "${SUBJECT}" ]] && SUBJECT='Notification'
|
||||
[[ -z "${DESCRIPTION}" ]] && DESCRIPTION='No description'
|
||||
[[ -z "${IMPORTANCE}" ]] && IMPORTANCE='normal'
|
||||
[[ -z "${TIMESTAMP}" ]] && TIMESTAMP=$(date +%s)
|
||||
# ensure link has a host
|
||||
if [[ -n "${LINK}" ]] && [[ ${LINK} != http* ]]; then
|
||||
source <(grep "NGINX_DEFAULTURL" /usr/local/emhttp/state/nginx.ini 2>/dev/null)
|
||||
LINK=${NGINX_DEFAULTURL}${LINK}
|
||||
fi
|
||||
# Discord will not allow links with bare hostname, links must have both hostname and tld or no link at all
|
||||
if [[ -n "${LINK}" ]]; then
|
||||
HOST=$(echo "${LINK}" | cut -d'/' -f3)
|
||||
[[ ${HOST} != *.* ]] && LINK=
|
||||
fi
|
||||
|
||||
# note: there is no default for CONTENT
|
||||
|
||||
# send DESCRIPTION and/or CONTENT. Ignore the default DESCRIPTION.
|
||||
[[ "${DESCRIPTION}" == 'No description' ]] && DESCRIPTION=""
|
||||
if [[ -n "${DESCRIPTION}" ]] && [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}\n\n${CONTENT}"
|
||||
elif [[ -n "${DESCRIPTION}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}"
|
||||
elif [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${CONTENT}"
|
||||
fi
|
||||
# split into 1024 character segments
|
||||
[[ -n "${FULL_DETAILS}" ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"name": "Description",
|
||||
"value": "${FULL_DETAILS:0:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 1024 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:1024:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 2048 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:2048:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/timestamp.html
|
||||
# https://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
|
||||
FORMATTED_TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%S.000Z\" -d @"${TIMESTAMP}")
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/thumbnail.html
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/color.html
|
||||
# vary data based on IMPORTANCE
|
||||
if [[ "${IMPORTANCE}" != "normal" ]] && [[ "${IMPORTANCE}" != "warning" ]] && [[ "${IMPORTANCE}" != "alert" ]]; then
|
||||
IMPORTANCE="normal"
|
||||
fi
|
||||
case "${IMPORTANCE}" in
|
||||
normal)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-normal.png"
|
||||
COLOR="39208"
|
||||
;;
|
||||
warning)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-warning.png"
|
||||
COLOR="16747567"
|
||||
;;
|
||||
alert)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-alert.png"
|
||||
COLOR="14821416"
|
||||
[[ -n "${DISCORD_TAG_ID}" && "${DISCORD_TAG_ID}" == "none" ]] && DISCORD_TAG_ID=""
|
||||
if [[ -n "${DISCORD_TAG_ID}" ]]; then
|
||||
# add leading @ if needed
|
||||
[[ "${DISCORD_TAG_ID:0:1}" != "@" ]] && DISCORD_TAG_ID="@${DISCORD_TAG_ID}"
|
||||
# @mentions only work in the "content" area, not the "embed" area
|
||||
DISCORD_CONTENT_AREA="\"content\": \"<${DISCORD_TAG_ID}>\","
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/author.html
|
||||
# if SERVER_ICON is defined, use it
|
||||
[[ -n "${SERVER_ICON}" && "${SERVER_ICON:0:8}" == "https://" ]] && ICON_URL="\"icon_url\": \"${SERVER_ICON}\","
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/url.html
|
||||
# if LINK is defined, use it
|
||||
[[ -n "${LINK}" ]] && LINK_URL="\"url\": \"${LINK}\","
|
||||
|
||||
DATA=$(
|
||||
cat <<EOF
|
||||
{
|
||||
${DISCORD_CONTENT_AREA}
|
||||
"embeds": [
|
||||
{
|
||||
"title": "${EVENT:0:256}",
|
||||
"description": "${SUBJECT:0:2043}",
|
||||
${LINK_URL}
|
||||
"timestamp": ${FORMATTED_TIMESTAMP},
|
||||
"color": "${COLOR}",
|
||||
"author": {
|
||||
${ICON_URL}
|
||||
"name": "${HOSTNAME}"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "${THUMBNAIL}"
|
||||
},
|
||||
"fields": [
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Priority",
|
||||
"value": "${IMPORTANCE}",
|
||||
"inline": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# echo "${DATA}" >>"${LOG}"
|
||||
|
||||
# try several times in case we are being rate limited
|
||||
# this is not foolproof, messages can still be rejected
|
||||
MAX=4
|
||||
for ((i = 1; i <= "${MAX}"; i++)); do
|
||||
RET=$(
|
||||
curl -s -X "POST" "$WEBH_URL" -H 'Content-Type: application/json' --data-ascii @- <<EOF
|
||||
${DATA}
|
||||
EOF
|
||||
)
|
||||
# if nothing was returned, message was successfully sent. exit loop
|
||||
[[ -z "${RET}" ]] && break
|
||||
# log the attempt
|
||||
{
|
||||
date
|
||||
echo "attempt ${i} of ${MAX} failed"
|
||||
echo "${RET}"
|
||||
} >>"${LOG}"
|
||||
# if there was an error with the submission, log details and exit loop
|
||||
[[ "${RET}" != *"retry_after"* ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification" && break
|
||||
# if retries exhausted, log failure
|
||||
[[ "${i}" -eq "${MAX}" ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification - rate limited" && break
|
||||
# we were rate limited, try again after a delay
|
||||
sleep 1
|
||||
done
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
39
emhttp/plugins/dynamix/agents/Gotify.xml
Normal file
39
emhttp/plugins/dynamix/agents/Gotify.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Gotify</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8888/" Desc="Full Server Base URL" Default="">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="3"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="7"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
40
emhttp/plugins/dynamix/agents/Prowl.xml
Normal file
40
emhttp/plugins/dynamix/agents/Prowl.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Prowl</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your api key as explained [a href='https://www.prowlapp.com/api_settings.php' target='_blank'][u]here[/u].[/a]" Desc="Api Key" Default="">API_KEY</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="2"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "apikey=$API_KEY" \
|
||||
-F "application=$APP_NAME" \
|
||||
-F "event=$TITLE" \
|
||||
-F "description=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
https://api.prowlapp.com/publicapi/add 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
39
emhttp/plugins/dynamix/agents/PushBits.xml
Normal file
39
emhttp/plugins/dynamix/agents/PushBits.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>PushBits</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8080/" Desc="Full Server Base URL" Default="FULL PUSHBITS URL">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="YOUR APP TOKEN">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="21"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
24
emhttp/plugins/dynamix/agents/Pushbullet.xml
Normal file
24
emhttp/plugins/dynamix/agents/Pushbullet.xml
Normal file
@@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Pushbullet</Name>
|
||||
<Variables>
|
||||
<Variable Help="The Access Token can be found [a href='https://www.pushbullet.com/account' target='_blank'] [u]here[/u].[/a]" Desc="Access Token" Default="">TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
##########
|
||||
{0}
|
||||
##########
|
||||
MESSAGE=$(echo "$MESSAGE" | sed -e 's:<br[ /]*>:\\n:gI' -e 's/<[^>]*>//g')
|
||||
|
||||
curl -s -k \
|
||||
-X POST --header "Authorization: Bearer $TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
-d "{\"type\": \"note\", \"title\": \"$TITLE\", \"body\": \"$MESSAGE\"}" \
|
||||
https://api.pushbullet.com/v2/pushes 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
38
emhttp/plugins/dynamix/agents/Pushover.xml
Normal file
38
emhttp/plugins/dynamix/agents/Pushover.xml
Normal file
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Pushover</Name>
|
||||
<Variables>
|
||||
<Variable Help="The User Key can be found [a href='https://pushover.net/' target='_blank'][u]here[/u].[/a]" Desc="User Key" Default="">USER_KEY</Variable>
|
||||
<Variable Help="The App Token can be found [a href='https://pushover.net/apps' target='_blank'][u]here[/u][/a]." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$SUBJECT,$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="-1"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "token=$APP_TOKEN" \
|
||||
-F "user=$USER_KEY" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "timestamp=$TIMESTAMP" \
|
||||
-F "priority=$PRIORITY" \
|
||||
-F "html=1" \
|
||||
https://api.pushover.net/1/messages.json 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
40
emhttp/plugins/dynamix/agents/Pushplus.xml
Normal file
40
emhttp/plugins/dynamix/agents/Pushplus.xml
Normal file
@@ -0,0 +1,40 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Pushplus</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your token from [a href='https://www.pushplus.plus/push1.html' target='_blank'][u]here[/u][/a]." Desc="Push Token" Default="FILL WITH YOUR OWN">TOKEN</Variable>
|
||||
<Variable Help="Optional. Specify the group code or the 'topic' mentioned in the [a href='https://www.pushplus.plus/doc/guide/api.html' target='_blank'][u]API docs[/u][/a] used for this push. It's used to [a href='https://www.pushplus.plus/push2.html' target='_blank'][u]push to multiple people[/u][/a] instead of pushing to the owner. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Group Code" Default="none">TOPIC</Variable>
|
||||
<Variable Help="Optional. Specify the message channel used for this push. [b]The default value is 'wechat'.[/b]" Desc="Specific Channel" Default="wechat">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the webhook used for this push when the push channel is 'webhook' or 'cp'. [b]To disable this feature, specify 'none'.[/b]" Desc="Webhook" Default="none">WEBHOOK</Variable>
|
||||
<Variable Help="Optional. Specify the callback url used for this push and the pushplus server will send a post request to it after each push completed. [b]To disable this feature, specify 'none'.[/b]" Desc="Callback Url" Default="none">CALLBACKURL</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
#Pushplus don't allow \n in title and the length limit is 100 for free accounts
|
||||
TITLE=$(echo -e "$TITLE" | tr "\n" " ")
|
||||
TITLE=$(echo "${TITLE:0:95}")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${TOPIC}" && "${TOPIC}" == "none" ]] && TOPIC=""
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL="wechat"
|
||||
[[ -n "${WEBHOOK}" && "${WEBHOOK}" == "none" ]] && WEBHOOK=""
|
||||
[[ -n "${CALLBACKURL}" && "${CALLBACKURL}" == "none" ]] && CALLBACKURL=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "token=$TOKEN" \
|
||||
-F "title=$TITLE" \
|
||||
-F "content=$MESSAGE" \
|
||||
-F "topic=$TOPIC" \
|
||||
-F "template=txt" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "webhook=$WEBHOOK" \
|
||||
-F "callbackUrl=$CALLBACKURL" \
|
||||
"https://www.pushplus.plus/send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
31
emhttp/plugins/dynamix/agents/ServerChan.xml
Normal file
31
emhttp/plugins/dynamix/agents/ServerChan.xml
Normal file
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>ServerChan</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your SendKey from [a href='https://sct.ftqq.com/sendkey' target='_blank'][u]here[/u][/a]." Desc="SendKey" Default="FILL WITH YOUR OWN">SENDKEY</Variable>
|
||||
<Variable Help="Optional. Specify the message channel(s) used for this push, e.g., '9' or '9|66'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Channel" Default="none">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the openid(s) or UID(s) used for this push, e.g., 'openid1,openid2' or 'UID1|UID2'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Openid" Default="none">OPENID</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE" | sed -z 's/\n/\n\n/g')
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL=""
|
||||
[[ -n "${OPENID}" && "${OPENID}" == "none" ]] && OPENID=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "desp=$MESSAGE" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "openid=$OPENID" \
|
||||
"https://sctapi.ftqq.com/$SENDKEY.send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
22
emhttp/plugins/dynamix/agents/Slack.xml
Normal file
22
emhttp/plugins/dynamix/agents/Slack.xml
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Slack</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your WebHook as explained [a href='https://api.slack.com/incoming-webhooks' target='_blank'][u]here[/u].[/a]" Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
curl -X POST --header 'Content-Type: application/json' \
|
||||
-d "{\"username\": \"$APP_NAME\", \"text\": \"*$TITLE* \n $MESSAGE\"}" $WEBH_URL 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
43
emhttp/plugins/dynamix/agents/Telegram.xml
Normal file
43
emhttp/plugins/dynamix/agents/Telegram.xml
Normal file
@@ -0,0 +1,43 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>Telegram</Name>
|
||||
<Variables>
|
||||
<Variable Help="[a href='https://telegram.me/botfather' target='_blank'][u]BotFather[/u][/a] is the one bot to rule them all.[br][br]
|
||||
1. Make a bot using BotFather[br]
|
||||
2. Paste the bot token in this field[br]
|
||||
3. Message the bot via Telegram (either via a direct message or a message via a group)[br]
|
||||
4. Test the bot[br][br]
|
||||
* To reset the notifications receiving user or group, run [i]rm /boot/config/plugins/dynamix/telegram/chatid[/i] in the terminal and re-run steps 3. and 4.[/a]" Desc="Bot Access Token" Default="">BOT_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
LEGACY=/boot/config/telegram
|
||||
TELEGRAM=/boot/config/plugins/dynamix/telegram
|
||||
STORED_TOKEN=$(< $TELEGRAM/token) || "";
|
||||
|
||||
# move legacy folder (if existing)
|
||||
[[ -d $LEGACY && ! -d $TELEGRAM ]] && mv $LEGACY $TELEGRAM
|
||||
|
||||
if [[ ! -f $TELEGRAM/token || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
echo $BOT_TOKEN > $TELEGRAM/token;
|
||||
fi
|
||||
|
||||
if [[ ! -f $TELEGRAM/chatid || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
LASTCHATID=$(curl -s https://api.telegram.org/bot$BOT_TOKEN/getUpdates | jq ".result | last .message .chat .id");
|
||||
[[ $LASTCHATID =~ ^-*[0-9]+$ ]] && echo $LASTCHATID > $TELEGRAM/chatid || exit 1
|
||||
fi
|
||||
|
||||
CHATID=$(< $TELEGRAM/chatid);
|
||||
MESSAGE=$(echo -e "$(hostname): $TITLE\n$MESSAGE");
|
||||
curl -G -s "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" --data-urlencode "chat_id=$CHATID" --data-urlencode "text=$MESSAGE" 2>&1;
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
42
emhttp/plugins/dynamix/agents/ntfy.sh.xml
Normal file
42
emhttp/plugins/dynamix/agents/ntfy.sh.xml
Normal file
@@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agent>
|
||||
<Name>ntfy.sh</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://ntfy.sh" Desc="Full Server Base URL" Default="FULL NTFY.SH URL">SERVER_URL</Variable>
|
||||
<Variable Help="The ntfy.sh Token to use." Desc="ntfy.sh Token" Default="YOUR NTFY.SH TOKEN">NTFY_TOKEN</Variable>
|
||||
<Variable Help="The ntfy.sh Topic to use." Desc="ntfy.sh Topic" Default="Unraid">TOPIC</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="default"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="high"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="urgent"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl \
|
||||
-H "Priority: $PRIORITY" \
|
||||
-H "Icon: https://raw.githubusercontent.com/unraid/webgui/master/emhttp/plugins/dynamix.vm.manager/templates/images/unraid.png" \
|
||||
-H "Authorization: Bearer $NTFY_TOKEN" \
|
||||
-H "Title: $TITLE" \
|
||||
-d "$MESSAGE" \
|
||||
$SERVER_URL/$TOPIC
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
@@ -17,7 +17,7 @@ if (!empty($_COOKIE['unraid_'.md5($server_name)])) {
|
||||
|
||||
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));
|
||||
@@ -529,7 +529,12 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']);
|
||||
cookieEnabled = document.cookie.indexOf("cookietest=")!=-1;
|
||||
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
|
||||
if (!cookieEnabled) {
|
||||
document.write("<p class='error'><?=_('Browser cookie support required for Unraid OS webgui')?></p>");
|
||||
const errorElement = document.createElement('p');
|
||||
errorElement.classList.add('error');
|
||||
errorElement.textContent = "<?=_('Please enable cookies to use the Unraid webGUI')?>";
|
||||
|
||||
document.body.textContent = '';
|
||||
document.body.appendChild(errorElement);
|
||||
}
|
||||
</script>
|
||||
</form>
|
||||
@@ -543,7 +548,7 @@ $theme_dark = in_array($display['theme'], ['black', 'gray']);
|
||||
</div>
|
||||
|
||||
<? if (($twoFactorRequired && !empty($token)) || !$twoFactorRequired) { ?>
|
||||
<p class="js-removeTimeout"><a href="https://docs.unraid.net/unraid-os/manual/troubleshooting#lost-root-password" target="_blank"><?=_('Password recovery')?></a></p>
|
||||
<p class="js-removeTimeout"><a href="https://docs.unraid.net/go/lost-root-password/" target="_blank"><?=_('Password recovery')?></a></p>
|
||||
<? } ?>
|
||||
|
||||
</div>
|
||||
|
@@ -347,7 +347,14 @@ $THEME_DARK = in_array($display['theme'],['black','gray']);
|
||||
document.cookie = "cookietest=1";
|
||||
cookieEnabled = document.cookie.indexOf("cookietest=")!=-1;
|
||||
document.cookie = "cookietest=1; expires=Thu, 01-Jan-1970 00:00:01 GMT";
|
||||
if (!cookieEnabled) document.write("<p class='error'><?=_('Browser cookie support required for Unraid OS webgui')?></p>");
|
||||
if (!cookieEnabled) {
|
||||
const errorElement = document.createElement('p');
|
||||
errorElement.classList.add('error');
|
||||
errorElement.textContent = "<?=_('Please enable cookies to use the Unraid webGUI')?>";
|
||||
|
||||
document.body.textContent = '';
|
||||
document.body.appendChild(errorElement);
|
||||
}
|
||||
// Password toggling
|
||||
const $passToggle = document.querySelector('.js-pass-toggle');
|
||||
const $passToggleHideSvg = $passToggle.querySelector('.js-pass-toggle-hide');
|
||||
|
@@ -98,24 +98,62 @@ case 'ct':
|
||||
echo "\0".implode(';',array_map('urlencode',$cts));
|
||||
break;
|
||||
case 'is':
|
||||
$syslinux = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$size = count($syslinux);
|
||||
$menu = $i = 0;
|
||||
$isol = "";
|
||||
$isolcpus = [];
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($syslinux[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'menu default')) $menu = 1;
|
||||
if (scan($syslinux[$n],'append')) foreach (explode(' ',$syslinux[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isol = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$menu = $i = 0;
|
||||
$isol = "";
|
||||
$isolcpus = [];
|
||||
$bootcfg = file('/boot/syslinux/syslinux.cfg', FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$size = count($bootcfg);
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($bootcfg[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'menu default')) $menu = 1;
|
||||
if (scan($bootcfg[$n],'append')) foreach (explode(' ',$bootcfg[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isol = explode('=',$cmd)[1]; break;}
|
||||
$n++;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$isol = "";
|
||||
$isolcpus = [];
|
||||
$bootcfg = file('/boot/grub/grub.cfg', FILE_IGNORE_NEW_LINES);
|
||||
// find the default section
|
||||
$menu_entries = [];
|
||||
foreach ($bootcfg as $line) {
|
||||
if (preg_match('/set default=(\d+)/', $line, $match)) {
|
||||
$bootentry = (int)$match[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
// split boot entries
|
||||
foreach ($bootcfg as $line) {
|
||||
if (strpos($line, 'menuentry ') === 0) {
|
||||
$in_menuentry = true;
|
||||
$current_entry = $line . "\n";
|
||||
} elseif ($in_menuentry) {
|
||||
$current_entry .= $line . "\n";
|
||||
if (trim($line) === "}") {
|
||||
$menu_entries[] = $current_entry;
|
||||
$in_menuentry = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
// search in selected menuentry
|
||||
$menuentry = explode("\n", $menu_entries[$bootentry]);
|
||||
// find the current isolcpus setting
|
||||
if (scan($menu_entries[$bootentry],'linux ')) {
|
||||
foreach ($menuentry as $cmd) {
|
||||
if (scan($cmd,'isolcpus')) {
|
||||
$isol = explode('=',$cmd)[1];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
$i++;
|
||||
}
|
||||
if ($isol != '') {
|
||||
// convert to individual numbers
|
||||
@@ -133,24 +171,28 @@ case 'is':
|
||||
break;
|
||||
case 'cmd':
|
||||
$isolcpus_now = $isolcpus_new = '';
|
||||
$syslinux = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
$cmdline = explode(' ',file_get_contents('/proc/cmdline'));
|
||||
foreach ($cmdline as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_now = $cmd; break;}
|
||||
$size = count($syslinux);
|
||||
$menu = $i = 0;
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($syslinux[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($syslinux[$n],'label ') && $n < $size) {
|
||||
if (scan($syslinux[$n],'menu default')) $menu = 1;
|
||||
if (scan($syslinux[$n],'append')) foreach (explode(' ',$syslinux[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_new = $cmd; break;}
|
||||
$n++;
|
||||
if (is_file('/boot/syslinux/syslinux.cfg')) {
|
||||
$bootcfg = file('/boot/syslinux/syslinux.cfg',FILE_IGNORE_NEW_LINES+FILE_SKIP_EMPTY_LINES);
|
||||
foreach ($cmdline as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_now = $cmd; break;}
|
||||
$size = count($bootcfg);
|
||||
$menu = $i = 0;
|
||||
// find the default section
|
||||
while ($i < $size) {
|
||||
if (scan($bootcfg[$i],'label ')) {
|
||||
$n = $i + 1;
|
||||
// find the current isolcpus setting
|
||||
while (!scan($bootcfg[$n],'label ') && $n < $size) {
|
||||
if (scan($bootcfg[$n],'menu default')) $menu = 1;
|
||||
if (scan($bootcfg[$n],'append')) foreach (explode(' ',$bootcfg[$n]) as $cmd) if (scan($cmd,'isolcpus')) {$isolcpus_new = $cmd; break;}
|
||||
$n++;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
}
|
||||
if ($menu) break; else $i = $n - 1;
|
||||
$i++;
|
||||
}
|
||||
$i++;
|
||||
} elseif (is_file('/boot/grub/grub.cfg')) {
|
||||
$bootcfg = file('/boot/grub/grub.cfg', FILE_IGNORE_NEW_LINES);
|
||||
}
|
||||
echo $isolcpus_now==$isolcpus_new ? 0 : 1;
|
||||
break;
|
||||
|
@@ -48,12 +48,13 @@ if ($_POST['docker']) {
|
||||
$template = $info['template'];
|
||||
$shell = $info['shell'];
|
||||
$webGui = html_entity_decode($info['url']);
|
||||
$TSwebGui = html_entity_decode($info['TSurl']);
|
||||
$support = html_entity_decode($info['Support']);
|
||||
$project = html_entity_decode($info['Project']);
|
||||
$registry = html_entity_decode($info['registry']);
|
||||
$donateLink = html_entity_decode($info['DonateLink']);
|
||||
$readme = html_entity_decode($info['ReadMe']);
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), $shell, $id, addslashes($support), addslashes($project), addslashes($registry), addslashes($donateLink), addslashes($readme));
|
||||
$menu = sprintf("onclick=\"addDockerContainerContext('%s','%s','%s',%s,%s,%s,%s,'%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($name), addslashes($ct['ImageId']), addslashes($template), $running, $paused, $updateStatus, $is_autostart, addslashes($webGui), addslashes($TSwebGui), $shell, $id, addslashes($support), addslashes($project), addslashes($registry), addslashes($donateLink), addslashes($readme));
|
||||
$shape = $running ? ($paused ? 'pause' : 'play') : 'square';
|
||||
$status = $running ? ($paused ? 'paused' : 'started') : 'stopped';
|
||||
$color = $status=='started' ? 'green-text' : ($status=='paused' ? 'orange-text' : 'red-text');
|
||||
@@ -119,7 +120,9 @@ if ($_POST['vms']) {
|
||||
if (empty($template)) $template = 'Custom';
|
||||
$log = (is_file("/var/log/libvirt/qemu/$vm.log") ? "libvirt/qemu/$vm.log" : '');
|
||||
if (!isset($domain_cfg["CONSOLE"])) $vmrcconsole = "web" ; else $vmrcconsole = $domain_cfg["CONSOLE"] ;
|
||||
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole);
|
||||
if (!isset($domain_cfg["RDPOPT"])) $vmrcconsole .= ";no" ; else $vmrcconsole .= ";".$domain_cfg["RDPOPT"] ;
|
||||
$WebUI = html_entity_decode($arrConfig["template"]["webui"]);
|
||||
$menu = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,false,addslashes(str_replace('"',"'",$WebUI)));
|
||||
$icon = $lv->domain_get_icon_url($res);
|
||||
switch ($state) {
|
||||
case 'running':
|
||||
@@ -144,7 +147,7 @@ if ($_POST['vms']) {
|
||||
echo "<span class='outer solid vms $status'><span id='vm-$uuid' $menu class='hand'>$image</span><span class='inner'>$vm<br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span></span>";
|
||||
if ($state == "running") {
|
||||
#Build VM Usage array.
|
||||
$menuusage = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,true);
|
||||
$menuusage = sprintf("onclick=\"addVMContext('%s','%s','%s','%s','%s','%s','%s','%s','%s','%s','%s')\"", addslashes($vm), addslashes($uuid), addslashes($template), $state, addslashes($vmrcurl), strtoupper($vmrcprotocol), addslashes($log),addslashes($fstype), $vmrcconsole,true,addslashes(str_replace('"',"'",$WebUI)));
|
||||
$vmusagehtml[] = "<span class='outer solid vmsuse $status'><span id='vmusage-$uuid' $menuusage class='hand'>$image</span><span class='inner'>$vm<br><i class='fa fa-$shape $status $color'></i><span class='state'>"._($status)."</span></span>";
|
||||
$vmusagehtml[] = "<br><br><span id='vmmetrics-gcpu-".$uuid."'>"._("Loading")."....</span>";
|
||||
$vmusagehtml[] = "<br><span id='vmmetrics-hcpu-".$uuid."'>"._("Loading")."....</span>";
|
||||
|
@@ -582,7 +582,7 @@ function viewHistory() {
|
||||
}
|
||||
function flashReport() {
|
||||
$.post('/webGui/include/Report.php',{cmd:'config'},function(check){
|
||||
if (check>0) addBannerWarning("<?=_('Your flash drive is corrupted or offline').'. '._('Post your diagnostics in the forum for help').'.'?> <a target='_blank' href='https://docs.unraid.net/unraid-os/manual/changing-the-flash-device'><?=_('See also here')?></a>");
|
||||
if (check>0) addBannerWarning("<?=_('Your flash drive is corrupted or offline').'. '._('Post your diagnostics in the forum for help').'.'?> <a target='_blank' href='https://docs.unraid.net/go/changing-the-flash-device/'><?=_('See also here')?></a>");
|
||||
});
|
||||
}
|
||||
$(function() {
|
||||
@@ -803,7 +803,7 @@ default:
|
||||
}
|
||||
echo "</span></span><span id='countdown'></span><span id='user-notice' class='red-text'></span>";
|
||||
echo "<span id='copyright'>Unraid® webGui ©2024, Lime Technology, Inc.";
|
||||
echo " <a href='https://docs.unraid.net/category/manual' target='_blank' title=\""._('Online manual')."\"><i class='fa fa-book'></i> "._('manual')."</a>";
|
||||
echo " <a href='https://docs.unraid.net/go/manual/' target='_blank' title=\""._('Online manual')."\"><i class='fa fa-book'></i> "._('manual')."</a>";
|
||||
echo "</span></div>";
|
||||
?>
|
||||
<script>
|
||||
|
@@ -28,13 +28,42 @@ $var = parse_ini_file('state/var.ini');
|
||||
$sec = parse_ini_file('state/sec.ini',true);
|
||||
$sec_nfs = parse_ini_file('state/sec_nfs.ini',true);
|
||||
|
||||
// exit when no disks
|
||||
/* Get the pools from the disks.ini. */
|
||||
$pools_check = pools_filter(cache_filter($disks));
|
||||
$pools = implode(',', $pools_check);
|
||||
|
||||
/* If the configuration is pools only, then no array disks are available. */
|
||||
$poolsOnly = ((int)$var['SYS_ARRAY_SLOTS'] <= 2) ? true : false;
|
||||
|
||||
// exit when no mountable array disks
|
||||
$nodisks = "<tr><td class='empty' colspan='7'><strong>"._('There are no mountable array or pool disks - cannot add shares').".</strong></td></tr>";
|
||||
if (!checkDisks($disks)) die($nodisks);
|
||||
|
||||
// No shared disks
|
||||
$nodisks = "<tr><td class='empty' colspan='7'><i class='fa fa-folder-open-o icon'></i>"._('There are no exportable disk shares')."</td></tr>";
|
||||
if (!$disks) die($nodisks);
|
||||
|
||||
// GUI settings
|
||||
extract(parse_plugin_cfg('dynamix',true));
|
||||
|
||||
/* Function to filter out unwanted disks, check if any valid disks exist, and ignore disks with a blank device. */
|
||||
function checkDisks($disks) {
|
||||
global $pools, $poolsOnly;
|
||||
|
||||
$rc = false;
|
||||
|
||||
foreach ($disks as $disk) {
|
||||
/* Check the disk type, fsStatus, and ensure the device is not blank. */
|
||||
if (!in_array($disk['name'], ['flash', 'parity', 'parity2']) && strpos($disk['fsStatus'], 'Unmountable') === false && !empty($disk['device'])) {
|
||||
/* A valid disk with a non-blank device is found. */
|
||||
$rc = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return $rc;
|
||||
}
|
||||
|
||||
// Display export settings
|
||||
function disk_share_settings($protocol,$share) {
|
||||
if (empty($share)) return;
|
||||
|
@@ -40,9 +40,9 @@ default:
|
||||
$file = "/var/lib/$dir/check.status.$id";
|
||||
if (file_exists($file)) {
|
||||
switch ($cmd) {
|
||||
case 'btrfs-check': $pgrep = "pgrep -f '/sbin/btrfs check .*$dev'"; break;
|
||||
case 'rfs-check': $pgrep = "pgrep -f '/sbin/reiserfsck $dev'"; break;
|
||||
case 'xfs-check': $pgrep = "pgrep -f '/sbin/xfs_repair.*$dev'"; break;
|
||||
case 'btrfs-check': $pgrep = 'pgrep --ns $$ -f '."'/sbin/btrfs check .*$dev'"; break;
|
||||
case 'rfs-check': $pgrep = 'pgrep --ns $$ -f '."'/sbin/reiserfsck $dev'"; break;
|
||||
case 'xfs-check': $pgrep = 'pgrep --ns $$ -f '."'/sbin/xfs_repair.*$dev'"; break;
|
||||
}
|
||||
echo file_get_contents($file);
|
||||
if (!exec($pgrep)) echo "\0";
|
||||
|
@@ -9,6 +9,7 @@
|
||||
*
|
||||
* History:
|
||||
*
|
||||
* 1.2.1 - exclude folders from the /mnt/ root folder
|
||||
* 1.2.0 - adapted by Bergware for use in Unraid - support UTF-8 encoding & hardening
|
||||
* 1.1.1 - SECURITY: forcing root to prevent users from determining system's file structure (per DaveBrad)
|
||||
* 1.1.0 - adding multiSelect (checkbox) support (08/22/2014)
|
||||
@@ -48,27 +49,45 @@ $filters = (array)$_POST['filter'];
|
||||
$match = $_POST['match'];
|
||||
$checkbox = $_POST['multiSelect']=='true' ? "<input type='checkbox'>" : "";
|
||||
|
||||
/* Excluded folders to not show in the dropdown in the '/mnt/' directory only. */
|
||||
$excludedFolders = ["RecycleBin", "addons", "remotes", "rootshare", "user0"];
|
||||
|
||||
echo "<ul class='jqueryFileTree'>";
|
||||
if ($_POST['show_parent']=='true' && is_top($rootdir)) echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"".htmlspecialchars(dirname($rootdir))."\">..</a></li>";
|
||||
|
||||
if (is_low($rootdir) && is_dir($rootdir)) {
|
||||
$dirs = $files = [];
|
||||
$names = array_filter(scandir($rootdir,SCANDIR_SORT_NONE),function($n){return $n!='.' && $n!='..';});
|
||||
$names = array_filter(scandir($rootdir, SCANDIR_SORT_NONE), function($n) { return $n != '.' && $n != '..'; });
|
||||
natcasesort($names);
|
||||
foreach ($names as $name) if (is_dir($rootdir.$name)) $dirs[] = $name; else $files[] = $name;
|
||||
foreach ($dirs as $name) {
|
||||
$htmlRel = htmlspecialchars($rootdir.$name);
|
||||
$htmlName = htmlspecialchars(mb_strlen($name)<=33 ? $name : mb_substr($name,0,30).'...');
|
||||
if (is_dir($rootdir.$name)) {
|
||||
if (empty($match)||preg_match("/$match/",$rootdir.$name)) echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"$htmlRel/\">$htmlName</a></li>";
|
||||
foreach ($names as $name) {
|
||||
if (is_dir($rootdir . $name)) {
|
||||
$dirs[] = $name;
|
||||
} else {
|
||||
$files[] = $name;
|
||||
}
|
||||
}
|
||||
foreach ($dirs as $name) {
|
||||
$htmlRel = htmlspecialchars($rootdir . $name);
|
||||
$htmlName = htmlspecialchars(mb_strlen($name) <= 33 ? $name : mb_substr($name, 0, 30) . '...');
|
||||
|
||||
/* Exclude '.Recycle.Bin' from all directories */
|
||||
if ($name === ".Recycle.Bin") continue;
|
||||
|
||||
/* Exclude folders only when directory is '/mnt/' */
|
||||
if (in_array($name, $excludedFolders) && $rootdir === "/mnt/") continue;
|
||||
|
||||
echo "<li class='directory collapsed'>$checkbox<a href='#' rel=\"$htmlRel/\">$htmlName</a></li>";
|
||||
}
|
||||
foreach ($files as $name) {
|
||||
$htmlRel = htmlspecialchars($rootdir.$name);
|
||||
$htmlRel = htmlspecialchars($rootdir . $name);
|
||||
$htmlName = htmlspecialchars($name);
|
||||
$ext = mb_strtolower(pathinfo($name,PATHINFO_EXTENSION));
|
||||
foreach ($filters as $filter) if (empty($filter)||$ext==$filter) {
|
||||
if (empty($match)||preg_match("/$match/",$name)) echo "<li class='file ext_$ext'>$checkbox<a href='#' rel=\"$htmlRel\">$htmlName</a></li>";
|
||||
$ext = mb_strtolower(pathinfo($name, PATHINFO_EXTENSION));
|
||||
foreach ($filters as $filter) {
|
||||
if (empty($filter) || $ext == $filter) {
|
||||
if (empty($match) || preg_match("/$match/", $name)) {
|
||||
echo "<li class='file ext_$ext'>$checkbox<a href='#' rel=\"$htmlRel\">$htmlName</a></li>";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -212,20 +212,58 @@ function tail($file, $rows=1) {
|
||||
}
|
||||
return implode($echo);
|
||||
}
|
||||
|
||||
/* Get the last parity check from the parity history. */
|
||||
function last_parity_log() {
|
||||
$log = '/boot/config/parity-checks.log';
|
||||
[$date,$duration,$speed,$status,$error,$action,$size] = file_exists($log) ? my_explode('|',tail($log),7) : array_fill(0,7,0);
|
||||
if ($date) {
|
||||
[$y,$m,$d,$t] = my_preg_split('/ +/',$date,4);
|
||||
$date = strtotime("$d-$m-$y $t");
|
||||
}
|
||||
return [$date,$duration,$speed,$status,$error,$action,$size];
|
||||
$log = '/boot/config/parity-checks.log';
|
||||
|
||||
if (file_exists($log)) {
|
||||
list($date, $duration, $speed, $status, $error, $action, $size) = my_explode('|', tail($log), 7);
|
||||
} else {
|
||||
list($date, $duration, $speed, $status, $error, $action, $size) = array_fill(0, 7, 0);
|
||||
}
|
||||
|
||||
if ($date) {
|
||||
list($y, $m, $d, $t) = my_preg_split('/ +/', $date, 4);
|
||||
$date = strtotime("$d-$m-$y $t");
|
||||
}
|
||||
|
||||
return [$date, $duration, $speed, $status, $error, $action, $size];
|
||||
}
|
||||
|
||||
/* Get the last parity check from Unraid. */
|
||||
function last_parity_check() {
|
||||
global $var;
|
||||
|
||||
/* Files for the latest parity check. */
|
||||
$stamps = '/var/tmp/stamps.ini';
|
||||
$resync = '/var/tmp/resync.ini';
|
||||
|
||||
/* Get the latest parity information from Unraid. */
|
||||
$synced = file_exists($stamps) ? explode(',',file_get_contents($stamps)) : [];
|
||||
$sbSynced = array_shift($synced) ?: _var($var,'sbSynced',0);
|
||||
$idle = [];
|
||||
while (count($synced) > 1) {
|
||||
$idle[] = array_pop($synced) - array_pop($synced);
|
||||
}
|
||||
$action = _var($var, 'mdResyncAction');
|
||||
$size = _var($var, 'mdResyncSize', 0);
|
||||
if (file_exists($resync)) {
|
||||
list($action, $size) = my_explode(',', file_get_contents($resync));
|
||||
}
|
||||
$duration = $var['sbSynced2']-$sbSynced-array_sum($idle);
|
||||
$status = _var($var,'sbSyncExit');
|
||||
$speed = $status==0 ? round($size*1024/$duration) : 0;
|
||||
$error = _var($var,'sbSyncErrs',0);
|
||||
|
||||
return [$duration, $speed, $status, $error, $action, $size];
|
||||
}
|
||||
|
||||
function urlencode_path($path) {
|
||||
return str_replace("%2F", "/", urlencode($path));
|
||||
}
|
||||
function pgrep($process_name, $escape_arg=true) {
|
||||
$pid = exec("pgrep ".($escape_arg?escapeshellarg($process_name):$process_name), $output, $retval);
|
||||
$pid = exec('pgrep --ns $$ '.($escape_arg?escapeshellarg($process_name):$process_name), $output, $retval);
|
||||
return $retval==0 ? $pid : false;
|
||||
}
|
||||
function is_block($path) {
|
||||
@@ -270,26 +308,83 @@ function my_preg_split($split, $text, $count=2) {
|
||||
function delete_file(...$file) {
|
||||
array_map('unlink',array_filter($file,'file_exists'));
|
||||
}
|
||||
function my_mkdir($dirname,$permissions = 0777,$recursive = false) {
|
||||
$dirname = transpose_user_path($dirname);
|
||||
$pathinfo = pathinfo($dirname);
|
||||
$parent = $pathinfo["dirname"];
|
||||
function my_mkdir($dirname,$permissions = 0777,$recursive = false,$own = "nobody",$grp = "users") {
|
||||
if (is_dir($dirname)) return(false);
|
||||
$parent = $dirname;
|
||||
while (!is_dir($parent)){
|
||||
if (!$recursive) return(false);
|
||||
$pathinfo2 = pathinfo($parent);
|
||||
$parent = $pathinfo2["dirname"];
|
||||
}
|
||||
if (strpos($dirname,'/mnt/user/')===0) {
|
||||
$realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($parent)." 2>/dev/null"));
|
||||
if (!empty($realdisk)) {
|
||||
$dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", $dirname);
|
||||
$parent = str_replace('/mnt/user/', "/mnt/$realdisk/", $parent);
|
||||
}
|
||||
}
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' $parent"));
|
||||
$rtncode = false;
|
||||
switch ($fstype) {
|
||||
case "zfs":
|
||||
switch ($fstype) {
|
||||
case "zfs":
|
||||
$zfsdataset = trim(shell_exec("zfs list -H -o name $parent")) ;
|
||||
$rtncode=exec("zfs create $zfsdataset/{$pathinfo['filename']}");
|
||||
if (!$rtncode) mkdir($dirname, $permissions, $recursive);
|
||||
break;
|
||||
$zfsdataset .= str_replace($parent,"",$dirname);
|
||||
if ($recursive) $rtncode=exec("zfs create -p \"$zfsdataset\"");else $rtncode=exec("zfs create \"$zfsdataset\"");
|
||||
if (!$rtncode) mkdir($dirname, $permissions, $recursive); else chmod($zfsdataset,$permissions);
|
||||
break;
|
||||
case "btrfs":
|
||||
$rtncode=exec("btrfs subvolume create $dirname");
|
||||
if (!$rtncode) mkdir($dirname, $permissions, $recursive);
|
||||
if ($recursive) $rtncode=exec("btrfs subvolume create --parents \"$dirname\""); else $rtncode=exec("btrfs subvolume create \"$dirname\"");
|
||||
if (!$rtncode) mkdir($dirname, $permissions, $recursive); else chmod($dirname,$permissions);
|
||||
break;
|
||||
default:
|
||||
mkdir($dirname, $permissions, $recursive);
|
||||
break;
|
||||
}
|
||||
}
|
||||
chown($dirname, $own);
|
||||
chgrp($dirname, $grp);
|
||||
return($rtncode);
|
||||
}
|
||||
function my_rmdir($dirname) {
|
||||
if (!is_dir("$dirname")) {
|
||||
$return = [
|
||||
'rtncode' => "false",
|
||||
'type' => "NoDir",
|
||||
];
|
||||
return($return);
|
||||
}
|
||||
if (strpos($dirname,'/mnt/user/')===0) {
|
||||
$realdisk = trim(shell_exec("getfattr --absolute-names --only-values -n system.LOCATION ".escapeshellarg($dirname)." 2>/dev/null"));
|
||||
if (!empty($realdisk)) {
|
||||
$dirname = str_replace('/mnt/user/', "/mnt/$realdisk/", "$dirname");
|
||||
}
|
||||
}
|
||||
$fstype = trim(shell_exec(" stat -f -c '%T' ".escapeshellarg($dirname)));
|
||||
$rtncode = false;
|
||||
switch ($fstype) {
|
||||
case "zfs":
|
||||
$zfsoutput = array();
|
||||
$zfsdataset = trim(shell_exec("zfs list -H -o name ".escapeshellarg($dirname))) ;
|
||||
$cmdstr = "zfs destroy \"$zfsdataset\" 2>&1 ";
|
||||
$error = exec($cmdstr,$zfsoutput,$rtncode);
|
||||
$return = [
|
||||
'rtncode' => $rtncode,
|
||||
'output' => $zfsoutput,
|
||||
'dataset' => $zfsdataset,
|
||||
'type' => $fstype,
|
||||
'cmd' => $cmdstr,
|
||||
'error' => $error,
|
||||
];
|
||||
break;
|
||||
case "btrfs":
|
||||
default:
|
||||
$rtncode = rmdir($dirname);
|
||||
$return = [
|
||||
'rtncode' => $rtncode,
|
||||
'type' => $fstype,
|
||||
];
|
||||
break;
|
||||
}
|
||||
return($return);
|
||||
}
|
||||
function get_realvolume($path) {
|
||||
if (strpos($path,"/mnt/user/",0) === 0)
|
||||
|
@@ -1,627 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Agents>
|
||||
<Agent>
|
||||
<Name>Boxcar</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your access token as explained [a href='http://help.boxcar.io/knowledgebase/articles/314474-how-to-get-my-boxcar-access-token' target='_blank'][u]here[/u].[/a]" Desc="Access Token" Default="">ACCESS_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
curl -s -k \
|
||||
-d "user_credentials=$ACCESS_TOKEN" \
|
||||
-d "notification[title]=$TITLE" \
|
||||
-d "notification[long_message]=$MESSAGE" \
|
||||
-d "notification[source_name]=Unraid" \
|
||||
-d "notification[sound]=bird-1" \
|
||||
-d "notification[icon_url]=http://i.imgur.com/u63iSL1.png" \
|
||||
https://new.boxcar.io/api/notifications 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Discord</Name>
|
||||
<Variables>
|
||||
<Variable Help="Add an '#unraid-notifications' channel to your personal Discord server, then get a WebHook URL as explained [a href='https://support.discord.com/hc/en-us/articles/228383668-Intro-to-Webhooks' target='_blank'][u]here[/u].[/a] Note that multiple Unraid servers can use the same Webhook." Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Provide the https URL to an icon representing this Unraid server (using different icons for each server can help distinguish between them in the list of notifications.) To disable this feature, specify 'none'." Desc="Server Icon" Default="https://craftassets.unraid.net/uploads/logos/un-mark-gradient@2x.png">SERVER_ICON</Variable>
|
||||
<Variable Help="In Discord, right-click the '#unraid-notifications' channel and choose Notification Settings -> Only @mentions. Then to receive an @mention on 'alert' priority notifications only, provide your personal Discord ID (it is a series of numbers, not letters). To find your ID, in Discord type \@yourusername. To disable this feature, specify 'none'." Desc="Discord Tag ID" Default="none">DISCORD_TAG_ID</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
|
||||
############
|
||||
# Quick test with default values:
|
||||
# bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Quick test with values set through environment (all vars are optional)
|
||||
# EVENT="My Event" SUBJECT="My Subject" DESCRIPTION="My Description" CONTENT="My Message" IMPORTANCE="alert" LINK="/Dashboard" bash /boot/config/plugins/dynamix/notifications/agents/Discord.sh
|
||||
# Full test of notification system (at least one param is required)
|
||||
# /usr/local/emhttp/webGui/scripts/notify -e "My Event" -s "My Subject" -d "My Description" -m "My Message" -i "alert" -l "/Dashboard"
|
||||
#
|
||||
# If a notification does not go through, check the /var/log/notify_Discord file for hints
|
||||
############
|
||||
|
||||
############
|
||||
# Discord webhooks docs: https://birdie0.github.io/discord-webhooks-guide/
|
||||
#
|
||||
# Available fields from notification system
|
||||
# HOSTNAME
|
||||
# EVENT (notify -e)
|
||||
# IMPORTANCE (notify -i)
|
||||
# SUBJECT (notify -s)
|
||||
# DESCRIPTION (notify -d)
|
||||
# CONTENT (notify -m)
|
||||
# LINK (notify -l)
|
||||
# TIMESTAMP (seconds from epoch)
|
||||
|
||||
SCRIPTNAME=$(basename "$0")
|
||||
LOG="/var/log/notify_${SCRIPTNAME%.*}"
|
||||
|
||||
# for quick test, setup environment to mimic notify script
|
||||
[[ -z "${EVENT}" ]] && EVENT='Unraid Status'
|
||||
[[ -z "${SUBJECT}" ]] && SUBJECT='Notification'
|
||||
[[ -z "${DESCRIPTION}" ]] && DESCRIPTION='No description'
|
||||
[[ -z "${IMPORTANCE}" ]] && IMPORTANCE='normal'
|
||||
[[ -z "${TIMESTAMP}" ]] && TIMESTAMP=$(date +%s)
|
||||
# ensure link has a host
|
||||
if [[ -n "${LINK}" ]] && [[ ${LINK} != http* ]]; then
|
||||
source <(grep "NGINX_DEFAULTURL" /usr/local/emhttp/state/nginx.ini 2>/dev/null)
|
||||
LINK=${NGINX_DEFAULTURL}${LINK}
|
||||
fi
|
||||
# Discord will not allow links with bare hostname, links must have both hostname and tld or no link at all
|
||||
if [[ -n "${LINK}" ]]; then
|
||||
HOST=$(echo "${LINK}" | cut -d'/' -f3)
|
||||
[[ ${HOST} != *.* ]] && LINK=
|
||||
fi
|
||||
|
||||
# note: there is no default for CONTENT
|
||||
|
||||
# send DESCRIPTION and/or CONTENT. Ignore the default DESCRIPTION.
|
||||
[[ "${DESCRIPTION}" == 'No description' ]] && DESCRIPTION=""
|
||||
if [[ -n "${DESCRIPTION}" ]] && [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}\n\n${CONTENT}"
|
||||
elif [[ -n "${DESCRIPTION}" ]]; then
|
||||
FULL_DETAILS="${DESCRIPTION}"
|
||||
elif [[ -n "${CONTENT}" ]]; then
|
||||
FULL_DETAILS="${CONTENT}"
|
||||
fi
|
||||
# split into 1024 character segments
|
||||
[[ -n "${FULL_DETAILS}" ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
{
|
||||
"name": "Description",
|
||||
"value": "${FULL_DETAILS:0:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 1024 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:1024:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
[[ -n "${FULL_DETAILS}" ]] && [[ ${#FULL_DETAILS} -gt 2048 ]] && DESC_FIELD=$(
|
||||
cat <<EOF
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Description (cont)",
|
||||
"value": "${FULL_DETAILS:2048:1024}"
|
||||
},
|
||||
EOF
|
||||
)
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/timestamp.html
|
||||
# https://www.cyberciti.biz/faq/linux-unix-formatting-dates-for-display/
|
||||
FORMATTED_TIMESTAMP=$(date -u +\"%Y-%m-%dT%H:%M:%S.000Z\" -d @"${TIMESTAMP}")
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/thumbnail.html
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/color.html
|
||||
# vary data based on IMPORTANCE
|
||||
if [[ "${IMPORTANCE}" != "normal" ]] && [[ "${IMPORTANCE}" != "warning" ]] && [[ "${IMPORTANCE}" != "alert" ]]; then
|
||||
IMPORTANCE="normal"
|
||||
fi
|
||||
case "${IMPORTANCE}" in
|
||||
normal)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-normal.png"
|
||||
COLOR="39208"
|
||||
;;
|
||||
warning)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-warning.png"
|
||||
COLOR="16747567"
|
||||
;;
|
||||
alert)
|
||||
THUMBNAIL="https://craftassets.unraid.net/uploads/discord/notify-alert.png"
|
||||
COLOR="14821416"
|
||||
[[ -n "${DISCORD_TAG_ID}" && "${DISCORD_TAG_ID}" == "none" ]] && DISCORD_TAG_ID=""
|
||||
if [[ -n "${DISCORD_TAG_ID}" ]]; then
|
||||
# add leading @ if needed
|
||||
[[ "${DISCORD_TAG_ID:0:1}" != "@" ]] && DISCORD_TAG_ID="@${DISCORD_TAG_ID}"
|
||||
# @mentions only work in the "content" area, not the "embed" area
|
||||
DISCORD_CONTENT_AREA="\"content\": \"<${DISCORD_TAG_ID}>\","
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/author.html
|
||||
# if SERVER_ICON is defined, use it
|
||||
[[ -n "${SERVER_ICON}" && "${SERVER_ICON:0:8}" == "https://" ]] && ICON_URL="\"icon_url\": \"${SERVER_ICON}\","
|
||||
|
||||
# https://birdie0.github.io/discord-webhooks-guide/structure/embed/url.html
|
||||
# if LINK is defined, use it
|
||||
[[ -n "${LINK}" ]] && LINK_URL="\"url\": \"${LINK}\","
|
||||
|
||||
DATA=$(
|
||||
cat <<EOF
|
||||
{
|
||||
${DISCORD_CONTENT_AREA}
|
||||
"embeds": [
|
||||
{
|
||||
"title": "${EVENT:0:256}",
|
||||
"description": "${SUBJECT:0:2043}",
|
||||
${LINK_URL}
|
||||
"timestamp": ${FORMATTED_TIMESTAMP},
|
||||
"color": "${COLOR}",
|
||||
"author": {
|
||||
${ICON_URL}
|
||||
"name": "${HOSTNAME}"
|
||||
},
|
||||
"thumbnail": {
|
||||
"url": "${THUMBNAIL}"
|
||||
},
|
||||
"fields": [
|
||||
${DESC_FIELD}
|
||||
{
|
||||
"name": "Priority",
|
||||
"value": "${IMPORTANCE}",
|
||||
"inline": true
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
# echo "${DATA}" >>"${LOG}"
|
||||
|
||||
# try several times in case we are being rate limited
|
||||
# this is not foolproof, messages can still be rejected
|
||||
MAX=4
|
||||
for ((i = 1; i <= "${MAX}"; i++)); do
|
||||
RET=$(
|
||||
curl -s -X "POST" "$WEBH_URL" -H 'Content-Type: application/json' --data-ascii @- <<EOF
|
||||
${DATA}
|
||||
EOF
|
||||
)
|
||||
# if nothing was returned, message was successfully sent. exit loop
|
||||
[[ -z "${RET}" ]] && break
|
||||
# log the attempt
|
||||
{
|
||||
date
|
||||
echo "attempt ${i} of ${MAX} failed"
|
||||
echo "${RET}"
|
||||
} >>"${LOG}"
|
||||
# if there was an error with the submission, log details and exit loop
|
||||
[[ "${RET}" != *"retry_after"* ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification" && break
|
||||
# if retries exhausted, log failure
|
||||
[[ "${i}" -eq "${MAX}" ]] && echo "${DATA}" >>"${LOG}" && logger -t "${SCRIPTNAME}" -- "Failed sending notification - rate limited" && break
|
||||
# we were rate limited, try again after a delay
|
||||
sleep 1
|
||||
done
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Gotify</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8888/" Desc="Full Server Base URL" Default="">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="3"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="7"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Join</Name>
|
||||
<Variables>
|
||||
<Variable Help="The API key can be found [a href='https://joinjoaomgcd.appspot.com' target='_blank'] [u]here[/u].[/a]" Desc="API key" Default="">API_KEY</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
##########
|
||||
{0}
|
||||
##########
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
curl -s -k -G \
|
||||
-d "apikey=$API_KEY" \
|
||||
--data-urlencode "title=$TITLE" \
|
||||
--data-urlencode "text=$MESSAGE" \
|
||||
-d "deviceId=group.all" \
|
||||
https://joinjoaomgcd.appspot.com/_ah/api/messaging/v1/sendPush 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>ntfy.sh</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://ntfy.sh" Desc="Full Server Base URL" Default="FULL NTFY.SH URL">SERVER_URL</Variable>
|
||||
<Variable Help="The ntfy.sh Token to use." Desc="ntfy.sh Token" Default="YOUR NTFY.SH TOKEN">NTFY_TOKEN</Variable>
|
||||
<Variable Help="The ntfy.sh Topic to use." Desc="ntfy.sh Topic" Default="Unraid">TOPIC</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="default"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="high"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="urgent"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl \
|
||||
-H "Priority: $PRIORITY" \
|
||||
-H "Icon: https://raw.githubusercontent.com/unraid/webgui/master/emhttp/plugins/dynamix.vm.manager/templates/images/unraid.png" \
|
||||
-H "Authorization: Bearer $NTFY_TOKEN" \
|
||||
-H "Title: $TITLE" \
|
||||
-d "$MESSAGE" \
|
||||
$SERVER_URL/$TOPIC
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Prowl</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your api key as explained [a href='https://www.prowlapp.com/api_settings.php' target='_blank'][u]here[/u].[/a]" Desc="Api Key" Default="">API_KEY</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="2"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "apikey=$API_KEY" \
|
||||
-F "application=$APP_NAME" \
|
||||
-F "event=$TITLE" \
|
||||
-F "description=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
https://api.prowlapp.com/publicapi/add 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>PushBits</Name>
|
||||
<Variables>
|
||||
<Variable Help="The full server base URL including protocol and port. eg: https://example.com:8080/" Desc="Full Server Base URL" Default="FULL PUSHBITS URL">SERVER_URL</Variable>
|
||||
<Variable Help="The App Token to use." Desc="App Token" Default="YOUR APP TOKEN">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="5"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="21"
|
||||
;;
|
||||
esac
|
||||
|
||||
# Remove any trailing slash
|
||||
SERVER_URL=${SERVER_URL%/}
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "priority=$PRIORITY" \
|
||||
${SERVER_URL}/message?token=$APP_TOKEN 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Pushbullet</Name>
|
||||
<Variables>
|
||||
<Variable Help="The Access Token can be found [a href='https://www.pushbullet.com/account' target='_blank'] [u]here[/u].[/a]" Desc="Access Token" Default="">TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
##########
|
||||
{0}
|
||||
##########
|
||||
MESSAGE=$(echo "$MESSAGE" | sed -e 's:<br[ /]*>:\\n:gI' -e 's/<[^>]*>//g')
|
||||
|
||||
curl -s -k \
|
||||
-X POST --header "Authorization: Bearer $TOKEN" \
|
||||
--header 'Content-Type: application/json' \
|
||||
-d "{\"type\": \"note\", \"title\": \"$TITLE\", \"body\": \"$MESSAGE\"}" \
|
||||
https://api.pushbullet.com/v2/pushes 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Pushover</Name>
|
||||
<Variables>
|
||||
<Variable Help="The User Key can be found [a href='https://pushover.net/' target='_blank'][u]here[/u].[/a]" Desc="User Key" Default="">USER_KEY</Variable>
|
||||
<Variable Help="The App Token can be found [a href='https://pushover.net/apps' target='_blank'][u]here[/u][/a]." Desc="App Token" Default="">APP_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$SUBJECT,$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
case "$IMPORTANCE" in
|
||||
'normal' )
|
||||
PRIORITY="-1"
|
||||
;;
|
||||
'warning' )
|
||||
PRIORITY="0"
|
||||
;;
|
||||
'alert' )
|
||||
PRIORITY="1"
|
||||
;;
|
||||
esac
|
||||
|
||||
curl -s -k \
|
||||
-F "token=$APP_TOKEN" \
|
||||
-F "user=$USER_KEY" \
|
||||
-F "message=$MESSAGE" \
|
||||
-F "timestamp=$TIMESTAMP" \
|
||||
-F "priority=$PRIORITY" \
|
||||
-F "html=1" \
|
||||
https://api.pushover.net/1/messages.json 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Pushplus</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your token from [a href='https://www.pushplus.plus/push1.html' target='_blank'][u]here[/u][/a]." Desc="Push Token" Default="FILL WITH YOUR OWN">TOKEN</Variable>
|
||||
<Variable Help="Optional. Specify the group code or the 'topic' mentioned in the [a href='https://www.pushplus.plus/doc/guide/api.html' target='_blank'][u]API docs[/u][/a] used for this push. It's used to [a href='https://www.pushplus.plus/push2.html' target='_blank'][u]push to multiple people[/u][/a] instead of pushing to the owner. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Group Code" Default="none">TOPIC</Variable>
|
||||
<Variable Help="Optional. Specify the message channel used for this push. [b]The default value is 'wechat'.[/b]" Desc="Specific Channel" Default="wechat">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the webhook used for this push when the push channel is 'webhook' or 'cp'. [b]To disable this feature, specify 'none'.[/b]" Desc="Webhook" Default="none">WEBHOOK</Variable>
|
||||
<Variable Help="Optional. Specify the callback url used for this push and the pushplus server will send a post request to it after each push completed. [b]To disable this feature, specify 'none'.[/b]" Desc="Callback Url" Default="none">CALLBACKURL</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
#Pushplus don't allow \n in title and the length limit is 100 for free accounts
|
||||
TITLE=$(echo -e "$TITLE" | tr "\n" " ")
|
||||
TITLE=$(echo "${TITLE:0:95}")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${TOPIC}" && "${TOPIC}" == "none" ]] && TOPIC=""
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL="wechat"
|
||||
[[ -n "${WEBHOOK}" && "${WEBHOOK}" == "none" ]] && WEBHOOK=""
|
||||
[[ -n "${CALLBACKURL}" && "${CALLBACKURL}" == "none" ]] && CALLBACKURL=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "token=$TOKEN" \
|
||||
-F "title=$TITLE" \
|
||||
-F "content=$MESSAGE" \
|
||||
-F "topic=$TOPIC" \
|
||||
-F "template=txt" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "webhook=$WEBHOOK" \
|
||||
-F "callbackUrl=$CALLBACKURL" \
|
||||
"https://www.pushplus.plus/send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>ServerChan</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your SendKey from [a href='https://sct.ftqq.com/sendkey' target='_blank'][u]here[/u][/a]." Desc="SendKey" Default="FILL WITH YOUR OWN">SENDKEY</Variable>
|
||||
<Variable Help="Optional. Specify the message channel(s) used for this push, e.g., '9' or '9|66'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Channel" Default="none">CHANNEL</Variable>
|
||||
<Variable Help="Optional. Specify the openid(s) or UID(s) used for this push, e.g., 'openid1,openid2' or 'UID1|UID2'. [b]To disable this feature, specify 'none'.[/b]" Desc="Specific Openid" Default="none">OPENID</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE" | sed -z 's/\n/\n\n/g')
|
||||
[[ -n "${CHANNEL}" && "${CHANNEL}" == "none" ]] && CHANNEL=""
|
||||
[[ -n "${OPENID}" && "${OPENID}" == "none" ]] && OPENID=""
|
||||
|
||||
curl -s -k -X POST \
|
||||
-F "title=$TITLE" \
|
||||
-F "desp=$MESSAGE" \
|
||||
-F "channel=$CHANNEL" \
|
||||
-F "openid=$OPENID" \
|
||||
"https://sctapi.ftqq.com/$SENDKEY.send" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Slack</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your WebHook as explained [a href='https://api.slack.com/incoming-webhooks' target='_blank'][u]here[/u].[/a]" Desc="WebHook URL" Default="USE YOUR OWN WEBHOOK VALUE HERE">WEBH_URL</Variable>
|
||||
<Variable Help="Application name, e.g., Unraid Server." Desc="Application Name" Default="Unraid Server">APP_NAME</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
curl -X POST --header 'Content-Type: application/json' \
|
||||
-d "{\"username\": \"$APP_NAME\", \"text\": \"*$TITLE* \n $MESSAGE\"}" $WEBH_URL 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Telegram</Name>
|
||||
<Variables>
|
||||
<Variable Help="[a href='https://telegram.me/botfather' target='_blank'][u]BotFather[/u][/a] is the one bot to rule them all.[br][br]
|
||||
1. Make a bot using BotFather[br]
|
||||
2. Paste the bot token in this field[br]
|
||||
3. Message the bot via Telegram (either via a direct message or a message via a group)[br]
|
||||
4. Test the bot[br][br]
|
||||
* To reset the notifications receiving user or group, run [i]rm /boot/config/plugins/dynamix/telegram/chatid[/i] in the terminal and re-run steps 3. and 4.[/a]" Desc="Bot Access Token" Default="">BOT_TOKEN</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
LEGACY=/boot/config/telegram
|
||||
TELEGRAM=/boot/config/plugins/dynamix/telegram
|
||||
STORED_TOKEN=$(< $TELEGRAM/token) || "";
|
||||
|
||||
# move legacy folder (if existing)
|
||||
[[ -d $LEGACY && ! -d $TELEGRAM ]] && mv $LEGACY $TELEGRAM
|
||||
|
||||
if [[ ! -f $TELEGRAM/token || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
echo $BOT_TOKEN > $TELEGRAM/token;
|
||||
fi
|
||||
|
||||
if [[ ! -f $TELEGRAM/chatid || "$STORED_TOKEN" != "$BOT_TOKEN" ]]; then
|
||||
mkdir -p $TELEGRAM;
|
||||
LASTCHATID=$(curl -s https://api.telegram.org/bot$BOT_TOKEN/getUpdates | jq ".result | last .message .chat .id");
|
||||
[[ $LASTCHATID =~ ^-*[0-9]+$ ]] && echo $LASTCHATID > $TELEGRAM/chatid || exit 1
|
||||
fi
|
||||
|
||||
CHATID=$(< $TELEGRAM/chatid);
|
||||
MESSAGE=$(echo -e "$(hostname): $TITLE\n$MESSAGE");
|
||||
curl -G -s "https://api.telegram.org/bot$BOT_TOKEN/sendMessage" --data-urlencode "chat_id=$CHATID" --data-urlencode "text=$MESSAGE" 2>&1;
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
<Agent>
|
||||
<Name>Bark</Name>
|
||||
<Variables>
|
||||
<Variable Help="Get your push url from Bark APP. [a href='https://bark.day.app/#/tutorial' target='_blank'][u]tutorial[/u][/a]." Desc="PushUrl" Default="https://api.day.app/your_key">PUSHURL</Variable>
|
||||
<Variable Help="Optional. Specify the message group used for this push. [b]To disable this feature, specify 'none'.[/b]" Desc="Group" Default="Unraid">GROUP</Variable>
|
||||
<Variable Help="Optional. Specify the message sound, copy the sound name from Bark APP. [b]To disable this feature, specify 'none'.[/b]" Desc="Sound" Default="none">SOUND</Variable>
|
||||
<Variable Help="Specify the fields which are included in the title of the notification." Desc="Notification Title" Default="$SUBJECT">TITLE</Variable>
|
||||
<Variable Help="Specify the fields which are included in the message body of the notification." Desc="Notification Message" Default="$DESCRIPTION">MESSAGE</Variable>
|
||||
</Variables>
|
||||
<Script>
|
||||
<![CDATA[
|
||||
#!/bin/bash
|
||||
############
|
||||
{0}
|
||||
############
|
||||
# Markdown newline style for message content
|
||||
TITLE=$(echo -e "$TITLE")
|
||||
MESSAGE=$(echo -e "$MESSAGE")
|
||||
[[ -n "${GROUP}" && "${GROUP}" == "none" ]] && GROUP=""
|
||||
[[ -n "${SOUND}" && "${SOUND}" == "none" ]] && SOUND=""
|
||||
|
||||
curl -X "POST" "$PUSHURL" \
|
||||
-H 'Content-Type: application/json; charset=utf-8' \
|
||||
-d "{\"body\": \"$MESSAGE\", \"title\": \"$TITLE\", \"sound\": \"$SOUND\", \"group\": \"$GROUP\"}" 2>&1
|
||||
]]>
|
||||
</Script>
|
||||
</Agent>
|
||||
</Agents>
|
@@ -44,14 +44,14 @@ switch ($_GET['tag']) {
|
||||
case 'ttyd':
|
||||
// check if ttyd already running
|
||||
$sock = "/var/run/ttyd.sock";
|
||||
exec("pgrep -f '$sock'", $ttyd_pid, $retval);
|
||||
exec('pgrep --ns $$ -f '."'$sock'", $ttyd_pid, $retval);
|
||||
if ($retval == 0) {
|
||||
// check if there are any child processes, ie, curently open tty windows
|
||||
exec("pgrep -P ".$ttyd_pid[0], $output, $retval);
|
||||
exec('pgrep --ns $$ -P '.$ttyd_pid[0], $output, $retval);
|
||||
// no child processes, restart ttyd to pick up possible font size change
|
||||
if ($retval != 0) exec("kill ".$ttyd_pid[0]);
|
||||
}
|
||||
if ($retval != 0) exec("ttyd-exec -i '$sock' bash --login");
|
||||
if ($retval != 0) exec("ttyd-exec -i '$sock' '" . posix_getpwuid(0)['shell'] . "' --login");
|
||||
break;
|
||||
case 'syslog':
|
||||
// read syslog file
|
||||
|
@@ -78,7 +78,7 @@ function tab_title($title,$path,$tag) {
|
||||
if (!$tag || substr($tag,-4)=='.png') {
|
||||
$file = "$path/icons/".($tag ?: strtolower(str_replace(' ','',$title)).".png");
|
||||
if (file_exists("$docroot/$file")) {
|
||||
return "<img src='/$file' class='icon'>$title";
|
||||
return "<img src='/$file' class='icon' style='max-width: 18px; max-height: 18px; width: auto; height: auto; object-fit: contain;'>$title";
|
||||
} else {
|
||||
return "<i class='fa fa-th title'></i>$title";
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user