Compare commits

...

547 Commits

Author SHA1 Message Date
Tom Mortensen
95c6913c62 small chang in how rc.cgroup2unaid is invoked 2024-10-11 02:24:43 -07:00
Tom Mortensen
b783d4b207 more killall and pgrep updates. 2024-10-11 02:24:43 -07:00
Tom Mortensen
a866de833a Revert "stop ntpd from complaining about multiple IP addresses" 2024-10-11 02:24:43 -07:00
tom mortensen
ee31e35849 Merge pull request #1882 from desertwitch/fix-rsyslogd
(bugfix) /etc/rc.d/rc.rsyslogd: OS-native PID for decisions
2024-10-10 17:13:35 -07:00
tom mortensen
50e7389c8a Merge pull request #1889 from SimonFair/ntp-fix
killall and pgrep updates.
2024-10-10 17:02:59 -07:00
tom mortensen
d536ef285b Merge pull request #1890 from dlandon/delete_DS_Store_files_when_deleting_user_share
Delete all '*.DS_Store' files when deleting share.
2024-10-10 16:57:08 -07:00
dlandon
2dc82b61de Delete all '*.DS_Store' files when deleting share. 2024-10-10 16:16:09 -05:00
SimonFair
57ec7909e5 Update rc.sshd 2024-10-10 21:54:16 +01:00
SimonFair
01c6f64b52 Revert sshd loops. 2024-10-10 21:03:02 +01:00
SimonFair
80d567dfde killall and pgrep updates.
Set --ns $$ on commands.
2024-10-10 19:18:54 +01:00
tom mortensen
99d60fa08a Merge pull request #1888 from SimonFair/ntp-fix
Samba update and to use smbcontrol for reload
2024-10-10 10:37:49 -07:00
tom mortensen
8bbf176b8b Merge pull request #1887 from unraid/fix-device-list
Fix PHP error in device_list
2024-10-10 10:36:50 -07:00
tom mortensen
ca51a3799b Merge pull request #1885 from dlandon/remove_ps.txt_file_from_diagnostics
Remove ps.txt from diagnostics.
2024-10-10 10:36:28 -07:00
tom mortensen
e4bb758b05 Merge pull request #1884 from dlandon/show_delete_on_shares_settings_page_at_all_times
Delete share is always visible.
2024-10-10 10:35:59 -07:00
SimonFair
3c007fa1d0 Samba update and to use smbcontrol for reload 2024-10-10 17:17:17 +01:00
ljm42
c062e4dd9c Fix PHP error in device_list
Was preventing the main page from populating with data in some cases
2024-10-10 09:09:39 -07:00
desertwitch
06b1c9a20f /etc/rc.d/rc.rsyslogd: check status before reload 2024-10-10 07:03:48 +02:00
desertwitch
bf6d5982be /etc/rc.d/rc.rsyslogd: use pgrep, killall with PID namespace 2024-10-10 06:51:05 +02:00
dlandon
d9bd5b56c8 Remove ps.txt from diagnostics. 2024-10-09 13:08:55 -05:00
dlandon
bc7c66fec9 Delete share is always visible and enabled only when it is safe to delete the share. 2024-10-09 12:06:48 -05:00
tom mortensen
dc50e7d2c2 Merge pull request #1881 from SimonFair/ntp-fix
Set NTP to use interfaces.
2024-10-09 08:56:07 -07:00
desertwitch
0061c66dfe /etc/rc.d/rc.rsyslogd: OS-native PID for decisions
bugfix: multiple running rsyslogd processes (e.g. spawned by Docker containers) caused the rc.d script to think that the OS-native process was already running, resulting in startup failure of the OSes daemon.
2024-10-09 14:52:44 +02:00
SimonFair
91caf869f5 Set NTP to use inferfaces. 2024-10-08 21:57:48 +01:00
tom mortensen
97c3a4621b Merge pull request #1877 from dlandon/cannot_change_share_settings_if_pools_missing
Fix a situation where the secondary pool device is missing.
2024-10-08 12:18:48 -07:00
tom mortensen
1ffb22bddf Merge pull request #1880 from ich777/ich777-docker-directory-fix
Fix for docker directory
2024-10-08 12:16:44 -07:00
tom mortensen
1d9e14f07c Merge pull request #1878 from SimonFair/VM-ZFS-Fixes
Fix virtiofsd pre processor + add virtiogpu option to gui.
2024-10-08 12:14:26 -07:00
6f7b97e37a Fix for docker directory
- store path outside from function to not shorten path if users switches multiple times between `image` and `folder`
- make sure overlay2 is selected by default
2024-10-07 14:54:08 +02:00
SimonFair
ed7219d9c7 Add virtio gpu option. 2024-10-06 11:29:57 +01:00
SimonFair
082d7d842b Revert "Add virtio gu(2d) as an option of template."
This reverts commit 9a502776a1.
2024-10-06 11:26:34 +01:00
SimonFair
9a502776a1 Add virtio gu(2d) as an option of template. 2024-10-06 11:17:19 +01:00
SimonFair
03346f4709 Update virtiofsd.php 2024-10-05 07:57:19 +01:00
dlandon
1cc84832ee Fix a situation where secondary pool device is missing and the Array is chosen for the primary device.. 2024-10-03 13:57:21 -05:00
Tom Mortensen
959df7e46c fx: Agent notifications don't work if there's a problem with email notifications 2024-10-02 09:55:44 -07:00
Tom Mortensen
3fb6c2147b Small change of var name CUSTOMFA to NGINX_CUSTOMFA. 2024-10-02 09:55:44 -07:00
Tom Mortensen
3db6fa9a1d update rc.bind (but not used) 2024-10-02 09:55:44 -07:00
tom mortensen
0c5987fab0 Merge pull request #1811 from unraid/kill-samba
Forcibly kill samba if needed
2024-10-02 09:52:46 -07:00
tom mortensen
fcbc8f700e Merge pull request #1876 from dkaser/check-container-networks
fix: prevent deleting containers that are assigned as a network
2024-10-01 14:00:08 -07:00
Derek Kaser
6fd88575b2 fix: prevent deleting containers that are assigned as a network 2024-10-01 04:21:51 +00:00
tom mortensen
bff0238f88 Merge pull request #1875 from dkaser/sanitize-tsnet
feat: sanitize ts.net domain names from nginx config
2024-09-30 15:45:50 -07:00
Derek Kaser
ee7f1f4a5b feat: sanitize ts.net domain names from nginx config 2024-09-30 02:29:37 +00:00
tom mortensen
c3dd62f1d5 Merge pull request #1874 from dlandon/container_device_not_showing
Container device not showing in Dockerman.
2024-09-27 09:19:24 -07:00
tom mortensen
7c0fb18e3c Merge pull request #1873 from unraid/add-csp-frame-ancestors
Improved "Content-Security-Policy frame-ancestors" support
2024-09-27 09:15:29 -07:00
ljm42
fe2e2ff897 define CUSTOMFA in /etc/defaults/nginx 2024-09-26 16:03:36 -07:00
ljm42
74530129ae Merge branch 'master' into add-csp-frame-ancestors 2024-09-26 15:52:44 -07:00
tom mortensen
968e3b1d72 Merge pull request #1870 from dkaser/tailscale-routes
feat: show routes from all routing tables, not just default
2024-09-26 12:25:30 -07:00
tom mortensen
6bdcb38c47 Merge pull request #1869 from ich777/ich777-rc.sshd-fix
Fix for rc.sshd
2024-09-26 12:25:09 -07:00
tom mortensen
8c7cdca4aa Merge pull request #1868 from unraid/ts-redirect
redirect http TS url to https TS url
2024-09-26 12:24:42 -07:00
tom mortensen
08024a0464 Merge pull request #1867 from ich777/ich777-docker-fix
Fix for 3rd party containers "not available" colour
2024-09-26 12:24:05 -07:00
dlandon
828cd7b747 Container device not showing in Dockerman. 2024-09-26 09:13:42 -05:00
ljm42
0ce3960de6 Improved "Content-Security-Policy frame-ancestors" support 2024-09-25 23:16:52 -07:00
tom mortensen
8b91d22796 Merge pull request #1872 from ich777/ich777-rc.docker-patch
Update rc.docker
2024-09-25 10:36:52 -07:00
b716920800 Update rc.docker
- Remove ID since not necessary
2024-09-24 16:55:21 +02:00
6d749a8b1a Update rc.docker
- Further improvement to stop containers only managed by Unraid or the Compose plugin
- Small fix for the notification
2024-09-24 16:46:02 +02:00
69b95ae27d Update rc.docker
- Only stop Unraid managed containers
- Don't kill containers since Docker will kill them if they won't stop after the set timeout when the daemon is stopping
- Increase timeout for daemon to die to 30 seconds (seems a bit short if 3rd party containers are installed)
- Rephrase message for daemon to die and display it only once
2024-09-24 15:51:46 +02:00
Derek Kaser
20e29ab5af feat: show routes from all routing tables, not just default 2024-09-23 21:32:13 +00:00
a75bc3d4d7 Fix for rc.sshd
- Remove trailing slash to be sure to grab the correct PIDs
2024-09-23 22:16:02 +02:00
ljm42
e8e5ccdf18 redirect http TS url to https TS url 2024-09-23 12:59:05 -07:00
020ed9a07f Small fix for 3rd party containers
- Fix orange text "not available" to use the default text colour.
2024-09-23 21:19:04 +02:00
Tom Mortensen
8f656e87b1 establish sensible zfs pool defaults 2024-09-23 10:57:13 -07:00
Tom Mortensen
54b1e81b38 unconditinally set directories to mode 0777 2024-09-23 10:54:57 -07:00
Tom Mortensen
4c6be23467 Clean up empty cgroups 2024-09-20 09:20:44 -07:00
Tom Mortensen
4a4983f7c5 rc.S: reboot should not invoke shutdown 2024-09-20 09:20:44 -07:00
tom mortensen
dcfaa1afa0 Merge pull request #1866 from SimonFair/VM-ZFS-Fixes
Fix VM usage stats value if timer > 1 sec.
2024-09-20 09:19:46 -07:00
SimonFair
e52813b626 Divide net and disk values by timer 2024-09-19 22:16:00 +01:00
tom mortensen
760aac71df Merge pull request #1863 from dlandon/default_array_file_system_message
Change default file system text.
2024-09-16 14:58:28 -07:00
tom mortensen
20ef176665 Merge pull request #1862 from ich777/patch-4
Fix for rc.docker for interfaces with higher index than 0
2024-09-16 14:57:48 -07:00
tom mortensen
66d7193dab Merge pull request #1858 from ich777/rc.sshd_fix
Only kill sshd from host
2024-09-16 14:56:54 -07:00
tom mortensen
9c9c79b1b3 Merge pull request #1857 from ich777/ich777-docker-container-network
Allow users to select Container networks in the WebUI
2024-09-16 14:55:54 -07:00
tom mortensen
8aac4ee119 Merge pull request #1856 from desertwitch/patch-1
en_US/helptext.txt: Fix broken link in GUI
2024-09-16 14:55:00 -07:00
tom mortensen
8120959c2f Merge pull request #1854 from desertwitch/fix-php-warning
dynamix/include/SysDevs.php: fix PHP warnings
2024-09-16 14:54:20 -07:00
tom mortensen
72abe50721 Merge pull request #1853 from Commifreak/libvirt-log-timeout-info
Add a log line noting how long we wait currently for a VM action.
2024-09-16 14:52:55 -07:00
tom mortensen
18e37ed045 Merge pull request #1852 from dkaser/tailscale-nginx
Add fastcgi_path_info to default nginx configuration
2024-09-16 14:51:36 -07:00
tom mortensen
6845c007a7 Merge pull request #1851 from ich777/page-builder-fix
Fix issue with plugin icons
2024-09-16 14:50:50 -07:00
tom mortensen
b844f941d0 Merge pull request #1850 from unraid/add-ts-certs
Add support for Tailscale certs in the webgui
2024-09-16 14:49:59 -07:00
tom mortensen
5883e767aa Merge pull request #1849 from dlandon/parity-check-shows-incorrect-results
Parity check wrong again.
2024-09-16 14:46:15 -07:00
tom mortensen
efc4fa2673 Merge pull request #1848 from ich777/ich777-not-dockerman-managed
Correctly identify/show non dockerman Managed containers
2024-09-16 14:45:11 -07:00
tom mortensen
0810fc5bd8 Merge pull request #1847 from SimonFair/VM-ZFS-Fixes
VM ZFS  dataset removal processing additional fixes
2024-09-16 14:44:14 -07:00
tom mortensen
897365a5de Merge pull request #1845 from Squidly271/patch-42
Adjust URL for CA installation
2024-09-16 14:42:52 -07:00
dlandon
8d628aad4f Change default file system text. 2024-09-13 11:47:57 -05:00
e3c4ff280d Small fix 2024-09-12 23:33:27 +02:00
19de7c1979 Fix for rc.docker for interfaces with higher index than 0
- Fix error in first if condition which always returned `false`
- Change name from variables so that it a bit easier to read
- Make sure that custom interfaces with a higher index then 0 are properly rebuilt
2024-09-12 23:12:03 +02:00
5ec695921a Fix sshd_update logic 2024-09-11 07:39:34 +02:00
171a77feec Change function
- return `1` if daemon is running and return `0` if it is not running
2024-09-11 07:38:15 +02:00
3e29f0b8b8 Only kill sshd from host 2024-09-10 22:32:07 +02:00
b62c8f5a12 Update Helpers.php 2024-09-09 17:09:15 +02:00
e9faee0d27 Update CreateDocker.php 2024-09-09 17:07:43 +02:00
Rysz
16823d07b1 en_US/helptext.txt: Fix broken link 2024-09-09 06:56:35 +02:00
desertwitch
73705b71fa dynamix/include/SysDevs.php: fix PHP warnings
test for exec-returned variable existence before usage
2024-09-08 20:21:18 +02:00
Robin Kluth
6b31532688 Add a log line noting how long we wait currently for a VM action. 2024-09-06 15:46:06 +02:00
Derek Kaser
039c798b43 Add fastcgi_path_info to default nginx configuration 2024-09-05 22:00:20 +00:00
1e43abc785 Fix issue with plugin icons
- make sure the maximum icon size for a plugin is 18x18px
2024-09-05 10:16:51 +02:00
ljm42
9b1081d2e1 Add support for Tailscale certs in the webgui 2024-09-04 21:37:08 -07:00
SimonFair
c27e018fdb Reduce Multifunction starting bus. 2024-09-04 21:11:44 +01:00
dlandon
ef5067584b Change file_put_contents_atomic() back to file_put_contents() since it is liable to change. 2024-09-04 12:34:23 -05:00
SimonFair
4ea425411a Add disable rename if snapshots found. 2024-09-03 21:45:50 +01:00
dlandon
24bdc5169d Parity check wrong again. 2024-09-03 15:36:12 -05:00
SimonFair
753d87c690 Additional VM ZFS delete fixes 2024-09-02 18:03:10 +01:00
07d02f579f Add files via upload 2024-09-02 17:48:25 +02:00
SimonFair
ceb97ab392 Disable dataset processing. 2024-09-01 21:17:05 +01:00
Squidly271
6a15afa2a8 Update Apps.page 2024-09-01 15:57:02 -04:00
tom mortensen
175d24afd2 Merge pull request #1842 from dlandon/rework-share-list
Fix condition where useCache is 'no' when there is no array.
2024-08-30 13:00:58 -07:00
Tom Mortensen
12828eec63 stop ntpd from complaining about multiple IP addresses 2024-08-30 09:04:48 -07:00
dlandon
156599031a Fix condition where useCache is 'no' when there is no array. 2024-08-30 07:25:33 -05:00
tom mortensen
15f4138c87 Merge pull request #1839 from SimonFair/VM-ZFS-Fixes
ZFS fixes
2024-08-29 11:58:12 -07:00
tom mortensen
fd6e4f1ba1 Merge pull request #1840 from dlandon/rework-share-list
Fix share list.
2024-08-29 11:57:17 -07:00
tom mortensen
72a47035ef Merge pull request #1841 from dlandon/rename-pool-message
Add warning message to rename pool dialog.
2024-08-29 11:55:57 -07:00
dlandon
d57bf205fa Add warning message to rename pool dialog. 2024-08-29 13:23:38 -05:00
dlandon
16a8e7092d Fix share list not showing invalid storage configurations and some code cleanup. 2024-08-29 13:00:06 -05:00
SimonFair
00b1f77742 ZFS fixes 2024-08-29 17:45:00 +01:00
Tom Mortensen
506270e413 Fixes zfs subpool creation issue.
Fixes DeviceInfo not populating for array devices when Started.
2024-08-29 02:04:38 -07:00
Tom Mortensen
303c76d7da rc.nfsd: the 'update' method only causes re-read of /etc/exports 2024-08-29 02:03:51 -07:00
Tom Mortensen
73ea1bb7b3 remove deprecated check 2024-08-29 02:02:55 -07:00
tom mortensen
9d4ca6a2c9 Merge pull request #1837 from ich777/docker_fix
Make sure to not show internal IP from stopped containers
2024-08-29 01:25:29 -07:00
49b82d0eb8 Make sure to not show internal IP from stopped containers 2024-08-28 20:33:25 +02:00
tom mortensen
106f155ecc Merge pull request #1835 from donbuehl/feature/1832
Feature Request: Use user's actual shell in OpenTerminal.php #1832
2024-08-27 21:14:52 -07:00
donbuehl
53704b58aa Final minimal viable solution 2024-08-27 21:43:46 +02:00
tom mortensen
da9add3637 Merge pull request #1836 from desertwitch/feature-default-file
update.php: add two optional methods to define defaults
2024-08-27 12:18:16 -07:00
donbuehl
db77c13552 MVP solution for zsh plugin
Implemented a minimal viable solution for the zsh plugin:
- Checks if the shell is /bin/zsh
- Falls back to bash if not

This approach prioritizes simplicity and reliability over a more general solution to avoid potential errors.
2024-08-26 14:29:24 +02:00
donbuehl
be22c0e1f8 Simplify shell detection for openterminal 2024-08-25 15:44:28 +02:00
donbuehl
cd9d20eaf3 Simplify shell detection for openterminal 2024-08-25 15:19:53 +02:00
Rysz
c4afbba9bc update.php: do not silence parse errors 2024-08-25 13:16:36 +02:00
Rysz
16089cd927 update.php: shorten var name and improve documentation 2024-08-25 13:13:26 +02:00
Rysz
1ede8e621b update.php: remove unneeded spaces 2024-08-25 08:43:54 +02:00
Rysz
64ead9a127 update.php: additional safeguards 2024-08-25 08:28:40 +02:00
Rysz
ff80906d11 update.php: add #defaultvalues parameter 2024-08-25 07:36:23 +02:00
tom mortensen
8981e8bb15 Merge pull request #1834 from ich777/grub_cpuisolation_fix
GRUB compatibility changes
2024-08-24 09:18:04 -07:00
tom mortensen
83675005d2 Merge pull request #1833 from SimonFair/VM-Unmap-support
Fix vmusage stopping/Unmap Defaults and Fix Multifunction.
2024-08-24 09:17:10 -07:00
SimonFair
4b7f2bfcee Default new disk to unmap. 2024-08-23 07:30:11 +01:00
SimonFair
72ff3c52c0 Unmap Default and Multifuncton
Set default for new VMs to discard="unmap"
Fix BSOD for VMs and Multifunction.
2024-08-22 23:24:18 +01:00
Tom Mortensen
69e11713e5 Dashboard: Soften messages during parity sync/check 2024-08-21 23:28:44 -07:00
desertwitch
67cf2db493 update.php: clarify paths in documentation 2024-08-21 12:38:40 +02:00
desertwitch
631479d27d update.php: follow #file behavior for more versatility 2024-08-21 12:36:56 +02:00
desertwitch
28b3d2ae71 update.php: clarify documentation comment 2024-08-21 12:02:09 +02:00
desertwitch
ec1689dc68 update.php: null coalescing to elvis operator 2024-08-21 11:11:47 +02:00
desertwitch
3615992dc4 update.php: add #defaultfile parameter 2024-08-21 10:51:37 +02:00
d9f83cc76b Fix diagnostics to support GRUB 2024-08-21 07:54:31 +02:00
donbuehl
7e6ad9512d Wrap getUserShell() call with escapeshellarg() for additional security 2024-08-20 09:08:31 +02:00
donbuehl
d7b4dfd44b Fixed missing $ in variable 2024-08-19 22:14:46 +02:00
donbuehl
c62ef28fc3 Refactor getUserShell() for improved clarity and maintainability
- Introduce  variable for better code readability
- Simplify return logic using the default shell variable
- Maintain comprehensive error handling with Throwable
2024-08-19 22:06:41 +02:00
donbuehl
07fa790411 Improve getUserShell() function for accuracy and consistency
- Refine username matching to prevent partial matches
- Use English for syslog error message
2024-08-19 21:49:31 +02:00
donbuehl
fb19a99ad4 Added function getUserShell() to OpenTerminal.php and use it for the execution 2024-08-19 18:16:38 +02:00
d0dcf6c314 Fix for GRUB bootloader 2024-08-19 16:58:31 +02:00
0d925a2471 Fix for GRUB bootloader 2024-08-19 16:54:19 +02:00
SimonFair
2b4eb1abad Fix vmusage stopping when you on a new page on a tab. 2024-08-19 13:16:50 +01:00
tom mortensen
251881d850 Merge pull request #1831 from SimonFair/VM-Unmap-support
Add Unmap Support
2024-08-18 10:34:35 -07:00
tom mortensen
24fce7582c Merge pull request #1828 from mtongnz/network-display-improvements
Network info display improvements (part 2)
2024-08-17 08:28:45 -07:00
SimonFair
b56f3e529c Add Discard option. 2024-08-17 00:34:52 +01:00
tom mortensen
130e3fcd44 Merge pull request #1829 from desertwitch/fix-php-settings
fix inconsistent PHP error reporting defaults (PHPSettings.page)
2024-08-16 09:15:35 -07:00
mtongnz
c1b2bb7435 fix: better display of container LAN IP for ipvlan & macvlan 2024-08-16 18:20:10 +12:00
mtongnz
840e19d322 fix: some networks not showing 2024-08-16 17:35:22 +12:00
SimonFair
2ae85fdd31 Add unmap on VM change. 2024-08-15 22:36:15 +01:00
SimonFair
0db0032648 Add Unmap Support 2024-08-15 19:29:20 +01:00
desertwitch
8d9e2a04c0 PHP constants for better read-/maintainability 2024-08-15 16:49:32 +02:00
desertwitch
31f81349a8 sanitize PHP setting constants for update.php
update.php encapsulates the PHP constants used by the OS default PHP error reporting level with double quotes, which breaks functionality as they are then treated as text. by turning the PHP constants into strings first this problem is avoided.
2024-08-15 16:29:45 +02:00
desertwitch
3de8e05432 fix inconsistent PHP error reporting defaults 2024-08-15 14:08:58 +02:00
Matt
1c019c8f08 fix: display LAN:IP for multiple external networks 2024-08-15 18:50:44 +12:00
Matt
8d76d6f1cc fix: display multiple networks for all types
This commit ensures containers with multiple networks are always displayed.
Previously, networks connected to containers (docker network connect...) don't show
2024-08-15 15:55:01 +12:00
tom mortensen
3e17f35e19 Merge pull request #1826 from zackspear/refactor/registration-page-transfer-ineligible-copy
refactor: registration transfer check ineligible copy
2024-08-14 11:15:46 -07:00
Zack Spear
6f51589547 refactor: registration page transfer check ineligible copy 2024-08-13 17:28:29 -07:00
Zack Spear
b18e734381 chore: update .gitignore for prettier usage 2024-08-13 17:27:26 -07:00
tom mortensen
b5a8223ffe Merge pull request #1820 from Garbee/ssh-key-regex
feat(user-edit): update allowed ssh key encryptions
2024-08-13 12:46:39 -07:00
tom mortensen
936adea879 Merge pull request #1824 from unraid/fix-typo
Fix typo in help text
2024-08-13 12:44:31 -07:00
tom mortensen
1572378824 Merge pull request #1822 from SimonFair/VM-Manager-PHP-Fixes
Add ZFS Checking
2024-08-13 12:39:12 -07:00
Tom Mortensen
0cf3585a0d update error messsage 2024-08-13 12:37:57 -07:00
Tom Mortensen
3f103f2089 DeviceInfo page improvements:
* Present confirmation dialog for Delete Pool operation
* Fix expansion of single device pool to multi-device zfs mirror or btrfs raid-1
2024-08-13 12:28:28 -07:00
Tom Mortensen
ed308c3a69 Display warning along side replacement and expansion devivces on Main if they will be initialized 2024-08-13 12:28:28 -07:00
Tom Mortensen
985d077af5 Samba smb.conf: set "nmbd bind explicit broadcast = no" if NetBIOS enabled 2024-08-13 12:28:28 -07:00
Tom Mortensen
86b8b170d1 Startup improvements in rc.S script:
* Automatically repair boot sector backup
* Detect bad root value in syslinux.cfg
* Explicitly unmount all file systems if cannot continue boot
2024-08-13 12:28:28 -07:00
Tom Mortensen
d351e51f58 remove wol source 2024-08-13 12:28:28 -07:00
tom mortensen
53c1788580 Merge pull request #1821 from Garbee/remove-document-write
fix: replace document write usages
2024-08-13 12:27:42 -07:00
tom mortensen
0870461731 Merge pull request #1818 from ich777/grub_vm_settings_fix
Grub VM settings fix
2024-08-13 10:27:49 -07:00
ljm42
043d2baaf7 Fix typo in help text 2024-08-12 12:21:57 -07:00
SimonFair
32bb9bb6d9 Add ZFS Checking
This will check VM name does not include characters that are not valid for ZFS.
Existing VMs are not modified but will throw error and disable update if invalid characters are found.
2024-08-12 17:18:28 +01:00
Jonathan Garbee
2ff8b77c9d fix: replace document write usages 2024-08-10 15:35:49 +00:00
Jonathan Garbee
654db74167 feat(user-edit): update allowed ssh key encryptions 2024-08-10 14:51:06 +00:00
fb2b66b5b0 Make VM Settings work with grub 2024-08-09 12:38:18 +02:00
663665a61b Make VM Settings work when using GRUB 2024-08-09 12:37:00 +02:00
tom mortensen
c9333ea955 Merge pull request #1616 from mtongnz/network-display-improvements
Network info display improvements
2024-08-08 23:16:15 -07:00
tom mortensen
bad23e7647 Merge pull request #1817 from dlandon/master
Sort users in natural order on shares page.
2024-08-08 09:48:11 -07:00
17909f889e Fix PHP Warning (#3)
- Fix PHP Warnings on Add Container page
2024-08-08 20:32:38 +12:00
8cabad6f0d Changes (#2)
* Changes

- Revert commit #ed5fe721d9cdfeb65148a5f12eebdf35729e1dfb
- Fix php errors

* Update DockerContainers.page

- Change `External IP:Port` to `LAN IP:Port`
2024-08-08 16:58:58 +12:00
dlandon
566113f86c Sort users in natural order. 2024-08-07 16:56:09 -05:00
tom mortensen
21e640184e Merge pull request #1814 from suzukua/master
Fixed an issue where routes could not be saved in languages ​​other t…
2024-08-07 10:07:55 -07:00
suzuki
202196f7d3 Fixed an issue where routes could not be saved in languages ​​other than English
fix  https://forums.unraid.net/bug-reports/stable-releases/%E3%80%9061211%E3%80%91%E3%80%90bug%E3%80%91cannot-save-static-route-r3156/
2024-08-03 22:17:51 +08:00
9c2aa45751 Minor improvements (#1)
* few changes, please test

* Change weird formatting

* Another formatting change

* Fix for `container:` networks

* Hide IPs/Ports when container is not running

* WebUI fix

- generate WebUI links correctly
- don't show WebUI from containers which use a `container:` network
2024-08-03 17:39:00 +12:00
ljm42
33a73b2fb5 Forcibly kill samba if needed 2024-08-02 10:33:34 -07:00
tom mortensen
06735f7a3e Merge pull request #1805 from SimonFair/VM-Manager-PHP-Fixes
Fix delete VM if in a dataset.
2024-08-01 14:18:51 -07:00
tom mortensen
e7f7ef43c0 Merge pull request #1809 from desertwitch/patch-1
update.php: release file pointer after usage
2024-08-01 14:13:53 -07:00
tom mortensen
c5b692e805 Merge pull request #1808 from ich777/overlay2-gui-support
Overlay2 GUI support
2024-08-01 12:30:57 -07:00
SimonFair
6a91f270e3 Fix Audio and PCI for Mutlifunction. 2024-08-01 19:13:14 +01:00
Rysz
6d3a7a3298 release file pointer after usage 2024-08-01 10:53:47 +02:00
f81118ffe3 Remove unnecessary comment 2024-07-31 21:33:50 +02:00
f9107ebe11 Always display storage driver when Docker is active 2024-07-31 12:02:05 +02:00
76f58d2995 overlay2 default 2024-07-31 11:05:54 +02:00
b4722f57aa Set default backingfs type to native for existing installations 2024-07-31 10:59:38 +02:00
3314860d31 Change the default to overlay2 2024-07-31 10:56:03 +02:00
9a6b62ae98 Bugfix for new installations 2024-07-31 10:55:30 +02:00
e0b1612633 Add helptext for docker storage driver when active 2024-07-30 21:26:54 +02:00
1e41ac637d Bugfix for helptexts 2024-07-30 21:26:10 +02:00
858a3aa999 Display storage driver with started array 2024-07-30 21:19:57 +02:00
79c484e2e5 Add helptext for Docker storage driver 2024-07-30 19:22:40 +02:00
49793ff602 Add DOCKER_BACKINGFS with native option 2024-07-30 19:07:49 +02:00
342619a567 Add WebUI option for overlay2
- switch between native and overlay2 storage drivers
2024-07-30 19:05:39 +02:00
mtongnz
27047c8832 fix: improve IP & port display further as per suggestions 2024-07-28 11:18:31 +12:00
tom mortensen
b850940f19 Merge pull request #1793 from jbtwo/fix-ups-settings-page
Fix UPS settings page when config contains empty values
2024-07-27 01:11:47 -07:00
tom mortensen
de3334b0d2 Merge pull request #1803 from bergware/master
Fix broken "show_interfaces" script (take 2)
2024-07-27 01:11:10 -07:00
tom mortensen
373bb9f2ee Merge pull request #1804 from unraid/detect-bad-root-in-syslinux
Detect bad root value in syslinux.cfg
2024-07-27 01:10:26 -07:00
bergware
833194705c Merge remote-tracking branch 'upstream/master' 2024-07-25 18:39:11 +02:00
Justin Brown
392cc77a20 switch to _var function for setting batteryLevel & batteryRuntime 2024-07-24 16:57:35 -04:00
SimonFair
d272bf78ac Fix delete VM if in a dataset. 2024-07-24 21:20:06 +01:00
ljm42
9430366eaf Detect bad root value in syslinux.cfg 2024-07-24 10:58:15 -07:00
bergware
71dc414592 Fix broken "show_interfaces" script (take 2) 2024-07-24 13:57:00 +02:00
Tom Mortensen
2b41f9a8d2 Device Info page:
* Prevent reducing pool slot count unless pool state is NEW_ARRAY
* Permit changing pool file system type only when Stopped
* Add "Delete Pool" button which unassigns all devices of a pool and then removes the pool
* Change button label "Erase" to "Erase Pool"
2024-07-24 02:13:22 -07:00
Tom Mortensen
70c01ec454 Include subpool devices in list of devices to be formatted when appropriate 2024-07-24 02:12:58 -07:00
Tom Mortensen
7840ae6d3c new handling of 'ntp.conf' and 'ntp.conf-' (similar to exports) 2024-07-24 00:41:27 -07:00
Tom Mortensen
5b009dfb39 remove unnecessary symlinks 2024-07-24 00:41:27 -07:00
tom mortensen
5565c02f74 Merge pull request #1796 from desertwitch/desertwitch-patch-1
fix table order breaking when cookie is left malformed
2024-07-24 00:37:19 -07:00
tom mortensen
793289bc7f Merge pull request #1792 from dlandon/master
Fix overlapping text on VPN Manager page; php warning; allow UD disks to show in file picker.
2024-07-24 00:31:21 -07:00
tom mortensen
7d92761860 Merge pull request #1797 from SimonFair/VM-Manager-PHP-Fixes
Fix memory Stats in VM Usage.
2024-07-24 00:29:01 -07:00
tom mortensen
1021adc33b Merge pull request #1799 from dlandon/diagnostocs-ipv6-fix
Diagnostics ipv6 fix
2024-07-24 00:28:11 -07:00
tom mortensen
c9374f7911 Merge pull request #1800 from unraid/use-docs-go-links
Use "go links" when linking to Docs
2024-07-24 00:27:20 -07:00
tom mortensen
73a17a0306 Merge pull request #1802 from bergware/master
Fix broken "show_interface" script
2024-07-24 00:26:44 -07:00
bergware
0729386af9 Fix broken "show_interface" script 2024-07-23 19:41:02 +02:00
bergware
4b8ec6e5a3 Fix broken "show_interface" script 2024-07-23 19:34:40 +02:00
dlandon
ea2fa8a8db Chmod on non-existent file causing php warning. 2024-07-22 09:58:06 -05:00
dlandon
d7e474257c Allow UD disks to be listed in file picker. 2024-07-21 04:55:49 -05:00
mtongnz
4b3e8f2d46 fix: handle webUI link generation correctly 2024-07-20 13:23:56 +12:00
mtongnz
54a6e3dd13 fix: better display of ip & ports to avoid beginner confusion - from suggestions by @ich777 2024-07-20 12:04:30 +12:00
SimonFair
e20f37d936 Update emhttp/plugins/dynamix/nchan/vm_dashusage
Co-authored-by: Shay Levy <levyshay1@gmail.com>
2024-07-19 21:04:17 +01:00
ljm42
4c1c566e78 Add trailing slash to go links
Eliminates an unnecessary redirect
2024-07-19 12:02:00 -07:00
ljm42
a6c50b208a Add trailing slash to go links
Eliminates an unnecessary redirect
2024-07-19 12:00:36 -07:00
SimonFair
9a23761dc8 Update vm_dashusage 2024-07-19 19:44:59 +01:00
dlandon
359334b85a Fix another php error. 2024-07-19 12:55:45 -05:00
SimonFair
f5ed6964dd Update vm_usage 2024-07-19 18:55:03 +01:00
SimonFair
882ee7f911 Hide Max if Current and Max are equal 2024-07-19 18:30:45 +01:00
ljm42
234a749b7f Use "go links" when linking to Docs 2024-07-19 10:01:50 -07:00
dlandon
a9891f557e Merge branch 'unraid:master' into master 2024-07-19 10:33:48 -05:00
dlandon
04640a5708 Anonimize IPv6 addresses with square braces. 2024-07-19 10:31:06 -05:00
SimonFair
fac92be4d4 Fix memory Stats in VM Usage.
Three columns inuse/Current/Maximum
2024-07-19 14:21:09 +01:00
Rysz
817ed5c1c1 fix table order breaking when cookie is left malformed 2024-07-18 22:13:13 +02:00
Tom Mortensen
a4abe0fa55 update firefox symlink 2024-07-18 12:14:35 -07:00
dlandon
6920542d6c Merge branch 'unraid:master' into master 2024-07-18 06:14:40 -05:00
Justin Brown
36f9e7402c fix UPS settings page when config contains empty values 2024-07-16 19:32:30 -04:00
dlandon
cfa547972e Fix overlapping text on VPN Manager page. 2024-07-16 13:24:15 -05:00
tom mortensen
84b5baf402 Merge pull request #1791 from ich777/docker-dashboard-patch
Show Docker RAM usage on Dashboard & minor changes
2024-07-16 10:01:16 -07:00
tom mortensen
1b162e2c21 Merge pull request #1790 from dlandon/master
Fix php warning in device_list; VPN Manager not showing tunnel status.
2024-07-16 10:00:44 -07:00
tom mortensen
75f0c2e4f6 Merge pull request #1788 from ich777/initial-overlay2-support
Initial Docker overlay2 support
2024-07-16 09:59:39 -07:00
tom mortensen
67369622fa Merge pull request #1787 from ich777/ipv6-forward
Add IPv6 forward rule
2024-07-16 09:58:49 -07:00
tom mortensen
17fdfb6f50 Merge pull request #1786 from ich777/proxy-fix
fix path
2024-07-16 09:58:10 -07:00
tom mortensen
9e80fb13c7 Merge pull request #1783 from SimonFair/VM-Manager-PHP-Fixes
Move restore favorites after plugin installs + VM Template formatting for PCI others
2024-07-16 09:57:34 -07:00
tom mortensen
1d6c3f4375 Merge pull request #1782 from zackspear/fix/vm-icon-edit
fix: AddVM & UpdateVM icon selection styles
2024-07-16 09:57:10 -07:00
58c31f4ef6 Show Docker RAM usage on Dashboard
- Add Docker RAM usage to Dashbaord page
- Rename 'Services' to 'System'
- Remove 'usage' suffix from Services naming scheme
2024-07-16 13:57:21 +02:00
dlandon
84b767ddcd Fix php[ warning. 2024-07-15 17:32:11 -05:00
dlandon
32d3f88b44 VPN Manager not showing tunnel status. 2024-07-15 17:28:20 -05:00
Zack Spear
6156a2582f fix: UpdateVM.css temlpate img chooser 2024-07-15 13:43:22 -07:00
SimonFair
8a55228b8e Update VMedit.php 2024-07-15 18:59:56 +01:00
SimonFair
fe7ab1fc43 FIX Issue with VM Template formatting for other PCI 2024-07-15 18:41:29 +01:00
30492ed2f2 Initial Docker overlay2 support
- override filesystem type to overlay2 if `DOCKER_BACKINGFS` in docker.cfg is set to `overlay2`
2024-07-15 17:40:10 +02:00
da1ef5c0e0 IPv6 forward
- Add `IPV6_FORWARD` to rc.docker and set it to `ACCEPT` instead of `DROP`
2024-07-15 17:22:48 +02:00
93054c2091 fix path
- fix path for generated proxy file
2024-07-15 15:47:50 +02:00
SimonFair
f2abfaf292 Move restore favorites after plugin installs 2024-07-13 15:33:37 +01:00
Zack Spear
5a42314c60 fix: AddVM & UpdateVM icon selection styles 2024-07-12 14:55:00 -07:00
Tom Mortensen
bbed6047e3 Introduce 'Allocation profile' config setting for specifying pool/subpool data layout
Fix: spindown delay setting missing for parity devices
2024-07-12 11:53:32 -07:00
Tom Mortensen
a7f6ac7389 maybe needed in future 2024-07-10 23:01:09 -07:00
Tom Mortensen
f7748f7619 make executable 2024-07-10 23:00:20 -07:00
Tom Mortensen
d4968e1b19 simplify display of file system type on Main 2024-07-10 22:59:53 -07:00
Tom Mortensen
fb680469ac fix git glitch which resulted in file not updated 2024-07-10 22:58:42 -07:00
tom mortensen
eaabbec7e0 Merge pull request #1780 from unraid/fix-trim
Make trim consistent between "trim now" and cron job
2024-07-10 21:41:39 -07:00
ljm42
693a0260af Make trim consistent between "Run now" and cron job 2024-07-10 17:59:15 -07:00
tom mortensen
130c9c6373 Merge pull request #1779 from ich777/ich777-agents-fix
Fix for notification agents
2024-07-10 14:22:17 -07:00
73c264e9fe Fix
- Fix PushBits and Pushbullet not showing up
2024-07-10 14:47:06 +02:00
667741129a Fix
- Fix PushBits and Pushbullet not showing up
2024-07-10 14:45:49 +02:00
tom mortensen
42fe45595f Merge pull request #1778 from dlandon/master
Parity check does not show completed on array operations page.
2024-07-09 13:44:58 -07:00
dlandon
ed9b8322d4 Fix clobbering CAs $cfg variable. 2024-07-09 14:57:58 -05:00
dlandon
cfb2daa531 Update help text for PID Limit. 2024-07-09 06:37:58 -05:00
dlandon
694d35b412 Parity check does not show completed on array operations page. 2024-07-09 06:21:29 -05:00
tom mortensen
4011d942ce Merge pull request #1775 from dlandon/master
Mover status button on 'Array Operation' page; Docker PID Limit setting.
2024-07-08 09:27:58 -07:00
tom mortensen
5b95526e84 Merge pull request #1774 from SimonFair/VM-Manager-PHP-Fixes
Updates to rc.apcupsd and VM updates
2024-07-08 09:26:28 -07:00
SimonFair
667418e656 Fix for multiple nics when bridge mode changes. 2024-07-07 15:39:31 +01:00
SimonFair
c0d4e1de1b Support Blockpull and Blockcommit for stopped VMs. 2024-07-07 14:57:04 +01:00
SimonFair
ec7ec769f4 Fix for guest not init display. 2024-07-07 14:27:59 +01:00
dlandon
1524f2d69a Add "Docker PID Limit" to limit docker container PIDs. 2024-07-05 03:47:51 -05:00
dlandon
c9109d61bf Set default value for DOCKER_PID_LIMIT. 2024-07-05 03:47:09 -05:00
dlandon
a9a2b73849 Add help text for PID limit. 2024-07-05 03:46:29 -05:00
Tom Mortensen
b1b76d8ffa set_proxy script moved 2024-07-04 09:31:58 -07:00
SimonFair
891ee6beff Update rc.apcupsd 2024-07-03 22:40:50 +01:00
SimonFair
bd4eb4189b remove config check 2024-07-03 22:39:40 +01:00
dlandon
24fd56fff3 Monitor the var 'shareMoverActive' instead of the mover.pid file. 2024-07-03 16:12:03 -05:00
SimonFair
45d71932a1 Updates to rc.apcupsd 2024-07-03 21:39:51 +01:00
tom mortensen
f8ad3105c7 Merge pull request #1773 from dlandon/master
Parity display on dashboard.
2024-07-03 11:06:19 -07:00
dlandon
a8adba6728 Dashboard doesn't show all of parity status. 2024-07-03 12:19:38 -05:00
tom mortensen
6ab9f0a017 Merge pull request #1772 from dlandon/master
Move set_proxy script; dashboard not showing correct parity info.
2024-07-03 05:50:46 -07:00
dlandon
4904a5021c Dashboard not showing correct parity info. 2024-07-03 05:51:05 -05:00
dlandon
859e697413 Make script executable 2024-07-02 15:49:16 -05:00
dlandon
c60cbb5c6e Move set_proxy script. 2024-07-02 15:37:50 -05:00
tom mortensen
4a3a1f526d Merge pull request #1770 from dlandon/master
Fix tile management showing a blank dialog.
2024-07-02 08:49:23 -07:00
tom mortensen
38758462f7 Merge pull request #1769 from SimonFair/VM-Manager-PHP-Fixes
Fix broken helptext
2024-07-02 08:48:07 -07:00
dlandon
7e8b8e0a0a Fix tile management showing a blank dialog. 2024-07-01 14:33:12 -05:00
Tom Mortensen
2f2f50d178 Imp: Speed things up: use AVAHI reload instead of restart
Reduce size of samba secrets.tdb file when saving to flash.
2024-07-01 12:32:41 -07:00
Tom Mortensen
69d2996c31 Fix: ZFS striped pool shows as raidz, but it's still a stripe 2024-07-01 12:31:38 -07:00
SimonFair
f4a8bf0d81 Fix broken helptext 2024-07-01 20:16:25 +01:00
tom mortensen
9d63b5ecf8 Merge pull request #1768 from dlandon/master
Fix share edit updates failing with languages other that English.
2024-07-01 10:48:04 -07:00
dlandon
95353c143a Fix translation of 'Apply' when performing 'POST' that blocks updating; some code cleanup to combine messages and variable scope. 2024-07-01 12:12:09 -05:00
tom mortensen
925fff60a9 Merge pull request #1767 from SimonFair/VM-Manager-PHP-Fixes
Change to be enabled rather than disabled.
2024-06-30 17:12:48 -07:00
SimonFair
8965860224 Change to be enabled rather than disabled. 2024-06-30 21:03:03 +01:00
tom mortensen
9d15302bf0 Merge pull request #1766 from dlandon/master
Remove debug from share edit used for subpools removed from primary and secondary drop downs; fix incorrect commit.
2024-06-30 10:35:47 -07:00
dlandon
6bb27eec3a Fix incorrect commit to fix exit status not returned. 2024-06-30 12:27:43 -05:00
dlandon
84ad25fe18 Remove debug. 2024-06-30 11:40:59 -05:00
tom mortensen
9f9429424f Merge pull request #1763 from SimonFair/VM-Manager-PHP-Fixes
VM Remove subpools, Disable Favorites.
2024-06-30 08:53:17 -07:00
tom mortensen
04e0fac01c Merge pull request #1765 from dlandon/master
Xfs check not detecting file issues; remove subpools from share edit.
2024-06-30 08:53:01 -07:00
dlandon
27973b8e34 Remove sub pools from available pools in primary and secondary storage. 2024-06-30 09:34:46 -05:00
dlandon
d6f8206275 Merge branch 'master' of https://github.com/dlandon/webgui 2024-06-30 09:26:59 -05:00
dlandon
cd1b4031df Xfs not properly showing issues from exit code. 2024-06-30 09:26:26 -05:00
dlandon
44dfe5a7b1 Revert "Properly show when parity operation is complete."
This reverts commit 1ae58dc360.
2024-06-30 08:34:56 -05:00
dlandon
1ae58dc360 Properly show when parity operation is complete. 2024-06-30 07:49:04 -05:00
dlandon
e0c0440d69 Fix exit status failing that results in xfs issues not being detected. 2024-06-30 07:43:56 -05:00
SimonFair
282cfc41ca VM Remove subpools, Disable Favorites. 2024-06-29 19:29:20 +01:00
dlandon
26f261e93b Merge remote-tracking branch 'upstream/master' 2024-06-29 08:13:47 -05:00
tom mortensen
65e67856bb Merge pull request #1762 from SimonFair/VM-Manager-PHP-Fixes
PHP Fixes and opentab for VM URL
2024-06-28 18:06:49 -07:00
tom mortensen
b73327d4b7 Merge pull request #1761 from dlandon/master
Parity check not showing correct results.
2024-06-28 18:06:09 -07:00
SimonFair
3fdcbf125d PHP Fixes and opentab for VM URL 2024-06-27 20:17:05 +01:00
dlandon
941778d288 Parity check sometimes shows last historical check and not the current parity check just completed. 2024-06-26 16:11:36 -05:00
Tom Mortensen
90cbff0058 Docker and libvirt disabled by default.
Permit removing unRAID array if no devices assigned.
2024-06-24 23:28:52 -07:00
tom mortensen
30b76de196 Merge pull request #1760 from dlandon/master
Fix Syslog memory error on large log files and php warning.
2024-06-24 11:05:59 -07:00
dlandon
7f681fe2fa Fix php warning about undefined variable. 2024-06-23 06:39:36 -05:00
dlandon
72651576f4 Increase memory limit to handle large log files. 2024-06-23 06:39:01 -05:00
dlandon
2ea62cca3d Merge remote-tracking branch 'upstream/master' 2024-06-19 07:57:52 -05:00
Tom Mortensen
20f34bc0b9 mover: bettern handling cases of primary/secondary configured but not present 2024-06-18 11:15:40 -07:00
tom mortensen
c2a4e68196 Merge pull request #1758 from dlandon/master
Minor Device Info page fixes.
2024-06-17 11:17:54 -07:00
tom mortensen
b75c420ce0 Merge pull request #1759 from othyn/master
Behaviour fix for #1743 : "Add in the ability to set a manual UPS capacity override in `dynamix.apcupsd`"
2024-06-17 11:17:21 -07:00
Ben
da18a7e213 Re-implementation of #1743 based on the received feedback. This implementation is far more robust, as it injects the NOMPOWER value into the existing code, allowing the existing Unraid GUI code to interpret and display the values in the same manner 2024-06-15 11:17:48 +01:00
dlandon
f6ae173275 xfs_repair not running in the background. 2024-06-13 07:12:53 -05:00
dlandon
dd9474ef55 Can only show zfs pool information when disk is mounted. 2024-06-13 06:42:48 -05:00
tom mortensen
7512161e1a Merge pull request #1757 from dlandon/master
Zfs pool information and fix btrfs stats and usage displays and CPU pinning post too many variables php error.
2024-06-11 09:33:54 -07:00
tom mortensen
68fe9a9399 Merge pull request #1756 from SimonFair/VM-GUI-XML-inline-edit
Fix typo in helptext.
2024-06-11 09:33:31 -07:00
tom mortensen
78826be39d Merge pull request #1755 from Squidly271/patch-41
Update Lincstation icon + add Laptop
2024-06-11 09:33:15 -07:00
dlandon
c4eff321f2 Use atomic file write. 2024-06-10 13:21:00 -05:00
dlandon
6d2de179a0 Update DeviceInfo.page 2024-06-10 12:59:57 -05:00
dlandon
1da8308b81 Revert "Update DeviceInfo.page"
This reverts commit 243b838d80.
2024-06-10 12:47:32 -05:00
dlandon
243b838d80 Update DeviceInfo.page 2024-06-10 12:39:30 -05:00
dlandon
f93095d552 Update UpdateTwo.php 2024-06-10 12:36:14 -05:00
dlandon
eed5a94d03 Fix php error of too many variables in post. 2024-06-10 09:50:09 -05:00
dlandon
5c33796801 Merge remote-tracking branch 'upstream/master' 2024-06-10 03:50:40 -05:00
dlandon
3e1e33094c Add zfs pool information and deal with html special characters. 2024-06-10 03:48:28 -05:00
Squidly271
63d4bab0d1 Update default-cases.css 2024-06-08 08:08:36 -04:00
Squidly271
dbd1c9f7b1 Add laptop to font 2024-06-08 08:07:10 -04:00
SimonFair
446111b0b1 Fix typo in helptext. 2024-06-08 07:25:22 +01:00
Squidly271
be0dcff031 Update cases 2024-06-07 19:11:06 -04:00
Squidly271
cdce8e048a Update default-cases.css 2024-06-07 19:08:27 -04:00
Tom Mortensen
2494d1735e set reasonable defaults for zfs/btrfs fs profiles 2024-06-07 12:33:01 -07:00
Tom Mortensen
8328461cf5 Instead of fs used/free display 'device is part of pool' for zfs subpool devices 2024-06-07 12:32:08 -07:00
dlandon
1cb3134217 Merge remote-tracking branch 'upstream/master' 2024-06-06 16:07:12 -05:00
tom mortensen
18872afdd0 Merge pull request #1754 from dlandon/master
Add more info to btrfs pools and more user friendly xfs repairs.
2024-06-06 10:42:13 -07:00
dlandon
fa5b932c13 Add more info to btrfs pools and more user friendly xfs repairs. 2024-06-05 15:15:37 -05:00
tom mortensen
aa489bb92d Merge pull request #1753 from SimonFair/WebUI
Fix disk device name as string.
2024-06-05 11:42:48 -07:00
SimonFair
22124123a9 Return dev name as string. 2024-06-05 17:17:48 +01:00
tom mortensen
419b9a77e6 Merge pull request #1752 from SimonFair/WebUI
Add nogpu option.
2024-06-04 14:18:35 -07:00
tom mortensen
191f68642c Merge pull request #1743 from othyn/master
Add in the ability to set a manual UPS capacity override in `dynamix.apcupsd`
2024-06-04 14:14:37 -07:00
SimonFair
cfa1ec4860 Add nogpu 2024-06-04 17:39:15 +01:00
tom mortensen
955ba7f0a6 Merge pull request #1750 from SimonFair/WebUI
Create WebUI option in VM Template.
2024-06-03 13:13:34 -07:00
SimonFair
8354a5c6ac Code tidy 2024-06-02 16:14:18 +01:00
SimonFair
392811005c Update text. 2024-06-02 16:02:45 +01:00
SimonFair
b2916424be Add rdp menu option. 2024-06-02 15:06:58 +01:00
SimonFair
b35ce11344 Change get VM IP to a function. 2024-06-02 15:06:11 +01:00
SimonFair
91bc84a97a Replace spaces in VM names 2024-06-01 15:41:23 +01:00
SimonFair
e61132fe69 Create WebUI option for VMs 2024-06-01 15:25:30 +01:00
SimonFair
15af3fb6eb Fix zfs dir create if parent is a dir rather than dataset. 2024-05-31 15:10:24 +01:00
Tom Mortensen
c1493a61d7 fix messaging on Main when array Stopped 2024-05-30 14:39:02 -07:00
tom mortensen
a823ecf79c Merge pull request #1706 from SimonFair/New-VM-interface
New vm interface template with inline xml view
2024-05-30 12:50:12 -07:00
tom mortensen
50a27242ab Merge pull request #1742 from dlandon/master
Fix detection of no mountable devices and detection of an empty share.
2024-05-30 12:49:25 -07:00
tom mortensen
7656c9a55c Merge pull request #1744 from SimonFair/Enable-subvol-or-dataset-for-VMs
Create error message for ZFS with no memory dump.
2024-05-30 12:48:15 -07:00
tom mortensen
15913a6c64 Merge pull request #1745 from unraid/fix-dashboard-buttons
Dashboard: Fix Array Start/Stop buttons
2024-05-30 12:47:29 -07:00
tom mortensen
f8ce0a6e80 Merge pull request #1746 from zackspear/feat/downgrade-os-next-to-stable
feat: downgrade os next to stable
2024-05-30 12:45:02 -07:00
tom mortensen
470e02ed55 Merge pull request #1747 from Squidly271/patch-40
Update file_put_contents_atomic
2024-05-30 12:43:16 -07:00
SimonFair
40ac4fe6fe Disable xmlother 2024-05-30 13:03:14 +01:00
dlandon
ef795438cc Merge remote-tracking branch 'upstream/master' 2024-05-29 06:27:03 -05:00
Squidly271
7a968eac84 Update file_put_contents_atomic 2024-05-28 20:15:20 -04:00
Zack Spear
3ce94f664b feat: downgrade os next to stable 2024-05-28 13:06:05 -07:00
ljm42
5daacf1e97 Dashboard: Fix Array Start/Stop buttons 2024-05-28 12:19:43 -07:00
dlandon
e621ccebb1 Primary only pool needs to use the primary device full size for floor calculations. 2024-05-27 13:38:54 -05:00
Ben
e28d4a32fb Add in the ability to set a manual UPS capacity override in dynamix.apcupsd, so that Nominal Power can still be estimated based on the load percentage for UPS's that don't report their NOMPOWER 2024-05-27 19:19:04 +01:00
dlandon
f6427a3361 Fix logic to determine a share is empty and when no mountable devices found. 2024-05-27 06:51:41 -05:00
dlandon
91e14b22db Fix detection of no mountable devices. 2024-05-27 06:49:52 -05:00
dlandon
095b931dad Code cleanuo. 2024-05-27 06:49:05 -05:00
SimonFair
5303b18462 Update VMedit.php 2024-05-27 11:00:39 +01:00
SimonFair
7bdb5eeb03 Update Custom.form.php 2024-05-27 10:49:32 +01:00
SimonFair
e6ff7a5d7f Updates 2024-05-27 10:43:27 +01:00
SimonFair
4fcf899c5a Merge remote-tracking branch 'upstream/master' into New-VM-interface 2024-05-27 09:12:12 +01:00
SimonFair
d9325649bb Create error message for ZFS with no memory dunp. 2024-05-27 08:59:40 +01:00
Tom Mortensen
0f22fe77e6 support array status ERROR:NO_DEVICES 2024-05-26 12:16:42 -07:00
Tom Mortensen
45c1cab4b5 add mover function to move the shares off a selected array disk onto other array disks 2024-05-26 12:15:49 -07:00
tom mortensen
e29de3c3e5 Merge pull request #1740 from SimonFair/Enable-subvol-or-dataset-for-VMs
VM Bug fixes.
2024-05-26 11:55:32 -07:00
tom mortensen
a757fe0fe6 Merge pull request #1741 from dlandon/master
Fixes from extensive testing.
2024-05-26 10:37:21 -07:00
dlandon
55c79439f3 Add language translation to missing pool messages; correct backwards prefer mover message. 2024-05-26 11:40:46 -05:00
dlandon
32c769e6bd Fixes from extensive testing. 2024-05-26 08:39:31 -05:00
SimonFair
a6c0369444 Fix overwrite of xml if user template name is not changed. 2024-05-26 14:32:35 +01:00
SimonFair
74ee77521e Fix to use template machine type rather than default to windows default i440 2024-05-26 09:21:20 +01:00
SimonFair
290d4da90a Updates to my_mkdir. 2024-05-25 20:24:20 +01:00
SimonFair
efd598c8f8 Fix templates broken by evdevs. 2024-05-25 19:56:43 +01:00
tom mortensen
19be69461e Merge pull request #1739 from unraid/feat-array-optional2
When no array, hide parity check history details
2024-05-24 16:32:51 -07:00
ljm42
cf6f3acb3a When no array, hide parity check history details 2024-05-24 11:34:57 -07:00
tom mortensen
b14922734a Merge pull request #1736 from dlandon/master
Share configuration changes for "array optional" feature and a few bug fixes
2024-05-23 23:12:44 -07:00
tom mortensen
9aa50b1b1d Merge pull request #1737 from SimonFair/Enable-subvol-or-dataset-for-VMs
Update my_mkdir and VMs to use my_mkdir
2024-05-23 23:11:27 -07:00
tom mortensen
24d5ed5f1b Merge pull request #1738 from unraid/fix-notify-subject
Notifications: remove html entities from subject in email/agents
2024-05-23 23:10:56 -07:00
dlandon
a2d35294d3 Floor calculation needs to be based on secondary pool, not primary pool. 2024-05-23 18:49:42 -05:00
ljm42
73b3829d61 Notifications: remove html entities from subject used by email and agents 2024-05-23 16:17:00 -07:00
dlandon
7b568c2aa1 Shares set to 'prefer' when set up in an array will work when no array is configured. 2024-05-23 16:51:01 -05:00
SimonFair
f76337c97f Update VMs to use my_mkdir 2024-05-23 21:43:52 +01:00
dlandon
44e74e15d3 Show all array shares when in poolsOnly mode so user can see the shares that need fixing. 2024-05-23 13:59:11 -05:00
SimonFair
3ec2a92b41 Update my_mkdir 2024-05-23 19:22:29 +01:00
dlandon
e256fa8313 Fixes for dealing with share predefined for array use that are dealt with differently when there are array disks. 2024-05-23 10:57:07 -05:00
dlandon
ad2e98ea46 Handle the case when switching to no array, and an array 'prefer' move setting is set. 2024-05-23 09:32:58 -05:00
dlandon
ed1ee47ce4 Changes to support array optional feature. 2024-05-23 08:44:10 -05:00
dlandon
cce6b7ea92 Change no array detection. 2024-05-23 08:43:31 -05:00
dlandon
1d5ff688ba Change no array detection. 2024-05-23 06:55:13 -05:00
Tom Mortensen
81e48a2291 small array slots dropdown correction 2024-05-22 21:42:06 -07:00
tom mortensen
3deacb643d Merge pull request #1735 from unraid/feat-array-optional
Allow the Array to be optional
2024-05-22 21:04:57 -07:00
ljm42
a4f6b7dcad Allow the Array to be optional 2024-05-22 15:43:58 -07:00
Tom Mortensen
fecd6505af mover: support pool as secondary 2024-05-22 11:00:37 -07:00
SimonFair
6938fbb992 Update libvirt_helpers.php 2024-05-22 17:19:28 +01:00
SimonFair
8d1478a06e Merge remote-tracking branch 'upstream/master' into New-VM-interface 2024-05-22 13:44:15 +01:00
SimonFair
964d083551 Updates 2024-05-22 13:36:35 +01:00
tom mortensen
70a24418b1 Merge pull request #1734 from SimonFair/Snapshot-logging
VM Updates
2024-05-21 09:21:47 -07:00
SimonFair
db094cdf89 Set Virtual GPU default keyboard to no map. 2024-05-20 19:12:11 +01:00
dlandon
05f6afd1a2 Sanitize the model text field. 2024-05-20 11:23:16 -05:00
SimonFair
3e37b4a27d Fix for snapshot removal if active volume and vm has spaces in name. 2024-05-20 17:20:14 +01:00
dlandon
bdc9ef9782 Remove double quote and forward slashes from description. 2024-05-20 08:42:43 -05:00
SimonFair
215d16af41 Set error if trying to clone VM with snaps. 2024-05-19 16:21:36 +01:00
dlandon
dcb1e43d21 Remove test code. 2024-05-19 08:04:13 -05:00
dlandon
a5cab82c29 Fix detection of unmountable disk when checking for no mountable disks . 2024-05-19 07:05:51 -05:00
Tom Mortensen
280e7a52cd Change zfs profile text 'raid0' to 'stripe' 2024-05-18 18:50:55 -07:00
Tom Mortensen
f0d4c00389 Create meaningful zfs subpool descriptions 2024-05-18 18:50:16 -07:00
tom mortensen
3301357979 Merge pull request #1732 from Squidly271/patch-39
Use atomic writes for updates of config files
2024-05-18 10:42:41 -07:00
tom mortensen
7701f10ef4 Merge pull request #1733 from SimonFair/Snapshot-logging
Fixes for exclusive shares for VM Clone.
2024-05-18 10:41:47 -07:00
SimonFair
ab41b013e2 Fixes for exclusive shares for VM Clone. 2024-05-18 16:04:40 +01:00
Squidly271
04ef869aba Refactor 2024-05-18 10:28:43 -04:00
Squidly271
1be0380f36 Update update.php 2024-05-18 09:46:26 -04:00
Squidly271
8d4c007226 Update Wrappers.php 2024-05-18 09:43:28 -04:00
tom mortensen
f5e5ae2d28 Merge pull request #1731 from SimonFair/Snapshot-logging
Add logging for Revert and Snapshot.
2024-05-17 15:22:31 -07:00
tom mortensen
41363509e2 Merge pull request #1730 from zackspear/feat/web-component-enhancements
feat: web component enhancements – downgrades, updates, and registration
2024-05-17 15:21:53 -07:00
tom mortensen
59835e14eb Merge pull request #1729 from dlandon/master
Fix share floor calculation when the share is array only; fix detection of no mountable devices when adding shares
2024-05-17 15:21:37 -07:00
SimonFair
d6eb51f502 Add logging for Revert and Snapshot. 2024-05-17 19:04:46 +01:00
dlandon
cebe945e81 Refactir siome Java Script code for readability. 2024-05-16 14:42:30 -05:00
dlandon
7532448241 Code cleanup and add safeName() to cleanup up share name before applying settings. 2024-05-16 13:36:40 -05:00
dlandon
f184a79e85 Changes to address proper share floor calculations. 2024-05-16 13:31:00 -05:00
Zack Spear
45308bc7cb feat: web component enhancements 2024-05-14 16:16:26 -07:00
dlandon
e988ef55fc Fix share floor calculation for pool disks. 2024-05-14 09:31:55 -05:00
dlandon
4cf3e39add Add validity check to share floor to limit to manual entry to less than max free on smallest disk. 2024-05-14 05:31:17 -05:00
dlandon
d85d68de2e Exclude disks that have no device designation - disks are not assigned yet. 2024-05-13 18:15:57 -05:00
dlandon
6790400629 Fix share floor calculation when the share is array only. 2024-05-13 13:02:27 -05:00
Tom Mortensen
7f77338b3d remove 'x' bit from ./local/etc/rc.dnsmasq 2024-05-13 10:59:38 -07:00
Tom Mortensen
70d3d5a656 fix: Present warning text for pool replacement devices (that they will be overwritten) 2024-05-13 10:59:04 -07:00
tom mortensen
69bd331d2d Merge pull request #1727 from dlandon/master
Fix detection of no mountable disks available.
2024-05-13 10:02:09 -07:00
tom mortensen
c44beb2eb7 Merge pull request #1728 from SimonFair/Fix-VM-disk-error
Update VMUsageStats.page
2024-05-13 10:01:53 -07:00
SimonFair
2d136461fd Update VMUsageStats.page 2024-05-13 17:38:25 +01:00
dlandon
414dddb008 Fix detection of no mountable disks available. 2024-05-12 20:11:31 -05:00
Tom Mortensen
894bc28aaa Present warning text for pool replacement devices (that they will be overwritten) 2024-05-12 09:55:04 -07:00
tom mortensen
8bf3cae742 Merge pull request #1720 from dlandon/master
Disable "Add Share" button when there are no mountable array disks; disable Reiserfs selections; add multi line NFS Rule.
2024-05-11 21:20:56 -07:00
tom mortensen
86a014f82e Merge pull request #1721 from SimonFair/Fix-VM-disk-error
Fix expand evdev options
2024-05-11 21:18:08 -07:00
tom mortensen
91edca4576 Merge pull request #1724 from Squidly271/agents
Move notication agents to individual xml files
2024-05-11 21:17:23 -07:00
tom mortensen
1c4f5983af Merge pull request #1725 from Squidly271/HearNoEvilSeeNoEvilSpeakNoEvil
Add lincstation n1 to to cases
2024-05-11 21:15:42 -07:00
dlandon
f933c288e9 Add placeholder to help user understand rules. 2024-05-11 08:14:56 -05:00
Squidly271
dd0cf7f2fa Remove semicolon before anyone notices 2024-05-11 08:21:27 -04:00
Squidly271
939a30774a Add lincstation n1 to to cases 2024-05-11 08:17:41 -04:00
dlandon
6234c5edc0 Add multi line Rule. 2024-05-11 04:08:27 -05:00
dlandon
b34fe9838a Disable Reiserfs selections in drop down. 2024-05-11 04:03:44 -05:00
dlandon
f65780a67e Can never schedule a correcting parity check. 2024-05-10 18:54:49 -05:00
dlandon
80e5481de9 Revert "Can never schedule a correcting parity check."
This reverts commit 40c25d9d18.
2024-05-10 18:54:24 -05:00
dlandon
40c25d9d18 Can never schedule a correcting parity check. 2024-05-10 18:20:37 -05:00
SimonFair
5542b65e45 Update VMUsageStats.page 2024-05-10 12:18:38 +01:00
SimonFair
70760a302c Update Custom.form.php 2024-05-09 15:56:56 +01:00
Squidly271
0431343070 Delete emhttp/plugins/dynamix/include/NotificationAgents.xml 2024-05-08 21:01:18 -04:00
Squidly271
32f1c8c3ec Update NotificationAgents.page 2024-05-08 20:57:42 -04:00
Squidly271
8f8da76bf3 Add files via upload 2024-05-08 20:56:47 -04:00
SimonFair
58f5fb269b Fix expand evdev options 2024-05-08 21:09:30 +01:00
dlandon
9a76f69f5d Display message and disable add share button if there are no mountable array disks. 2024-05-08 09:59:41 -05:00
dlandon
dac9d237ee Merge remote-tracking branch 'upstream/master' 2024-05-08 09:43:32 -05:00
tom mortensen
f492ee8f7f Merge pull request #1709 from dlandon/master
Exclude certain folders from the dropdown on /mnt/.
2024-05-07 17:02:27 -07:00
tom mortensen
3cc0bf7e90 Merge pull request #1714 from unraid/fix-curl2
in http_get_contents, detect and recover from curl error 23
2024-05-07 17:02:00 -07:00
tom mortensen
bf92bdb1af Merge pull request #1716 from SimonFair/Fix-VM-disk-error
Various VM Manager Fixes
2024-05-07 17:01:25 -07:00
tom mortensen
7fdb5cb5be Merge pull request #1717 from baumerdev/fix_readfile_on_empty_files
Fix Error 500 on login / fix read file on empty files
2024-05-07 16:59:02 -07:00
SimonFair
c45dd68e08 Update VMUsageStats.page 2024-05-07 20:28:27 +01:00
Markus Baumer
8b5eb3f66d Fix read file on empty files 2024-05-07 20:38:43 +02:00
Tom Mortensen
43ae4c785d passwd file handling correction 2024-05-07 07:36:39 -07:00
ljm42
8da53df357 in http_get_contents, include a useragent 2024-05-06 14:06:40 -07:00
SimonFair
b4a3a90a46 Add evdev passthru. 2024-05-06 20:44:44 +01:00
SimonFair
072e3512e0 Various VM Manager Fixes
Fix VM memory size values on VM Usage
Fix issue with auto showing for QCOW2 files not in a VM path, should be manual
2024-05-06 12:45:41 +01:00
ljm42
ae8feb2dbc in http_get_contents, detect and recover from curl error 23
change from null to "deflate"
2024-05-04 15:58:35 -07:00
tom mortensen
f901cc9d4e Merge pull request #1713 from unraid/fix-add-local-to-hosts
hosts file improvements
2024-05-02 17:36:45 -07:00
ljm42
a8f0e3ec19 When avahidaemon running, add name.local to hosts file 2024-05-02 15:19:49 -07:00
ljm42
2db31b2012 Remove keys.lime-technology.com from hosts file 2024-05-02 14:49:31 -07:00
tom mortensen
85d1a2f6f5 Merge pull request #1705 from almightyYantao/patch-1
Add Wxwork Notification Agent
2024-05-02 14:07:41 -07:00
dlandon
9296e6297c Be sure /mnt/user is available before using it as root in file picker. 2024-05-02 08:39:53 -05:00
dlandon
245e6413cb Exclude folders from /mnt/ and .Recycle.Bin from all folders. 2024-05-02 08:07:27 -05:00
dlandon
4f82b16fdd Revert to minimized version. 2024-05-02 08:06:46 -05:00
dlandon
a90d083cb0 Revert excluded folders. 2024-05-02 08:06:20 -05:00
dlandon
efe372d1d9 Change variables back to the original plugin variables. 2024-05-01 04:58:17 -05:00
tom mortensen
995a95f053 Merge pull request #1712 from ich777/ich777-patch-1
Update rc.S
2024-04-30 22:05:13 -07:00
dlandon
0c32fe5d4d Change root folder on isos to /mnt/user. 2024-04-30 06:24:34 -05:00
f8ff2333bb Update rc.S
- remove wsync from XFS mount to prevent WebGUI from freezing during heavy I/O on /boot
2024-04-30 07:55:28 +02:00
dlandon
ba9bef985c Fix misspelled variable. 2024-04-29 17:58:39 -05:00
Tom Mortensen
45cfa13e14 changes to include '/bin' in repo 2024-04-29 12:43:24 -07:00
tom mortensen
424b5cb121 Merge pull request #1711 from unraid/fix-curl
In http_get_contents, detect and recover from curl error 23
2024-04-29 09:14:53 -07:00
ljm42
d1e5f3bb10 rc.local - be consistent with backup extension 2024-04-28 16:37:39 -07:00
ljm42
e6441a8345 in http_get_contents, detect and recover from curl error 23 2024-04-28 16:12:40 -07:00
tom mortensen
43c71ec2da Merge pull request #1710 from SimonFair/XML-Windows-Size
Fix QEMU command line expanding if no data.
2024-04-28 08:33:56 -07:00
dlandon
33b27f0a71 Put list of folders to be excluded in the file picker dropdown in a php variable for greater flexibility to make changes. 2024-04-27 11:13:59 -05:00
dlandon
90eb25f114 Change commenting. 2024-04-27 06:42:29 -05:00
SimonFair
71f99155b8 Merge remote-tracking branch 'upstream/master' into XML-Windows-Size 2024-04-27 10:09:06 +01:00
SimonFair
cb48e16845 Fix QEMU command line expanding if no data.
Rework of XML mobile page size.
2024-04-27 10:04:10 +01:00
dlandon
ff2328ddca Add pickexclude to file tree operations so folders can be excluded to minimize confusion. 2024-04-26 16:44:18 -05:00
SimonFair
b0680ee1e1 Update VMedit.php 2024-04-26 18:36:41 +01:00
tom mortensen
c8b9f1c9cc Merge pull request #1708 from SimonFair/Add-Support-for-QEMU-override-in-xml
Add qemu:override support
2024-04-26 06:38:40 -07:00
SimonFair
863afd348d Format XML for new VM 2024-04-26 10:15:41 +01:00
SimonFair
ae285176a9 Update WIP 2024-04-26 08:35:02 +01:00
SimonFair
d9588b7c83 Add qemu:override support 2024-04-25 18:53:55 +01:00
tom mortensen
f79f907923 Merge pull request #1707 from SimonFair/XML-Windows-Size
Fix mobile devices VM XML View
2024-04-24 07:30:13 -07:00
SimonFair
a5b7247d88 Fix mobile devices 2024-04-22 22:02:33 +01:00
Tom Mortensen
2a6c09f9c9 better handling of marking form fields 'disabled' 2024-04-22 11:51:07 -07:00
tom mortensen
b8400d4395 Merge pull request #1704 from SimonFair/XML-Windows-Size
Fit XML window to page size
2024-04-21 08:34:02 -07:00
SimonFair
191c067465 Update code 2024-04-21 11:34:09 +01:00
Alimighty.Yantao
511a8e47ac Add Wxwork Notification Agent
新增企业微信消息通知,在国内还是这些用的较多。

The new enterprise wechat message notification is still more used in China.
2024-04-20 17:22:12 +08:00
SimonFair
2bec253098 Update Custom.form.php 2024-04-19 15:25:43 +01:00
SimonFair
b21437cf5c Update Custom.formXML.php 2024-04-15 07:01:09 +01:00
SimonFair
da7749725f Merge remote-tracking branch 'upstream/master' into New-VM-interface 2024-04-13 07:58:36 +01:00
tom mortensen
7cce87baa2 Merge pull request #1695 from dlandon/master
Add password encryption and rewrite set_proxy script in php
2024-04-12 14:01:39 -07:00
tom mortensen
c3d232efc0 Merge pull request #1700 from SimonFair/Show-decimal-on-disk-1TB
Show decimals for vdisk > 1TB
2024-04-12 14:01:23 -07:00
tom mortensen
1b35b145f8 Merge pull request #1701 from unraid/clean-go-script
better go script cleanup
2024-04-12 14:00:52 -07:00
ljm42
7e0f91cb53 Merge branch 'clean-go-script' of https://github.com/unraid/webgui into clean-go-script 2024-04-12 13:44:29 -07:00
ljm42
b7f69da252 change backup extension 2024-04-12 13:43:47 -07:00
ljm42
8f99ba2346 backup go script with random extension
Revert "backup go script with random extension"

This reverts commit f3498bd869e998040685399a2cb198e57809653f.

change backup extension
2024-04-12 13:41:35 -07:00
dlandon
e87a15d439 New location of OutgoingProxy.php. 2024-04-12 14:37:10 -05:00
dlandon
1640e6d2a0 Simpler check for null vALUE. 2024-04-12 14:36:44 -05:00
dlandon
a76a68d4e2 Simpler check for null values. 2024-04-12 14:34:41 -05:00
dlandon
f9398d01c6 Move OutgoingProxy.php file to /include/ folder. 2024-04-12 14:16:56 -05:00
dlandon
56990109f5 Merge pull request #1 from unraid/proxy_tweaks
Proxy tweaks
2024-04-12 13:30:15 -05:00
ljm42
80b200950f better go script cleanup 2024-04-11 15:28:54 -07:00
SimonFair
62ea5702ce Add support for decimal values 2024-04-10 23:44:22 +01:00
SimonFair
e9c973baad Show decimals for disk > 1TB 2024-04-10 21:27:24 +01:00
mtongnz
ffabb996fc fix: external ports not showing for non-bridge networks 2024-04-06 16:38:02 +13:00
Matt
4a4444229e Apply suggestions from code review
Co-authored-by: FunkeCoder23 <12570656+FunkeCoder23@users.noreply.github.com>
2024-04-06 13:04:22 +13:00
SimonFair
9ca6b38ecb Create test template 2024-03-31 21:32:30 +01:00
mtongnz
083cd984ea style: fix bizzare formatting glitches 2024-01-12 16:09:52 +13:00
mtongnz
3dda97319d fix: don't return template for containers not managed by dockerman 2024-01-12 15:51:04 +13:00
mtongnz
064cac1110 fix: suppress error on template fetch 2024-01-12 15:46:43 +13:00
mtongnz
3d1b53d0ea style: line up spacing 2024-01-12 15:45:53 +13:00
mtongnz
61e99a390e fix: docker network info display (non dockerman & multiple networks) 2024-01-12 15:45:04 +13:00
163 changed files with 8944 additions and 3046 deletions

6
.gitignore vendored
View File

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

View File

@@ -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
@@ -2399,7 +2436,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.

View File

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

View File

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

View File

@@ -3,6 +3,7 @@ UPSCABLE="usb"
CUSTOMUPSCABLE=""
UPSTYPE="usb"
DEVICE=""
OVERRIDE_UPS_CAPACITY=""
BATTERYLEVEL="10"
MINUTES="10"
TIMEOUT="0"

View File

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

View File

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

View File

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

View File

@@ -8,3 +8,5 @@ DOCKER_USER_NETWORKS="remove"
DOCKER_ALLOW_ACCESS=""
DOCKER_TIMEOUT=10
DOCKER_READMORE="yes"
DOCKER_PID_LIMIT=""
DOCKER_BACKINGFS="overlay2"

View File

@@ -213,6 +213,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
@@ -423,6 +426,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 +497,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);
@@ -858,6 +867,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;
@@ -880,6 +890,21 @@ _(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']);
}
}
?>
:docker_container_network_help:
</select>
</div>
_(Console shell command)_:
: <select name="contShell">
@@ -1013,9 +1038,18 @@ 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('');
}
}
@@ -1111,6 +1145,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);

View File

@@ -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) {
@@ -293,7 +293,7 @@ class DockerTemplates {
}
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;
@@ -307,9 +307,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 +321,18 @@ 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'] = '';
}
if ( ($tmp['shell'] ?? false) == false )
$tmp['shell'] = $this->getTemplateValue($image, 'Shell');
@@ -338,8 +347,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));
}
@@ -811,6 +824,13 @@ class DockerClient {
global $docroot, $dockerManPaths;
$id = $id ?: $name;
$info = DockerUtil::loadJSON($dockerManPaths['webui-info']);
// Check to see if the container is linked to other containers
$networks = array_map(function ($n) { return $n['NetworkMode']; }, $this->getDockerContainers());
if (in_array("container:{$name}", $networks)) {
return "Container currently assigned as network for another container.";
}
// Attempt to remove container
$this->getDockerJSON("/containers/$id?force=1", 'DELETE', $code);
if (isset($info[$name])) {
@@ -897,7 +917,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 +936,57 @@ 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['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);

View File

@@ -69,7 +69,8 @@ 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']??'');
@@ -83,15 +84,34 @@ foreach ($containers as $ct) {
$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 +120,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 +138,64 @@ 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;
}
}
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>";
}
@@ -162,3 +211,4 @@ foreach ($images as $image) {
}
echo "\0".implode($docker)."\0".(pgrep('rc.docker')!==false ? 1:0);
?>

View File

@@ -40,7 +40,11 @@ function postToXML($post, $setOwnership=false) {
$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']);
if (!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';
@@ -132,7 +136,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 +245,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']) : '';
@@ -301,15 +313,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',
$cmdName, $cmdNetwork, $cmdMyIP, $cmdCPUset, $pid_limit, $cmdPrivileged, implode(' -e ', $Variables), implode(' -l ', $Labels), implode(' -p ', $Ports), implode(' -v ', $Volumes), 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) {
@@ -503,9 +527,15 @@ function getAllocations() {
$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');

View File

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

View File

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

View 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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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?>">&nbsp&nbsp&nbsp&nbsp<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"';?>
/> &nbsp&nbsp&nbsp&nbsp&nbsp&nbsp&nbsp <input type="checkbox" name="usbopt[<?=htmlspecialchars($arrDev['id'])?>]" id="usbopt<?=$i?>" value="<?=htmlspecialchars($arrDev['id'])?>" <?if ($arrDev["startupPolicy"] =="optional") echo 'checked="checked"';?>/>&nbsp&nbsp&nbsp&nbsp&nbsp
<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)_&nbsp&nbsp_(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. QEMUs 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. QEMUs 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 wont 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;
}
});

File diff suppressed because it is too large Load Diff

View File

@@ -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">
&nbsp;
: <input type="button" value="_(Install)_" onclick="installPlugin(this.form.file.value)">

View File

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

View File

@@ -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> &bullet; ".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>

View File

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

View File

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

View File

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

View File

@@ -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'))?>&nbsp;
<?$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>&nbsp;_(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;?>
&nbsp;
: <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?>">
&nbsp;
: <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?>">
&nbsp;
: <input type="submit" value="_(Cancel)_">
: <input type="submit" value="_(Cancel)_"> *_(Running)_*
:info_balance_cancel_help:
@@ -811,7 +895,7 @@ _(btrfs balance status)_:
<?else:?>
&nbsp;
: <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="">
&nbsp;
: <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?>">
&nbsp;
: <input type="submit" value="_(Cancel)_">
: <input type="submit" value="_(Cancel)_"> *_(Running)_*
:info_scrub_cancel_help:
@@ -906,7 +990,7 @@ _(btrfs scrub status)_:
<?else:?>
&nbsp;
: <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)):?>
&nbsp;
: <input type="submit" value="_(Check)_" disabled> **_(Check)_** _(is only available when array is Started in **Maintenance** mode)_.
<?else:?>
&nbsp;
: <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?>">
&nbsp;
: <input type="submit" value="_(Cancel)_">
: <input type="submit" value="_(Cancel)_"> *_(Running)_*
:info_scrub_cancel_help:
@@ -1044,18 +1123,28 @@ _(zfs pool status)_:
<?else:?>
&nbsp;
: <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">
&nbsp;
: <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">
&nbsp;
: <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">
&nbsp;
: <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">
&nbsp;
: <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):?>

View File

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

View File

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

View File

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

View File

@@ -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:
&nbsp;
: <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>

View File

@@ -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>&nbsp;</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>&nbsp;</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)_:
&nbsp;
: <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;?>

View File

@@ -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>&nbsp;</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>&nbsp;</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>';
}
?>

View File

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

View File

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

View File

@@ -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:
&nbsp;
: <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>

View File

@@ -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>
&nbsp;
: <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>

View File

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

View File

@@ -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(&quot;'+page+'&quot;);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(&quot;'+page+'&quot;);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

View File

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

View File

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

View File

@@ -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(&quot;'+page+'&quot;);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(&quot;'+page+'&quot;);return false"></i>');}
if (<?=$favorites?>) { $(this).hover(function(){$(this).find('i.favo').show();},function(){$(this).find('i.favo').hide();});}
});
});
</script>

View File

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

View File

@@ -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):?>
> ![](<?=autov('/webGui/images/wireguard-help.png')?>)
@@ -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;?>
&nbsp;

View File

@@ -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):?>
> ![](<?=autov('/webGui/images/wireguard-help.png')?>)
@@ -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;?>
&nbsp;

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

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

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

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

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

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

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

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

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

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

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

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

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

View File

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

View File

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

View File

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

View File

@@ -119,7 +119,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 +146,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>";

View File

@@ -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&reg; webGui &copy;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>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

View File

@@ -26,8 +26,8 @@ case 'Add Route':
if ($gateway && $route) exec("/etc/rc.d/rc.inet1 ".escapeshellarg("{$gateway}_{$route}_{$metric}_add"));
break;
default:
exec("ip -4 route show|grep -v '^127.0.0.0'",$ipv4);
exec("ip -6 route show|grep -Pv '^([am:]|(f[ef][0-9][0-9])::)|expires'",$ipv6);
exec("ip -4 route show table all|grep -Pv '^(127\\.0\\.0\\.0)|table local'",$ipv4);
exec("ip -6 route show table all|grep -Pv '^([am:]|(f[ef][0-9][0-9])::)|expires|table local'",$ipv6);
foreach ($ipv4 as $info) {
$cell = explode(' ',$info);
$route = $cell[0];

View File

@@ -18,9 +18,71 @@ require_once "$docroot/webGui/include/Helpers.php";
$_SERVER['REQUEST_URI'] = 'shares';
require_once "$docroot/webGui/include/Translations.php";
/* If the configuration is pools only, then no array disks are available. */
$poolsOnly = ((int)$var['SYS_ARRAY_SLOTS'] <= 2) ? true : false;
/* Check for any files in the share. */
if (isset($_POST['scan'])) {
die((new FilesystemIterator("/mnt/user/{$_POST['scan']}"))->valid() ? '0' : '1');
$directory = "/mnt/user/{$_POST['scan']}";
$hasFiles = false;
/* Check if the directory exists */
if (is_dir($directory)) {
/* Create a new RecursiveDirectoryIterator instance with SKIP_DOTS to skip . and .. entries */
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($directory, FilesystemIterator::SKIP_DOTS | FilesystemIterator::FOLLOW_SYMLINKS),
RecursiveIteratorIterator::SELF_FIRST
);
/* Iterate over each item in the directory and its subdirectories */
foreach ($iterator as $fileinfo) {
/* Check if the current item is a file and not a .DS_Store file */
if ($fileinfo->isFile() && !preg_match('/\.DS_Store$/i', $fileinfo->getFilename())) {
$hasFiles = true;
break;
}
}
/* Output 0 if files are found, 1 if no files are found */
die($hasFiles ? '0' : '1');
} else {
/* Output 1 if the directory does not exist */
die('1');
}
}
/* Remove all '.DS_Store' files from a directory recursively and delete empty directories. */
if (isset($_POST['delete'])) {
$nameToDelete = $_POST['delete'];
$dirPath = "/mnt/user/{$nameToDelete}";
if (is_dir($dirPath)) {
removeDSStoreFilesAndEmptyDirs($dirPath);
}
die("success");
}
/* Function to remove '.DS_Store' files and empty directories from a share. */
function removeDSStoreFilesAndEmptyDirs($dir) {
$iterator = new RecursiveIteratorIterator(
new RecursiveDirectoryIterator($dir, RecursiveDirectoryIterator::SKIP_DOTS),
RecursiveIteratorIterator::CHILD_FIRST
);
foreach ($iterator as $file) {
if ($file->isFile() && preg_match('/\.DS_Store$/i', $file->getFilename())) {
unlink($file->getRealPath());
}
}
/* Second pass to remove empty directories */
foreach ($iterator as $file) {
if ($file->isDir() && !(new \FilesystemIterator($file->getRealPath()))->valid()) {
rmdir($file->getRealPath());
}
}
}
if (isset($_POST['cleanup'])) {
$n = 0;
// active shares
@@ -46,6 +108,15 @@ $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);
/* Get the pools from the disks.ini. */
$pools_check = pools_filter(cache_filter($disks));
$pools = implode(',', $pools_check);
// 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);
// exit when no shares
$noshares = "<tr><td class='empty' colspan='7'><i class='fa fa-folder-open-o icon'></i>"._('There are no exportable user shares')."</td></tr>";
if (!$shares) die($noshares);
@@ -53,107 +124,198 @@ if (!$shares) die($noshares);
// GUI settings
extract(parse_plugin_cfg('dynamix',true));
$pools_check = pools_filter(cache_filter($disks));
$pools = implode(',', $pools_check);
// Natural sorting of share names
uksort($shares,'strnatcasecmp');
/* 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;
}
// Display export settings
function user_share_settings($protocol,$share) {
if (empty($share)) return;
if ($protocol!='yes' || $share['export']=='-') return "-";
return ($share['export']=='e') ? _(ucfirst($share['security'])) : '<em>'._(ucfirst($share['security'])).'</em>';
if (empty($share)) return;
if ($protocol!='yes' || $share['export']=='-') return "-";
return ($share['export']=='e') ? _(ucfirst($share['security'])) : '<em>'._(ucfirst($share['security'])).'</em>';
}
function globalInclude($name) {
global $var;
return substr($name,0,4)!='disk' || !$var['shareUserInclude'] || strpos("{$var['shareUserInclude']},","$name,")!==false;
global $var;
return substr($name,0,4)!='disk' || !$var['shareUserInclude'] || strpos("{$var['shareUserInclude']},","$name,")!==false;
}
function shareInclude($name) {
global $include;
return !$include || substr($name,0,4)!='disk' || strpos("$include,", "$name,")!==false;
global $include;
return !$include || substr($name,0,4)!='disk' || strpos("$include,", "$name,")!==false;
}
// Compute user shares & check encryption
$crypto = false;
foreach ($shares as $name => $share) {
if ($all!=0 && (!$compute || $compute==$name)) exec("$docroot/webGui/scripts/share_size ".escapeshellarg($name)." ssz1 ".escapeshellarg($pools));
$crypto |= _var($share,'luksStatus',0)>0;
if ($all!=0 && (!$compute || $compute==$name)) exec("$docroot/webGui/scripts/share_size ".escapeshellarg($name)." ssz1 ".escapeshellarg($pools));
$crypto |= _var($share,'luksStatus',0)>0;
}
// global shares include/exclude
$myDisks = array_filter(array_diff(array_keys($disks), explode(',',$var['shareUserExclude'])), 'globalInclude');
// Share size per disk
$ssz1 = [];
if ($all==0)
exec("rm -f /var/local/emhttp/*.ssz1");
else
foreach (glob("state/*.ssz1",GLOB_NOSORT) as $entry) $ssz1[basename($entry,'.ssz1')] = parse_ini_file($entry);
if ($all==0) {
exec("rm -f /var/local/emhttp/*.ssz1");
} else {
foreach (glob("state/*.ssz1",GLOB_NOSORT) as $entry) $ssz1[basename($entry,'.ssz1')] = parse_ini_file($entry);
}
/* Define constants for magic strings */
define('STATUS_GREEN_ON', 'green-on');
define('STATUS_YELLOW_ON', 'yellow-on');
define('LUKS_STATUS_UNKNOWN', 0);
define('LUKS_STATUS_ENCRYPTED', 1);
define('LUKS_STATUS_UNENCRYPTED', 2);
// Build table
$row = 0;
foreach ($shares as $name => $share) {
$row++;
$color = $share['color'];
switch ($color) {
case 'green-on' : $orb = 'circle'; $color = 'green'; $help = _('All files protected'); break;
case 'yellow-on': $orb = 'warning'; $color = 'yellow'; $help = _('Some or all files unprotected'); break;
}
if ($crypto) switch ($share['luksStatus']) {
case 0: $luks = "<i class='nolock fa fa-lock'></i>"; break;
case 1: $luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt green-text'></i><span>"._('All files encrypted')."</span></a>"; break;
case 2: $luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt orange-text'></i><span>"._('Some or all files unencrypted')."</span></a>"; break;
default: $luks = "<a class='info' onclick='return false'><i class='padlock fa fa-lock red-text'></i><span>"._('Unknown encryption state')."</span></a>"; break;
} else $luks = "";
echo "<tr><td><a class='view' href=\"/$path/Browse?dir=/mnt/user/",rawurlencode($name),"\"><i class=\"icon-u-tab\" title=\"",_('Browse')," /mnt/user/".rawurlencode($name),"\"></i></a>";
echo "<a class='info nohand' onclick='return false'><i class='fa fa-$orb orb $color-orb'></i><span style='left:18px'>$help</span></a>$luks<a href=\"/$path/Share?name=";
echo rawurlencode($name),"\" onclick=\"$.cookie('one','tab1')\">$name</a></td>";
echo "<td>{$share['comment']}</td>";
echo "<td>",user_share_settings($var['shareSMBEnabled'], $sec[$name]),"</td>";
echo "<td>",user_share_settings($var['shareNFSEnabled'], $sec_nfs[$name]),"</td>";
/* Is cachePool2 defined? If it is we need to show the cache pool 2 device name instead of 'Array'. */
if ($share['cachePool2']) {
$array = compress(my_disk($share['cachePool2'],$display['raw']));
$indicator = "<i class='fa fa-bullseye fa-fw'></i>";
} else {
$array = _('Array');
$indicator = "<i class='fa fa-database fa-fw'></i>";
}
// Check for non existent pool device
if (isset($share['cachePool']) && !in_array($share['cachePool'], $pools_check)) $share['useCache'] = "no";
/* Check that the share storage assignments are valid. */
if (($share['cachePool']) && (! in_array($share['cachePool'], $pools_check))) {
$array = compress(my_disk($share['cachePool'],$display['raw']));
$indicator = "<i class='fa fa-bullseye fa-fw'></i>";
$share_valid = false;
} else if (($share['cachePool2']) && (! in_array($share['cachePool2'], $pools_check))) {
$array = compress(my_disk($share['cachePool2'],$display['raw']));
$indicator = "<i class='fa fa-bullseye fa-fw'></i>";
$share_valid = false;
} else if (($poolsOnly) && (! $share['cachePool']) && (! $share['cachePool2'])) {
$share_valid = false;
} else {
/* Is the share exclusive? */
$exclusive = _var($share, 'exclusive') == 'yes' ? "<i class='fa fa-caret-right '></i> " : "";
$share_valid = true;
}
switch ($share['useCache']) {
case 'no':
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-database fa-fw'></i>"._('Array')."<span>".sprintf(_('Primary storage %s'),_('Array'))."</span></a>";
break;
case 'yes':
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'],$display['raw']))." <i class='fa fa-long-arrow-right fa-fw'></i><i class='fa fa-database fa-fw'></i>"._('Array')."<span>"._('Primary storage to Secondary storage')."</span></a>";
break;
case 'prefer':
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'],$display['raw']))." <i class='fa fa-long-arrow-left fa-fw'></i><i class='fa fa-database fa-fw'></i>"._('Array')."<span>"._('Secondary storage to Primary storage')."</span></a>";
break;
case 'only':
$exclusive = isset($share['exclusive']) && $share['exclusive']=='yes' ? "<i class='fa fa-caret-right '></i> " : "";
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>$exclusive".my_disk($share['cachePool'],$display['raw'])."<span>".sprintf(_('Primary storage %s'),$share['cachePool']).($exclusive ? ", "._('Exclusive access') : "")."</span></a>";
break;
}
if (array_key_exists($name,$ssz1)) {
echo "<td>$cache</td>";
echo "<td>",my_scale($ssz1[$name]['disk.total'], $unit)," $unit</td>";
echo "<td>",my_scale($share['free']*1024, $unit)," $unit</td>";
echo "</tr>";
foreach ($ssz1[$name] as $diskname => $disksize) {
if ($diskname=='disk.total') continue;
$include = $share['include'];
$inside = in_array($diskname, array_filter(array_diff($myDisks, explode(',',$share['exclude'])), 'shareInclude'));
echo "<tr class='",($inside ? "'>" : "warning'>");
echo "<td><a class='view'></a><a href='#' title='",_('Recompute'),"...' onclick=\"computeShare('",rawurlencode($name),"',$(this).parent())\"><i class='fa fa-refresh icon'></i></a>&nbsp;",_(my_disk($diskname,$display['raw']),3),"</td>";
echo "<td>",($inside ? "" : "<em>"._('Share is outside the list of designated disks')."</em>"),"</td>";
echo "<td></td>";
echo "<td></td>";
echo "<td></td>";
echo "<td>",my_scale($disksize, $unit)," $unit</td>";
echo "<td>",my_scale($disks[$diskname]['fsFree']*1024, $unit)," $unit</td>";
echo "</tr>";
}
} else {
echo "<td>$cache</td>";
echo "<td><a href='#' onclick=\"computeShare('",rawurlencode($name),"',$(this))\">",_('Compute'),"...</a></td>";
echo "<td>",my_scale($share['free']*1024, $unit)," $unit</td>";
echo "</tr>";
}
/* When there is no array, all pools are treated as 'only' cache. */
if (($poolsOnly) && (! $share['cachePool2'])) {
$share['useCache'] = 'only';
}
$row++;
$color = $share['color'] ?? '';
$orb = '';
$help = '';
switch ($color) {
case STATUS_GREEN_ON:
$orb = 'circle';
$color = 'green';
$help = _('All files protected');
break;
case STATUS_YELLOW_ON:
$orb = 'warning';
$color = 'yellow';
$help = _('Some or all files unprotected');
break;
default:
/* Handle unexpected color values */
$orb = 'question';
$color = 'grey';
$help = _('Unknown protection status');
break;
}
$luks = '';
if ($crypto) {
switch ($share['luksStatus'] ?? LUKS_STATUS_UNKNOWN) {
case LUKS_STATUS_UNKNOWN:
$luks = "<i class='nolock fa fa-lock'></i>";
break;
case LUKS_STATUS_ENCRYPTED:
$luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt green-text'></i><span>"._('All files encrypted')."</span></a>";
break;
case LUKS_STATUS_UNENCRYPTED:
$luks = "<a class='info' onclick='return false'><i class='padlock fa fa-unlock-alt orange-text'></i><span>"._('Some or all files unencrypted')."</span></a>";
break;
default:
$luks = "<a class='info' onclick='return false'><i class='padlock fa fa-lock red-text'></i><span>"._('Unknown encryption state')."</span></a>";
break;
}
}
echo "<tr><td><a class='view' href=\"/$path/Browse?dir=/mnt/user/", rawurlencode($name), "\"><i class=\"icon-u-tab\" title=\"", _('Browse'), " /mnt/user/" . rawurlencode($name), "\"></i></a>";
echo "<a class='info nohand' onclick='return false'><i class='fa fa-$orb orb $color-orb'></i><span style='left:18px'>$help</span></a>$luks<a href=\"/$path/Share?name=";
echo rawurlencode($name), "\" onclick=\"$.cookie('one','tab1')\">$name</a></td>";
echo "<td>{$share['comment']}</td>";
echo "<td>", user_share_settings($var['shareSMBEnabled'], $sec[$name]), "</td>";
echo "<td>", user_share_settings($var['shareNFSEnabled'], $sec_nfs[$name]), "</td>";
/* If the share pool or array is not valid, indicate that to the user. */
if (!$share_valid) {
$cache = "<a class='hand info none' onclick='return false'>".$indicator." <i class='fa fa-warning red-orb'></i> ".$array."<span>"._('This share is invalid').'; '. _('It references storage that does not exist')."</span></a>";
} else {
switch ($share['useCache']) {
case 'no':
$cache = "<a class='hand info none' onclick='return false'>".$indicator.$exclusive.$array."<span>".sprintf(_('Primary storage %s'), $array)."</span></a>";
break;
case 'yes':
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'], $display['raw']))." <i class='fa fa-long-arrow-right fa-fw'></i>".$indicator.$array."<span>"._('Primary storage to Secondary storage')."</span></a>";
break;
case 'prefer':
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>".compress(my_disk($share['cachePool'], $display['raw']))." <i class='fa fa-long-arrow-left fa-fw'></i>".$indicator.$array."<span>"._('Secondary storage to Primary storage')."</span></a>";
break;
case 'only':
$cache = "<a class='hand info none' onclick='return false'><i class='fa fa-bullseye fa-fw'></i>$exclusive".my_disk($share['cachePool'], $display['raw'])."<span>".sprintf(_('Primary storage %s'), $share['cachePool']).($exclusive ? ", "._('Exclusive access') : "")."</span></a>";
break;
default:
/* Handle unexpected useCache values */
$cache = "<a class='hand info none' onclick='return false'>". $indicator . $array . "<span>"._('Unknown cache usage')."</span></a>";
break;
}
}
if (array_key_exists($name, $ssz1)) {
echo "<td>$cache</td>";
echo "<td>", my_scale($ssz1[$name]['disk.total'], $unit), " $unit</td>";
echo "<td>", my_scale($share['free'] * 1024, $unit), " $unit</td>";
echo "</tr>";
foreach ($ssz1[$name] as $diskname => $disksize) {
if ($diskname == 'disk.total') continue;
$include = $share['include'];
$inside = in_array($diskname, array_filter(array_diff($myDisks, explode(',', $share['exclude'])), 'shareInclude'));
echo "<tr class='", ($inside ? "'>" : "warning'>");
echo "<td><a class='view'></a><a href='#' title='", _('Recompute'), "...' onclick=\"computeShare('", rawurlencode($name), "',$(this).parent())\"><i class='fa fa-refresh icon'></i></a>&nbsp;", _(my_disk($diskname, $display['raw']), 3), "</td>";
echo "<td>", ($inside ? "" : "<em>"._('Share is outside the list of designated disks')."</em>"), "</td>";
echo "<td></td>";
echo "<td></td>";
echo "<td></td>";
echo "<td>", my_scale($disksize, $unit), " $unit</td>";
echo "<td>", my_scale($disks[$diskname]['fsFree'] * 1024, $unit), " $unit</td>";
echo "</tr>";
}
} else {
echo "<td>$cache</td>";
echo "<td><a href='#' onclick=\"computeShare('", rawurlencode($name), "',$(this))\">", _('Compute'), "...</a></td>";
echo "<td>", my_scale($share['free'] * 1024, $unit), " $unit</td>";
echo "</tr>";
}
}
if ($row==0) echo $noshares;
?>

View File

@@ -15,7 +15,7 @@ $docroot ??= ($_SERVER['DOCUMENT_ROOT'] ?: '/usr/local/emhttp');
require_once "$docroot/webGui/include/Secure.php";
function pgrep($proc) {
return exec("pgrep -f $proc");
return exec('pgrep --ns $$ -f '."$proc");
}
if (isset($_POST['kill']) && $_POST['kill'] > 1) {

Some files were not shown because too many files have changed in this diff Show More