Skip to content

Commit 9a9de63

Browse files
committed
feat: add display settings modification for Unraid UI
- Introduced a new `DisplaySettingsModification` class to handle modifications to the DisplaySettings.page file. - Implemented functionality to remove the "fixed" class from the locale select element in the Display Settings page. - Added tests for the new modification, including a snapshot of the modified DisplaySettings.page. - Created a patch file to apply the changes to the DisplaySettings.page. This commit enhances the customization capabilities of the Unraid UI by allowing specific modifications to the display settings.
1 parent fe38e34 commit 9a9de63

File tree

5 files changed

+729
-0
lines changed

5 files changed

+729
-0
lines changed
Lines changed: 334 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,334 @@
1+
Menu="UserPreferences"
2+
Title="Display Settings"
3+
Icon="icon-display"
4+
Tag="desktop"
5+
---
6+
<?PHP
7+
/* Copyright 2005-2025, Lime Technology
8+
* Copyright 2012-2025, Bergware International.
9+
*
10+
* This program is free software; you can redistribute it and/or
11+
* modify it under the terms of the GNU General Public License version 2,
12+
* as published by the Free Software Foundation.
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*/
17+
?>
18+
<?
19+
$void = "<img src='/webGui/images/banner.png' id='image' width='330' height='30' onclick='$(&quot;#drop&quot;).click()' style='cursor:pointer' title='_(Click to select PNG file)_'>";
20+
$icon = "<i class='fa fa-trash top' title='_(Restore default image)_' onclick='restore()'></i>";
21+
$plugins = '/var/log/plugins';
22+
23+
require_once "$docroot/plugins/dynamix.plugin.manager/include/PluginHelpers.php";
24+
?>
25+
<script src="<?autov('/webGui/javascript/jquery.filedrop.js')?>"></script>
26+
<script>
27+
var path = '/boot/config/plugins/dynamix';
28+
var filename = '';
29+
var locale = "<?=$locale?>";
30+
31+
function restore() {
32+
// restore original image and activate APPLY button
33+
$('#dropbox').html("<?=$void?>");
34+
$('select[name="banner"]').trigger('change');
35+
filename = 'reset';
36+
}
37+
function upload(lang) {
38+
// save or delete upload when APPLY is pressed
39+
if (filename=='reset') {
40+
$.post("/webGui/include/FileUpload.php",{cmd:'delete',path:path,filename:'banner.png'});
41+
} else if (filename) {
42+
$.post("/webGui/include/FileUpload.php",{cmd:'save',path:path,filename:filename,output:'banner.png'});
43+
}
44+
// reset dashboard tiles when switching language
45+
if (lang != locale) {
46+
$.removeCookie('db-box1');
47+
$.removeCookie('db-box2');
48+
$.removeCookie('db-box3');
49+
$.removeCookie('inactive_content');
50+
$.removeCookie('hidden_content');
51+
}
52+
}
53+
function presetBanner(form) {
54+
if (form.banner.selectedIndex == 0) $('.js-bannerSettings').hide(); else $('.js-bannerSettings').show();
55+
}
56+
function presetRefresh(form) {
57+
for (var i=0,item; item=form.refresh.options[i]; i++) item.value *= -1;
58+
}
59+
function presetPassive(index) {
60+
if (index==0) $('#passive').hide(); else $('#passive').show();
61+
}
62+
function updateDirection(lang) {
63+
// var rtl = ['ar_AR','fa_FA'].includes(lang) ? "dir='rtl' " : "";
64+
// RTL display is not giving the desired results, we keep LTR
65+
var rtl = "";
66+
$('input[name="rtl"]').val(rtl);
67+
}
68+
69+
$(function() {
70+
var dropbox = $('#dropbox');
71+
// attach the drag-n-drop feature to the 'dropbox' element
72+
dropbox.filedrop({
73+
maxfiles:1,
74+
maxfilesize:512, // KB
75+
data: {"csrf_token": "<?=$var['csrf_token']?>"},
76+
url:'/webGui/include/FileUpload.php',
77+
beforeEach:function(file) {
78+
if (!file.type.match(/^image\/.*/)) {
79+
swal({title:"_(Warning)_",text:"_(Only PNG images are allowed)_!",type:"warning",html:true,confirmButtonText:"_(Ok)_"});
80+
return false;
81+
}
82+
},
83+
error: function(err, file, i) {
84+
switch (err) {
85+
case 'BrowserNotSupported':
86+
swal({title:"_(Browser error)_",text:"_(Your browser does not support HTML5 file uploads)_!",type:"error",html:true,confirmButtonText:"_(Ok)_"});
87+
break;
88+
case 'TooManyFiles':
89+
swal({title:"_(Too many files)_",text:"_(Please select one file only)_!",html:true,type:"error"});
90+
break;
91+
case 'FileTooLarge':
92+
swal({title:"_(File too large)_",text:"_(Maximum file upload size is 512K)_ (524,288 _(bytes)_)",type:"error",html:true,confirmButtonText:"_(Ok)_"});
93+
break;
94+
}
95+
},
96+
uploadStarted:function(i,file,count) {
97+
var image = $('img', $(dropbox));
98+
var reader = new FileReader();
99+
image.width = 330;
100+
image.height = 30;
101+
reader.onload = function(e){image.attr('src',e.target.result);};
102+
reader.readAsDataURL(file);
103+
},
104+
uploadFinished:function(i,file,response) {
105+
if (response == 'OK 200') {
106+
if (!filename || filename=='reset') $(dropbox).append("<?=$icon?>");
107+
$('select[name="banner"]').trigger('change');
108+
filename = file.name;
109+
} else {
110+
swal({title:"_(Upload error)_",text:response,type:"error",html:true,confirmButtonText:"_(Ok)_"});
111+
}
112+
}
113+
});
114+
// simulate a drop action when manual file selection is done
115+
$('#drop').bind('change', function(e) {
116+
var files = e.target.files;
117+
if ($('#dropbox').triggerHandler({type:'drop',dataTransfer:{files:files}})==false) e.stopImmediatePropagation();
118+
});
119+
presetBanner(document.display_settings);
120+
});
121+
</script>
122+
123+
:display_settings_help:
124+
125+
<form markdown="1" name="display_settings" method="POST" action="/update.php" target="progressFrame" onsubmit="upload(this.locale.value)">
126+
<input type="hidden" name="#file" value="dynamix/dynamix.cfg">
127+
<input type="hidden" name="#section" value="display">
128+
<input type="hidden" name="rtl" value="<?=$display['rtl']?>">
129+
130+
_(Display width)_:
131+
: <select name="width">
132+
<?=mk_option($display['width'], "",_('Boxed'))?>
133+
<?=mk_option($display['width'], "1",_('Unlimited'))?>
134+
</select>
135+
136+
:display_width_help:
137+
138+
_(Language)_:
139+
: <select name="locale" class="fixed" onchange="updateDirection(this.value)">
140+
<?echo mk_option($display['locale'], "","English");
141+
foreach (glob("$plugins/lang-*.xml",GLOB_NOSORT) as $xml_file) {
142+
$lang = language('Language', $xml_file);
143+
$home = language('LanguageLocal', $xml_file);
144+
$name = language('LanguagePack', $xml_file);
145+
echo mk_option($display['locale'], $name, "$home ($lang)");
146+
}
147+
?></select>
148+
149+
_(Font size)_:
150+
: <select name="font" id='font'>
151+
<?=mk_option($display['font'], "50",_('Very small'))?>
152+
<?=mk_option($display['font'], "56.25",_('Small'))?>
153+
<?=mk_option($display['font'], "",_('Normal'))?>
154+
<?=mk_option($display['font'], "68.75",_('Large'))?>
155+
<?=mk_option($display['font'], "75",_('Very large'))?>
156+
<?=mk_option($display['font'], "80",_('Huge'))?>
157+
</select>
158+
159+
:display_font_size_help:
160+
161+
_(Terminal font size)_:
162+
: <select name="tty" id="tty">
163+
<?=mk_option($display['tty'], "11",_('Very small'))?>
164+
<?=mk_option($display['tty'], "13",_('Small'))?>
165+
<?=mk_option($display['tty'], "15",_('Normal'))?>
166+
<?=mk_option($display['tty'], "17",_('Large'))?>
167+
<?=mk_option($display['tty'], "19",_('Very large'))?>
168+
<?=mk_option($display['tty'], "21",_('Huge'))?>
169+
</select>
170+
171+
:display_tty_size_help:
172+
173+
_(Number format)_:
174+
: <select name="number">
175+
<?=mk_option($display['number'], ".,",_('[D] dot : [G] comma'))?>
176+
<?=mk_option($display['number'], ". ",_('[D] dot : [G] space'))?>
177+
<?=mk_option($display['number'], ".",_('[D] dot : [G] none'))?>
178+
<?=mk_option($display['number'], ",.",_('[D] comma : [G] dot'))?>
179+
<?=mk_option($display['number'], ", ",_('[D] comma : [G] space'))?>
180+
<?=mk_option($display['number'], ",",_('[D] comma : [G] none'))?>
181+
</select>
182+
183+
_(Number scaling)_:
184+
: <select name="scale">
185+
<?=mk_option($display['scale'], "-1",_('Automatic'))?>
186+
<?=mk_option($display['scale'], "0",_('Disabled'))?>
187+
<?=mk_option($display['scale'], "1",_('KB'))?>
188+
<?=mk_option($display['scale'], "2",_('MB'))?>
189+
<?=mk_option($display['scale'], "3",_('GB'))?>
190+
<?=mk_option($display['scale'], "4",_('TB'))?>
191+
<?=mk_option($display['scale'], "5",_('PB'))?>
192+
</select>
193+
194+
_(Page view)_:
195+
: <select name="tabs">
196+
<?=mk_option($display['tabs'], "0",_('Tabbed'))?>
197+
<?=mk_option($display['tabs'], "1",_('Non-tabbed'))?>
198+
</select>
199+
200+
:display_page_view_help:
201+
202+
_(Placement of Users menu)_:
203+
: <select name="users">
204+
<?=mk_option($display['users'], "Tasks:3",_('Header menu'))?>
205+
<?=mk_option($display['users'], "UserPreferences",_('Settings menu'))?>
206+
</select>
207+
208+
:display_users_menu_help:
209+
210+
_(Listing height)_:
211+
: <select name="resize">
212+
<?=mk_option($display['resize'], "0",_('Automatic'))?>
213+
<?=mk_option($display['resize'], "1",_('Fixed'))?>
214+
</select>
215+
216+
:display_listing_height_help:
217+
218+
_(Display device name)_:
219+
: <select name="raw">
220+
<?=mk_option($display['raw'], "",_('Normalized'))?>
221+
<?=mk_option($display['raw'], "1",_('Raw'))?>
222+
</select>
223+
224+
_(Display world-wide-name in device ID)_:
225+
: <select name="wwn">
226+
<?=mk_option($display['wwn'], "0",_('Disabled'))?>
227+
<?=mk_option($display['wwn'], "1",_('Automatic'))?>
228+
</select>
229+
230+
:display_wwn_device_id_help:
231+
232+
_(Display array totals)_:
233+
: <select name="total">
234+
<?=mk_option($display['total'], "0",_('No'))?>
235+
<?=mk_option($display['total'], "1",_('Yes'))?>
236+
</select>
237+
238+
_(Show array utilization indicator)_:
239+
: <select name="usage">
240+
<?=mk_option($display['usage'], "0",_('No'))?>
241+
<?=mk_option($display['usage'], "1",_('Yes'))?>
242+
</select>
243+
244+
_(Temperature unit)_:
245+
: <select name="unit">
246+
<?=mk_option($display['unit'], "C",_('Celsius'))?>
247+
<?=mk_option($display['unit'], "F",_('Fahrenheit'))?>
248+
</select>
249+
250+
:display_temperature_unit_help:
251+
252+
_(Dynamix color theme)_:
253+
: <select name="theme">
254+
<?foreach (glob("$docroot/webGui/styles/themes/*.css") as $themes):?>
255+
<?$theme = basename($themes, '.css');?>
256+
<?=mk_option($display['theme'], $theme, _(ucfirst($theme)))?>
257+
<?endforeach;?>
258+
</select>
259+
260+
_(Used / Free columns)_:
261+
: <select name="text">
262+
<?=mk_option($display['text'], "0",_('Text'))?>
263+
<?=mk_option($display['text'], "1",_('Bar (gray)'))?>
264+
<?=mk_option($display['text'], "2",_('Bar (color)'))?>
265+
<?=mk_option($display['text'], "10",_('Text - Bar (gray)'))?>
266+
<?=mk_option($display['text'], "20",_('Text - Bar (color)'))?>
267+
<?=mk_option($display['text'], "11",_('Bar (gray) - Text'))?>
268+
<?=mk_option($display['text'], "21",_('Bar (color) - Text'))?>
269+
</select>
270+
271+
_(Header custom text color)_:
272+
: <input type="text" class="narrow" name="header" value="<?=$display['header']?>" maxlength="6" pattern="([0-9a-fA-F]{3}){1,2}" title="_(HTML color code of 3 or 6 hexadecimal digits)_">
273+
274+
:display_custom_text_color_help:
275+
276+
_(Header custom secondary text color)_:
277+
: <input type="text" class="narrow" name="headermetacolor" value="<?=$display['headermetacolor']?>" maxlength="6" pattern="([0-9a-fA-F]{3}){1,2}" title="_(HTML color code of 3 or 6 hexadecimal digits)_">
278+
279+
_(Header custom background color)_:
280+
: <input type="text" class="narrow" name="background" value="<?=$display['background']?>" maxlength="6" pattern="([0-9a-fA-F]{3}){1,2}" title="_(HTML color code of 3 or 6 hexadecimal digits)_">
281+
282+
:display_custom_background_color_help:
283+
284+
_(Header show description)_:
285+
: <select name="headerdescription">
286+
<?=mk_option($display['headerdescription'], "yes",_('Yes'))?>
287+
<?=mk_option($display['headerdescription'], "no",_('No'))?>
288+
</select>
289+
290+
_(Show banner)_:
291+
: <select name="banner" onchange="presetBanner(this.form)">
292+
<?=mk_option($display['banner'], "",_('No'))?>
293+
<?=mk_option($display['banner'], "image",_('Yes'))?>
294+
</select>
295+
296+
<div class="js-bannerSettings" markdown="1" style="display:none">
297+
_(Custom banner)_:
298+
<input type="hidden" name="#custom" value="">
299+
: <span id="dropbox">
300+
<?if (file_exists($banner)):?>
301+
<img src="<?=autov($banner)?>" width="330" height="30" onclick="$('#drop').click()" style="cursor:pointer" title="_(Click to select PNG file)_"><?=$icon?>
302+
<?else:?>
303+
<?=$void?>
304+
<?endif;?>
305+
</span><em>_(Drag-n-drop a PNG file or click the image at the left)_.</em><input type="file" id="drop" accept="image/*" style="display:none">
306+
307+
:display_custom_banner_help:
308+
</div>
309+
310+
<div class="js-bannerSettings" markdown="1" style="display:none">
311+
_(Show banner background color fade)_:
312+
: <select name="showBannerGradient">
313+
<?=mk_option($display['showBannerGradient'], "no",_('No'))?>
314+
<?=mk_option($display['showBannerGradient'], "yes",_('Yes'))?>
315+
</select>
316+
</div>
317+
318+
_(Favorites enabled)_:
319+
: <select name="favorites">
320+
<?=mk_option($display['favorites'], "yes",_('Yes'))?>
321+
<?=mk_option($display['favorites'], "no",_('No'))?>
322+
</select>
323+
324+
:display_favorites_enabled_help:
325+
326+
_(Allow realtime updates on inactive browsers)_:
327+
: <select name='liveUpdate'>
328+
<?=mk_option($display['liveUpdate'],"no",_('No'))?>
329+
<?=mk_option($display['liveUpdate'],"yes",_('Yes'))?>
330+
</select>
331+
332+
<input type="submit" name="#default" value="_(Default)_" onclick="filename='reset'">
333+
: <input type="submit" name="#apply" value="_(Apply)_" disabled><input type="button" value="_(Done)_" onclick="done()">
334+
</form>

api/src/unraid-api/unraid-file-modifier/modifications/__test__/generic-modification.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { describe, expect, test, vi } from 'vitest';
88
import { FileModification } from '@app/unraid-api/unraid-file-modifier/file-modification.js';
99
import AuthRequestModification from '@app/unraid-api/unraid-file-modifier/modifications/auth-request.modification.js';
1010
import DefaultPageLayoutModification from '@app/unraid-api/unraid-file-modifier/modifications/default-page-layout.modification.js';
11+
import DisplaySettingsModification from '@app/unraid-api/unraid-file-modifier/modifications/display-settings.modification.js';
1112
import NotificationsPageModification from '@app/unraid-api/unraid-file-modifier/modifications/notifications-page.modification.js';
1213
import RcNginxModification from '@app/unraid-api/unraid-file-modifier/modifications/rc-nginx.modification.js';
1314
import SSOFileModification from '@app/unraid-api/unraid-file-modifier/modifications/sso.modification.js';
@@ -35,6 +36,12 @@ const patchTestCases: ModificationTestCase[] = [
3536
'https://raw.githubusercontent.com/unraid/webgui/refs/heads/7.1/emhttp/plugins/dynamix/Notifications.page',
3637
fileName: 'Notifications.page',
3738
},
39+
{
40+
ModificationClass: DisplaySettingsModification,
41+
fileUrl:
42+
'https://raw.githubusercontent.com/unraid/webgui/refs/heads/7.1/emhttp/plugins/dynamix/DisplaySettings.page',
43+
fileName: 'DisplaySettings.page',
44+
},
3845
{
3946
ModificationClass: SSOFileModification,
4047
fileUrl:

0 commit comments

Comments
 (0)