🦄 Sync 2026-11-13 03:09:08

This commit is contained in:
github-actions[bot] committed 2026-11-13 03:09:08 +08:00
1 parent b8837465cd
commit be1c08bda8
255 files changed
+45916 -22815

No files matched your search

@@ -0,0 +1,6 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><filter id="a" x="-.1216" y="-.5614" width="1.243" height="2.123" color-interpolation-filters="sRGB"><feFlood flood-color="rgb(0,0,0)" in="SourceGraphic" result="flood"/><feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="0.100000"/><feOffset dx="0.000000" dy="0.000000" in="blur" result="offset"/><feComposite in="flood" in2="offset" operator="in" result="comp1"/><feComposite in="SourceGraphic" in2="comp1" result="fbSourceGraphic"/><feColorMatrix in="fbSourceGraphic" result="fbSourceGraphicAlpha" values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"/><feFlood flood-color="rgb(255,255,255)" in="fbSourceGraphic" result="flood"/><feGaussianBlur in="fbSourceGraphic" result="blur" stdDeviation="0.100000"/><feOffset dx="0.000000" dy="0.000000" in="blur" result="offset"/><feComposite in="flood" in2="offset" operator="in" result="comp1"/><feComposite in="fbSourceGraphic" in2="comp1" result="comp2"/></filter><filter id="b" x="-.1294" y="-.4062" width="1.259" height="1.939" color-interpolation-filters="sRGB"><feFlood flood-color="rgb(0,0,0)" in="SourceGraphic" result="flood"/><feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="0.200000"/><feOffset dx="0.000000" dy="0.000000" in="blur" result="offset"/><feComposite in="offset" in2="flood" operator="in" result="comp1"/><feComposite in="comp1" in2="comp1" operator="atop" result="comp2"/></filter></defs><g><g display="none" fill="#fff" filter="url(#b)"><ellipse transform="matrix(1.172 0 0 1.176 -.9828 -1.265)" cx="6.244" cy="6.975" rx="4.23" ry="4.197" display="none" filter="url(#a)" style="paint-order:stroke fill markers"/><rect transform="matrix(-.8208 .8391 .836 .8238 -.951 -1.646)" x="-1.56" y="3.974" width="4.41" height=".855" rx=".6935" filter="url(#a)" style="paint-order:stroke fill markers"/><rect transform="matrix(.8208 .8391 -.836 .8238 -.9828 -1.646)" x="7.22" y="-4.969" width="4.41" height=".855" rx=".6935" filter="url(#a)" style="paint-order:stroke fill markers"/></g></g><g display="none"><g display="none" fill="#fff" fill-opacity=".1511" filter="url(#b)"><ellipse transform="matrix(1.172 0 0 1.176 -.9828 -1.265)" cx="6.244" cy="6.975" rx="4.23" ry="4.197" display="inline" filter="url(#a)" style="paint-order:stroke fill markers"/><rect transform="matrix(-.8208 .8391 .836 .8238 -.951 -1.646)" x="-1.56" y="3.974" width="4.41" height=".855" rx=".6935" display="inline" filter="url(#a)" style="paint-order:stroke fill markers"/><rect transform="matrix(.8208 .8391 -.836 .8238 -.9828 -1.646)" x="7.22" y="-4.969" width="4.41" height=".855" rx=".6935" display="inline" filter="url(#a)" style="paint-order:stroke fill markers"/></g></g><g><ellipse transform="matrix(1.172 0 0 1.176 -.9828 -1.265)" cx="6.244" cy="6.975" rx="4.23" ry="4.197" fill="#fff" fill-opacity=".04534" filter="url(#a)" stroke="#000" stroke-linejoin="round" stroke-opacity=".7531" stroke-width=".2956" style="paint-order:stroke fill markers"/><rect transform="matrix(.8208 .8391 -.836 .8238 -.9828 -1.646)" x="7.22" y="-4.969" width="4.41" height=".855" rx=".6935" fill="#1a1a1a" filter="url(#a)" style="paint-order:stroke fill markers"/><path transform="matrix(.399 -1.015 1.033 .3952 2.578 -2.635)" d="m-3.237 5.853a3.947 4.426 0 0 1-2.126 3.927l-1.821-3.927z" fill="#00b8e0" filter="url(#a)" style="paint-order:stroke fill markers"/></g></svg>

After

Width:  |  Height:  |  Size: 3.5 KiB

@@ -0,0 +1,24 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.3 -0.3 48.6 32.6" width="48" height="32">
<defs>
<linearGradient id="grayGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8E8E8"/>
<stop offset="100%" stop-color="#F9F9F9"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="0.6" flood-color="#000000" flood-opacity="0.15"/>
</filter>
</defs>
<g filter="url(#shadow)" stroke="#333333" stroke-width="0.4" stroke-linejoin="round">
<rect x="0.5" y="25.73" width="7" height="5.97" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="10.25" y="20.22" width="7" height="11.48" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="20.0" y="14.24" width="7" height="17.46" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="29.75" y="8.27" width="7" height="23.43" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="39.5" y="2.3" width="7" height="29.4" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

@@ -0,0 +1,33 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.3 -0.3 48.6 32.6" width="48" height="32">
<defs>
<linearGradient id="grayGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8E8E8"/>
<stop offset="100%" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="redGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FF8A8A"/>
<stop offset="50%" stop-color="#FF5F5F"/>
<stop offset="100%" stop-color="#E84C4C"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="0.6" flood-color="#000000" flood-opacity="0.15"/>
</filter>
</defs>
<g filter="url(#shadow)" stroke="#333333" stroke-width="0.4" stroke-linejoin="round">
<rect x="0.5" y="25.73" width="7" height="5.97" rx="1.2" fill="url(#redGrad)"/>
<rect x="10.25" y="20.22" width="7" height="11.48" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="20.0" y="14.24" width="7" height="17.46" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="29.75" y="8.27" width="7" height="23.43" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="39.5" y="2.3" width="7" height="29.4" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
</g>
<g fill="#FFFFFF" opacity="0.45">
<path d="M2.00 25.73 L3.50 25.73 L2.50 31 L1.00 31 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,34 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.3 -0.3 48.6 32.6" width="48" height="32">
<defs>
<linearGradient id="grayGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8E8E8"/>
<stop offset="100%" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="orangeGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFCB7A"/>
<stop offset="50%" stop-color="#FFB347"/>
<stop offset="100%" stop-color="#E89A2E"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="0.6" flood-color="#000000" flood-opacity="0.15"/>
</filter>
</defs>
<g filter="url(#shadow)" stroke="#333333" stroke-width="0.4" stroke-linejoin="round">
<rect x="0.5" y="25.73" width="7" height="5.97" rx="1.2" fill="url(#orangeGrad)"/>
<rect x="10.25" y="20.22" width="7" height="11.48" rx="1.2" fill="url(#orangeGrad)"/>
<rect x="20.0" y="14.24" width="7" height="17.46" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="29.75" y="8.27" width="7" height="23.43" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="39.5" y="2.3" width="7" height="29.4" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
</g>
<g fill="#FFFFFF" opacity="0.45">
<path d="M2.00 25.73 L3.50 25.73 L2.50 31 L1.00 31 Z"/>
<path d="M11.75 20.22 L13.25 20.22 L12.25 31 L10.75 31 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.5 KiB

@@ -0,0 +1,35 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.3 -0.3 48.6 32.6" width="48" height="32">
<defs>
<linearGradient id="grayGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8E8E8"/>
<stop offset="100%" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="yellowGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#FFF8A8"/>
<stop offset="50%" stop-color="#FFEE6B"/>
<stop offset="100%" stop-color="#E8D04E"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="0.6" flood-color="#000000" flood-opacity="0.15"/>
</filter>
</defs>
<g filter="url(#shadow)" stroke="#333333" stroke-width="0.4" stroke-linejoin="round">
<rect x="0.5" y="25.73" width="7" height="5.97" rx="1.2" fill="url(#yellowGrad)"/>
<rect x="10.25" y="20.22" width="7" height="11.48" rx="1.2" fill="url(#yellowGrad)"/>
<rect x="20.0" y="14.24" width="7" height="17.46" rx="1.2" fill="url(#yellowGrad)"/>
<rect x="29.75" y="8.27" width="7" height="23.43" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
<rect x="39.5" y="2.3" width="7" height="29.4" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
</g>
<g fill="#FFFFFF" opacity="0.45">
<path d="M2.00 25.73 L3.50 25.73 L2.50 31 L1.00 31 Z"/>
<path d="M11.75 20.22 L13.25 20.22 L12.25 31 L10.75 31 Z"/>
<path d="M21.50 14.24 L23.00 14.24 L22.00 31 L20.50 31 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,36 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.3 -0.3 48.6 32.6" width="48" height="32">
<defs>
<linearGradient id="grayGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8E8E8"/>
<stop offset="100%" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="greenGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7CFF52"/>
<stop offset="50%" stop-color="#6BFF3E"/>
<stop offset="100%" stop-color="#5FEC34"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="0.6" flood-color="#000000" flood-opacity="0.15"/>
</filter>
</defs>
<g filter="url(#shadow)" stroke="#333333" stroke-width="0.4" stroke-linejoin="round">
<rect x="0.5" y="25.73" width="7" height="5.97" rx="1.2" fill="url(#greenGrad)"/>
<rect x="10.25" y="20.22" width="7" height="11.48" rx="1.2" fill="url(#greenGrad)"/>
<rect x="20.0" y="14.24" width="7" height="17.46" rx="1.2" fill="url(#greenGrad)"/>
<rect x="29.75" y="8.27" width="7" height="23.43" rx="1.2" fill="url(#greenGrad)"/>
<rect x="39.5" y="2.3" width="7" height="29.4" rx="1.2" fill="url(#grayGrad)" fill-opacity="0.5"/>
</g>
<g fill="#FFFFFF" opacity="0.45">
<path d="M2.00 25.73 L3.50 25.73 L2.50 31 L1.00 31 Z"/>
<path d="M11.75 20.22 L13.25 20.22 L12.25 31 L10.75 31 Z"/>
<path d="M21.50 14.24 L23.00 14.24 L22.00 31 L20.50 31 Z"/>
<path d="M31.25 8.27 L32.75 8.27 L31.75 31 L30.25 31 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

@@ -0,0 +1,37 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="-0.3 -0.3 48.6 32.6" width="48" height="32">
<defs>
<linearGradient id="grayGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#E8E8E8"/>
<stop offset="100%" stop-color="#F9F9F9"/>
</linearGradient>
<linearGradient id="greenGrad" x1="0" y1="0" x2="0" y2="1">
<stop offset="0%" stop-color="#7CFF52"/>
<stop offset="50%" stop-color="#6BFF3E"/>
<stop offset="100%" stop-color="#5FEC34"/>
</linearGradient>
<filter id="shadow" x="-20%" y="-20%" width="140%" height="140%">
<feDropShadow dx="0" dy="1" stdDeviation="0.6" flood-color="#000000" flood-opacity="0.15"/>
</filter>
</defs>
<g filter="url(#shadow)" stroke="#333333" stroke-width="0.4" stroke-linejoin="round">
<rect x="0.5" y="25.73" width="7" height="5.97" rx="1.2" fill="url(#greenGrad)"/>
<rect x="10.25" y="20.22" width="7" height="11.48" rx="1.2" fill="url(#greenGrad)"/>
<rect x="20.0" y="14.24" width="7" height="17.46" rx="1.2" fill="url(#greenGrad)"/>
<rect x="29.75" y="8.27" width="7" height="23.43" rx="1.2" fill="url(#greenGrad)"/>
<rect x="39.5" y="2.3" width="7" height="29.4" rx="1.2" fill="url(#greenGrad)"/>
</g>
<g fill="#FFFFFF" opacity="0.45">
<path d="M2.00 25.73 L3.50 25.73 L2.50 31 L1.00 31 Z"/>
<path d="M11.75 20.22 L13.25 20.22 L12.25 31 L10.75 31 Z"/>
<path d="M21.50 14.24 L23.00 14.24 L22.00 31 L20.50 31 Z"/>
<path d="M31.25 8.27 L32.75 8.27 L31.75 31 L30.25 31 Z"/>
<path d="M41.00 2.3 L42.50 2.3 L41.50 31 L40.00 31 Z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.7 KiB

@@ -0,0 +1,6 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg width="32" height="32" version="1.1" viewBox="0 0 8.467 8.467" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><defs><clipPath id="a"><path d="m1.321 0.04651v8.374h1.549c-0.02673-0.08837-0.04032-0.1804-0.05013-0.2723-0.008371-0.0947-0.009739-0.1897-0.01137-0.2847-9.26e-4 -0.05919-0.001296-0.1186-0.001561-0.1778-0.00322-0.05481 0.01484-0.1064 0.03462-0.1566 0.01232-0.03628 0.03829-0.06289 0.0615-0.09198 0.00563-0.0063 0.01133-0.01299 0.01654-0.01964-0.00724-3.704e-4 -0.01447-6.614e-4 -0.0217-0.00103-0.1156-0.00602-0.1027-0.1221 0.01292-0.1163 0.03892 0.00196 0.07785 0.00354 0.1168 0.00517 0.03617-0.03287 0.09876-0.05147 0.1835-0.03721 0.2067 0.03481 0.4168 0.04724 0.6258 0.06098 0.00971 2.91e-4 0.01922 7.408e-4 0.02894 0.00103 0.1252 0.00458 0.2502 0.01099 0.3752 0.02015 0.02336 4.498e-4 0.0469 6.879e-4 0.07028 5.292e-4 0.07628-5.557e-4 0.1053-0.00376 0.1814-0.0093 0.1703-0.01637 0.34-0.03571 0.5106-0.04858 0.1147-0.00917 0.2298-0.01034 0.3447-0.01654 0.05541-0.00368 0.1109-0.00803 0.1664-0.01085 0.02133-0.00177 0.04287-0.00383 0.06408-0.00672 0.01577-0.00214 0.03039-0.0031 0.04444-0.0031 0.2108-5.45e-5 0.2263 0.2265-0.01034 0.2579-0.02822 0.00374-0.05686 0.00678-0.08527 0.00879-0.05525 0.00279-0.1102 0.00729-0.1654 0.01085-0.03998 0.00212-0.07995 0.00357-0.1199 0.00517 0.1153 0.02049 0.1505 0.1176 0.09353 0.186 0.01285 0.02075 0.0251 0.04213 0.03669 0.06356 0.06642 0.032 0.08902 0.09154 0.06821 0.1426 0.00936 0.02177 0.01853 0.04363 0.02739 0.06563 0.00745 0.01983 0.01497 0.03972 0.02274 0.05943 0.1194 0.0319 0.1468 0.1331 0.08578 0.1959 0.00321 0.00653 0.00654 0.01314 0.00982 0.01964 0.02544 0.04938 0.05173 0.09845 0.0739 0.1493h1.486v-8.374zm1.709 7.417c-0.01172 0.01618-0.02389 0.03212-0.03721 0.04703-0.01609 0.0193-0.03441 0.03685-0.04341 0.06098-0.01442 0.03636-0.0294 0.07323-0.02636 0.1132 2.501e-4 0.05869 6.35e-4 0.1175 0.00156 0.1762 0.00156 0.09155 0.00295 0.1831 0.01085 0.2744 0.0102 0.09643 0.02524 0.193 0.05736 0.2847h0.9111c-0.01698-0.004919-0.03381-0.01007-0.05064-0.0155-0.1185-0.03823-0.1594-0.1159-0.1421-0.1762-0.02371-0.002434-0.04766-0.00498-0.07131-0.007752-0.1634-0.01915-0.2132-0.1329-0.1597-0.2031-0.1761-0.02793-0.2004-0.1865-0.07235-0.2377-0.01294-0.001164-0.02583-0.002275-0.03876-0.003617-0.2006-0.02075-0.229-0.1856-0.1065-0.2403-0.03654-0.00517-0.07322-0.01109-0.1096-0.01757-0.05595-0.00998-0.09642-0.03002-0.123-0.05478zm2.316 0.5447c-0.02516 0.00817-0.05489 0.0129-0.0894 0.01292-0.06069 3.62e-5 -0.1212 4.233e-4 -0.1819 5.291e-4 -0.09855 1.67e-4 -0.197 6.615e-4 -0.2956 0.00103-0.01102 0.00375-0.02231 0.00655-0.03359 0.0093 0.06909 0.00254 0.1381 0.00494 0.2072 0.00672 0.1163 0.00268 0.2325 0.0043 0.3488 0.00517 0.01964-2.86e-5 0.03923-2.238e-4 0.05891 0-5.557e-4 -0.00146-0.00101-0.00267-0.00156-0.00413-0.00422-0.01053-0.00859-0.02103-0.01292-0.03152zm-0.8676 0.2698c0.09998 0.01784 0.2003 0.03463 0.3008 0.04909 0.06178 0.01066 0.1237 0.01795 0.1855 0.02842 0.02646 0.00542 0.05318 0.00932 0.07958 0.01499 0.04652 0.00998 0.08198 0.02778 0.1075 0.04961h0.3793c-0.01607-0.03259-0.03331-0.06482-0.04961-0.09715-0.00384-0.00765-0.00761-0.01557-0.01137-0.02325-0.01761 0.00257-0.03692 0.00387-0.05788 0.0031-0.03738-0.00138-0.07474-0.00146-0.1121-0.00156-0.1179-8.735e-4 -0.2361-0.002434-0.354-0.005168-0.156-0.004022-0.3119-0.0104-0.4677-0.01809z" fill="#f00"/></clipPath></defs><g><path d="m4.233 6.724a2.522 0.7425 0 0 0-2.522 0.7426 2.522 0.7425 0 0 0 2.522 0.7421 2.522 0.7425 0 0 0 2.522-0.7421 2.522 0.7425 0 0 0-2.522-0.7426zm0 0.1049a2.029 0.5554 0 0 1 2.029 0.5555 2.029 0.5554 0 0 1-2.029 0.5555 2.029 0.5554 0 0 1-2.029-0.5555 2.029 0.5554 0 0 1 2.029-0.5555z" fill="#006680" stroke="#404040" stroke-linecap="round" stroke-linejoin="round" stroke-width=".033" style="paint-order:markers stroke fill"/><path d="m4.233 0.04671c-1.608 0-2.912 1.248-2.912 2.786s1.444 4.182 2.912 5.587c1.655-1.583 2.912-4.049 2.912-5.587s-1.304-2.786-2.912-2.786zm0 0.7877a2.096 2.096 0 0 1 2.096 2.096 2.096 2.096 0 0 1-2.096 2.095 2.096 2.096 0 0 1-2.096-2.095 2.096 2.096 0 0 1 2.096-2.096z" clip-path="url(#a)" display="none" fill="#008080" style="paint-order:markers stroke fill"/><path d="m4.233 0.2026c-1.392 0-2.522 1.081-2.522 2.413 0 1.332 1.25 3.622 2.522 4.838 1.433-1.371 2.522-3.506 2.522-4.838 0-1.332-1.129-2.413-2.522-2.413zm0 1.049a1.339 1.339 0 0 1 1.339 1.339 1.339 1.339 0 0 1-1.339 1.339 1.339 1.339 0 0 1-1.339-1.339 1.339 1.339 0 0 1 1.339-1.339z" fill="#00aad4" stroke="#3f3f3f" stroke-linecap="round" stroke-linejoin="round" stroke-width=".03307" style="paint-order:markers stroke fill"/></g></svg>

After

Width:  |  Height:  |  Size: 4.6 KiB

@@ -0,0 +1,6 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg width="48" height="48" version="1.1" viewBox="0 0 12.7 12.7" xml:space="preserve" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><defs><linearGradient id="a" x1="3.367" x2="9.428" y1="5.894" y2="10.74" gradientTransform="translate(-.09203 -.2079)" gradientUnits="userSpaceOnUse"><stop stop-color="#fddb6f" offset="0"/><stop stop-color="#f99b02" offset="1"/></linearGradient><filter id="b" x="-.07393" y="-.04038" width="1.154" height="1.094" color-interpolation-filters="sRGB"><feFlood flood-color="rgb(0,0,0)" flood-opacity=".498" in="SourceGraphic" result="flood"/><feGaussianBlur in="SourceGraphic" result="blur" stdDeviation="0.25971326"/><feOffset dx="0.300000" dy="0.300000" in="blur" result="offset"/><feComposite in="flood" in2="offset" operator="in" result="comp1"/><feComposite in="SourceGraphic" in2="comp1" result="fbSourceGraphic"/><feColorMatrix in="fbSourceGraphic" result="fbSourceGraphicAlpha" values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"/><feFlood flood-color="rgb(0,0,0)" flood-opacity=".498" in="fbSourceGraphic" result="flood"/><feGaussianBlur in="fbSourceGraphic" result="blur" stdDeviation="0.300000"/><feOffset dx="0.300000" dy="0.300000" in="blur" result="offset"/><feComposite in="flood" in2="offset" operator="in" result="comp1"/><feComposite in="fbSourceGraphic" in2="comp1" result="fbSourceGraphic"/><feColorMatrix in="fbSourceGraphic" result="fbSourceGraphicAlpha" values="0 0 0 -1 0 0 0 0 -1 0 0 0 0 -1 0 0 0 0 1 0"/><feFlood flood-color="rgb(0,0,0)" flood-opacity=".498" in="fbSourceGraphic" result="flood"/><feGaussianBlur in="fbSourceGraphic" result="blur" stdDeviation="0.200000"/><feOffset dx="-0.400000" dy="0.000000" in="blur" result="offset"/><feComposite in="flood" in2="offset" operator="in" result="comp1"/><feComposite in="fbSourceGraphic" in2="comp1" result="comp2"/></filter></defs><g display="none"><path transform="matrix(.2646 0 0 .2462 0 .3654)" d="m10.63 0.6328-0.3594 0.166c-0.5182 0.2398-0.9426 0.6431-1.154 1.098l-0.1855 0.3965v21.67c-8.488e-4 23.82-0.04002 21.99 0.4746 22.64 0.1402 0.177 0.4443 0.4058 0.7266 0.5469l0.4863 0.2422 27.68-0.04101 0.3828-0.1953c0.4502-0.23 0.8-0.6057 1.031-1.105l0.166-0.3594s0.0027-13.85-0.0049-26.1c-2.54e-4 -0.4089-0.0014-1.072-0.0026-1.481-0.01769-6.361-0.01672-9.287-0.01291-10.62 0.0017-0.5982-0.3519-1.41-0.7943-1.813l-1.087-0.9899c-0.8472-0.7714-1.693-1.545-2.537-2.32l-0.6274-0.5762c-0.8141-0.7477-1.879-1.163-2.985-1.163h-7.443z" display="inline" fill="#dae2e1" fill-opacity=".03" filter="url(#b)" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width=".1296" style="mix-blend-mode:hard-light;paint-order:markers stroke fill"/></g><g><rect x="3.275" y="5.398" width="6.149" height="6.045" rx=".7136" ry=".7136" fill="url(#a)" stroke="#000" stroke-width=".1"/><g transform="translate(-.09203 -.2079)" stroke="#000" stroke-opacity=".4975"><path d="m9.472 7.541-4.657-0.02644" stroke-width=".09862"/><path d="m4.858 5.636v1.905" stroke-width=".1"/><path d="m6.442 5.605v1.905" stroke-width=".1"/><path d="m8.043 5.605v1.905" stroke-width=".1"/><path d="m9.517 9.496-6.149-0.02644" stroke-width=".1133"/><path d="m4.841 9.476v2.174" stroke-width=".1068"/><path d="m6.442 9.483v2.174" stroke-width=".1068"/><path d="m8.086 9.49v2.174" stroke-width=".1068"/></g><text x="3.582727" y="4.3639469" fill="#a5a5a5" font-family="Calibri" font-size="3.175px" stroke="#000000" stroke-width=".007" xml:space="preserve"><tspan x="3.582727" y="4.3639469" fill="#a5a5a5" font-family="Arial" font-weight="bold" stroke-width=".007">SIM</tspan></text></g><path transform="matrix(1.002 0 0 1 -.00483 0)" d="m2.894 0.5126h5.755c0.3593 0 1.885 1.273 1.885 1.6v9.471c0 0.242-0.1962 0.4382-0.4382 0.4382h-7.255c-0.2534 0-0.4589-0.2055-0.4589-0.4589v-10.54c0-0.2831 0.2295-0.5126 0.5126-0.5126z" fill="#d8d8d8" fill-opacity=".2485" stroke="#262626" stroke-linecap="round" stroke-linejoin="round" stroke-width=".1388" style="paint-order:fill markers stroke"/></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,6 @@
<!--
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
https://github.com/4IceG
MIT License
-->
<svg width="32" height="32" version="1.1" viewBox="0 0 8.467 8.467" xml:space="preserve" xmlns="http://www.w3.org/2000/svg"><g stroke="#000" stroke-linecap="round" stroke-linejoin="round"><path d="m4.155 0.3664c-0.4348 0-0.785 0.3497-0.785 0.7844v1.696c0 0.0172 4.714e-4 0.03424 0.00155 0.05116-0.00108 0.01692-0.00155 0.03396-0.00155 0.05116v1.696 0.5643c-0.4468 0.2915-0.7162 0.7889-0.7162 1.322-9.62e-5 0.8722 0.707 1.579 1.579 1.579 0.8722 9.62e-5 1.579-0.707 1.579-1.579 5.01e-5 -0.5351-0.2709-1.034-0.7199-1.325v-0.8211-1.436c0-0.0172-9.817e-4 -0.03424-0.002067-0.05116 0.00108-0.01692 0.002067-0.03396 0.002067-0.05116v-1.696c0-0.4348-0.3502-0.7844-0.785-0.7844h-0.1524zm0.02532 0.2708h0.107c0.305 0 0.5509 0.2453 0.5509 0.5504v1.19c0 0.01207-7.944e-4 0.02379-0.00155 0.03566 7.572e-4 0.01187 0.00155 0.02359 0.00155 0.03566v1.008 0.2052 0.371c0.01028 0.00672 0.02044 0.01361 0.03049 0.02067h-0.03049v0.7979c0 0.01207-7.944e-4 0.02379-0.00155 0.03566 7.569e-4 0.01187 0.00155 0.02359 0.00155 0.03566v0.08578l-0.001034 0.1106c0.00473 0.2846 0.08672 0.2746 0.1581 0.339 0.02365 0.01681 0.04675 0.03439 0.06925 0.05271 0.02301 0.01596 0.04371 0.03082 0.05943 0.05013h-0.001034c0.2767 0.2517 0.4345 0.6083 0.4346 0.9824 6.87e-5 0.7338-0.5948 1.329-1.329 1.329-0.7336-2.167e-4 -1.328-0.595-1.328-1.329 2.06e-5 -0.3715 0.2676-0.8128 0.5473-1.07 0.08599-0.06315 0.189-0.1801 0.1798-0.4444l0.00155-0.04341v-0.06305c0-0.01207 2.808e-4 -0.02379 0.001034-0.03566-7.463e-4 -0.01187-0.001034-0.02359-0.001034-0.03566v-0.7979h-0.02791c0.0092-0.00652 0.01851-0.01289 0.02791-0.01912v-0.3726-0.02325-1.19c0-0.01207 2.808e-4 -0.02379 0.001034-0.03566-7.498e-4 -0.01187-0.001034-0.02359-0.001034-0.03566v-1.19c0-0.305 0.2458-0.5504 0.5509-0.5504z" fill="#545454" stroke-width=".0471" style="paint-order:markers stroke fill"/><path d="m4.209 3.101c-0.1347 0-0.2434 0.1081-0.2434 0.2429v0.5255c0 0.00533 1.823e-4 0.01078 5.168e-4 0.01602-3.347e-4 0.00524-5.168e-4 0.01069-5.168e-4 0.01602v0.525 0.07596 0.4806c0 0.00533 1.823e-4 0.01078 5.168e-4 0.01602-3.347e-4 0.00524-5.168e-4 0.01069-5.168e-4 0.01602v0.3318l-0.003101-0.002584c0.001556 0.01577 0.002578 0.03085 0.003101 0.04548 5.612e-4 0.0157 5.472e-4 0.03104 0 0.04548-0.005376 0.1418-0.0623 0.2238-0.1307 0.2734a0.9387 0.9387 0 0 0-0.08578 0.04599c-0.005764 0.002228-0.01137 0.004246-0.01705 0.006201l0.005684 5.167e-4a0.9387 0.9387 0 0 0-0.4429 0.7974 0.9387 0.9387 0 0 0 0.9384 0.939 0.9387 0.9387 0 0 0 0.9384-0.939 0.9387 0.9387 0 0 0-0.4517-0.8025l0.006201-5.168e-4c-0.006096-0.002097-0.01242-0.004306-0.0186-0.006718a0.9387 0.9387 0 0 0-0.07906-0.04134c-0.05941-0.04122-0.1108-0.1061-0.1292-0.2119v-0.03566-0.4449c0-0.00533-1.814e-4 -0.01078-5.168e-4 -0.01602 3.347e-4 -0.00524 5.168e-4 -0.01069 5.168e-4 -0.01602v-0.5255-0.1111-0.4449c0-0.00533-1.814e-4 -0.01078-5.168e-4 -0.01602 3.347e-4 -0.00524 5.168e-4 -0.01069 5.168e-4 -0.01602v-0.5255c0-0.1347-0.1081-0.2429-0.2429-0.2429h-0.04754z" fill="#ff2a2a" stroke-width=".033" style="paint-order:markers stroke fill"/></g><path d="m2.762 1.552c0.1462 0 0.2641 0.08971 0.2641 0.201 0 0.1113-0.1178 0.201-0.2641 0.201h-1.481c-0.1462 0-0.2641-0.08971-0.2641-0.201 0-0.1113 0.1178-0.201 0.2641-0.201zm-0.01654 0.7545c0.1462 0 0.2641 0.08971 0.2641 0.201 0 0.1113-0.1178 0.201-0.2641 0.201h-0.7948c-0.1462 0-0.2641-0.08971-0.2641-0.201 0-0.1113 0.1178-0.201 0.2641-0.201zm0 0.7545c0.1462 0 0.2641 0.08971 0.2641 0.201 0 0.1113-0.1178 0.201-0.2641 0.201h-1.481c-0.1462 0-0.2641-0.08971-0.2641-0.201 0-0.1113 0.1178-0.201 0.2641-0.201zm-0.02481 0.7545c0.1462 0 0.2641 0.08971 0.2641 0.201 0 0.1113-0.1178 0.201-0.2641 0.201h-0.7948c-0.1462 0-0.2641-0.08971-0.2641-0.201 0-0.1113 0.1178-0.201 0.2641-0.201zm0.0248 0.7545c0.1462 0 0.2641 0.08971 0.2641 0.201 0 0.1113-0.1178 0.201-0.2641 0.201h-1.481c-0.1462 0-0.2641-0.08971-0.2641-0.201 0-0.1113 0.1178-0.201 0.2641-0.201z" fill="#545454" stroke="#000" stroke-linecap="round" stroke-linejoin="round" stroke-width=".0471" style="paint-order:stroke markers fill"/></svg>

After

Width:  |  Height:  |  Size: 4.0 KiB

@@ -0,0 +1,177 @@
'use strict';
'require baseclass';
'require form';
'require fs';
'require view';
'require uci';
'require ui';
'require tools.widgets as widgets';
/*
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
MIT License
*/
let pkg = {
get Name() { return 'modemdata'; },
get URL() { return 'https://openwrt.org/packages/pkgdata/' + pkg.Name + '/'; },
// Package Manager
get pkgMgrURINew() { return 'admin/system/package-manager'; },
// opkg
get pkgMgrURIOld() { return 'admin/system/opkg'; },
get modemdefURI() { return 'admin/modem/modemdata/modemdefine'; },
bestPkgMgrURI: function () {
return L.resolveDefault(
fs.stat('/www/luci-static/resources/view/system/package-manager.js'), null
).then(st => {
if (st && st.type === 'file')
return pkg.pkgMgrURINew;
return L.resolveDefault(fs.stat('/usr/libexec/package-manager-call'), null)
.then(st2 => (st2 ? pkg.pkgMgrURINew : pkg.pkgMgrURIOld));
}).catch(() => pkg.pkgMgrURIOld);
},
openInstallerSearch: function (query) {
return pkg.bestPkgMgrURI().then(uri => {
let q = query ? ('?query=' + encodeURIComponent(query)) : '';
window.open(L.url(uri) + q, '_blank', 'noopener');
});
}
};
return view.extend({
option_install_curl: function () {
return pkg.openInstallerSearch('curl');
},
checkPackages: function () {
return fs.exec_direct('/usr/bin/opkg', ['list-installed'], 'text')
.catch(function () {
return fs.exec_direct('/usr/libexec/opkg-call', ['list-installed'], 'text')
.catch(function () {
return fs.exec_direct('/usr/libexec/package-manager-call', ['list-installed'], 'text')
.catch(function () { return ''; });
});
})
.then(function (data) {
data = (data || '').trim();
return data ? data.split('\n') : [];
});
},
load: function () {
return Promise.all([
this.checkPackages()
]);
},
render: function (data) {
let installedList = Array.isArray(data[0]) ? data[0] : [];
let curlInstalled = installedList.some(function (s) { return s && s.indexOf('curl') !== -1; });
let m, s, o;
m = new form.Map('modemdata', _('Configuration'),
_('Configuration panel for modemdata and gui application.')
);
s = m.section(form.TypedSection, 'modemdata', '', null);
s.anonymous = true;
o = s.option(form.ListValue, 'updtime', _('Refresh rate'),
_('Set how often the data should be refreshed in the Modem(s) tab. \
<br /><br /><b>Important</b> \
<br />Not every modem / protocol can communicate quickly with scripts. Too high a polling rate can freeze the modem. \
<br />ModemManager and uqmi need more time to respond.')
);
o.value('-1', _('Disabled'));
o.value('5', _('5 seconds'));
o.value('10', _('10 seconds'));
o.value('30', _('30 seconds'));
o.value('45', _('45 seconds'));
o.default = '5';
o.rmempty = false;
o = s.option(form.DynamicList, 'hide_data', _('Add data to hide'),
_('Enter the data you would like to hide, e.g. <code>IMEI, IMSI</code>.')
);
o.rmempty = true;
s = m.section(form.TypedSection, 'modemdata', null);
s.anonymous = true;
s.addremove = false;
s.tab('bts1', _('BTS search settings'));
s.anonymous = true;
o = s.taboption('bts1', form.DummyValue, '_dummy');
o.rawhtml = true;
o.default =
'<div class="cbi-section-descr">' +
_('Note: The package is fully compatible with the latest, third-generation version of the BTSearch website.') +
/* --- Downloading data using curl
' ' +
_('To download data (via the API), you must have the curl package installed (to do this click on the button added below).') +
*/
'</div>';
o = s.taboption('bts1', form.ListValue, 'website',
_('Website to search for BTS'),
_('Select a website for searching.')
);
o.value('www.btsearch.pl', _('btsearch.pl'));
o.value('lteitaly.it', _('lteitaly.it'));
o.default = 'www.btsearch.pl';
o.modalonly = true;
o = s.taboption('bts1', form.ListValue, 'btsaction', _('Action'),
_('Select an action after clicking the search button.')
);
o.value('open', _('Open page'));
o.default = 'open';
o.modalonly = true;
o.depends('website', 'www.btsearch.pl');
o.value('download', _('Download data'));
/* --- Downloading data using curl
if (curlInstalled) {
o.value('download', _('Download data'));
}
let sCurl = m.section(form.NamedSection, 'kmods', 'kmods', _());
sCurl.render = L.bind(function (view) {
return form.NamedSection.prototype.render.apply(this, this.varargs(arguments, 1))
.then(L.bind(function (node) {
let btn = curlInstalled
? E('button', { 'class': 'edit btn', 'disabled': true }, _('Installed'))
: E('button', {
'class': 'btn cbi-button-action',
'click': ui.createHandlerFn(view, 'option_install_curl', this.map),
'title': _('Install a package using package manager')
}, [_('Install…')]);
node.appendChild(E('div', { 'class': 'cbi-value' }, [
E('label', { 'class': 'cbi-value-title' }, _('Install curl')),
E('div', { 'class': 'cbi-value-field', 'style': 'width:25vw' },
E('div', { 'class': 'cbi-section-node' }, [ btn ])
)
]));
node.appendChild(E('br'));
node.appendChild(E('br'));
return node;
}, this));
}, sCurl, this);
*/
return m.render();
}
});
@@ -0,0 +1,656 @@
'use strict';
'require baseclass';
'require form';
'require fs';
'require view';
'require uci';
'require ui';
'require tools.widgets as widgets';
/*
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
MIT License
*/
function usrdesc(section_id) {
return E('span', (uci.get('defmodems', section_id, 'user_desc') || ''));
}
function getmodem(section_id) {
return E('span', (uci.get('defmodems', section_id, 'modem') || ''));
}
function getmodemdata(section_id) {
return E('span', '<code>' + (uci.get('defmodems', section_id, 'comm_port') + '</code>' || '<code>' + '' + '</code>'));
}
return view.extend({
modemPath: '/etc/modem/modemlist.json',
load: function () {
return Promise.all([
L.resolveDefault(fs.list('/dev'), null),
L.resolveDefault(fs.read_direct('/sys/kernel/debug/usb/devices', ['-r'])),
L.resolveDefault(fs.exec_direct('/usr/bin/mmcli', ['-L', '-J']), null),
this.checkPackages(),
uci.load('defmodems')
]);
},
pkg: {
get Name() { return 'defmodems'; },
get URL() { return 'https://openwrt.org/packages/pkgdata/' + this.Name + '/'; },
get pkgMgrURINew() { return 'admin/system/package-manager'; },
get pkgMgrURIOld() { return 'admin/system/opkg'; },
bestPkgMgrURI: function () {
return L.resolveDefault(
fs.stat('/www/luci-static/resources/view/system/package-manager.js'), null
).then(function (st) {
if (st && st.type === 'file')
return 'admin/system/package-manager';
return L.resolveDefault(fs.stat('/usr/libexec/package-manager-call'), null)
.then(function (st2) {
return st2 ? 'admin/system/package-manager' : 'admin/system/opkg';
});
}).catch(function () { return 'admin/system/opkg'; });
},
openInstallerSearch: function (query) {
let self = this;
return self.bestPkgMgrURI().then(function (uri) {
let q = query ? ('?query=' + encodeURIComponent(query)) : '';
window.open(L.url(uri) + q, '_blank', 'noopener');
});
}
},
fileModemDialog: baseclass.extend({
__init__: function (file, title, description, callback, fileExists) {
if (fileExists === undefined) fileExists = false;
this.file = file;
this.title = title;
this.description = description;
this.callback = callback;
this.fileExists = fileExists;
},
load: function () {
return L.resolveDefault(fs.read(this.file), '');
},
handleSave: function () {
let textarea = document.getElementById('widget.modal_content');
let value = textarea.value.trim().replace(/\r\n/g, '\n') + '\n';
return fs.write(this.file, value)
.then(function (rc) {
textarea.value = value;
return rc;
})
.catch(function (e) {
ui.addNotification(
null,
E('p', _('Unable to save the contents') + ': %s'.format(e.message))
);
})
.finally(function () {
ui.hideModal();
});
},
error: function (e) {
if (!this.fileExists && e instanceof Error && e.name === 'NotFoundError') {
return this.render();
} else {
ui.showModal(
this.title,
[
E('p', {}, _('Unable to read the contents') + ': %s'.format(e.message)),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Dismiss'))
])
],
'cbi-modal'
);
}
},
show: function () {
let self = this;
ui.showModal(null, E('p', { 'class': 'spinning' }, _('Loading')));
this.load()
.then(function (content) {
ui.hideModal();
return self.render(content);
})
.catch(function (e) {
ui.hideModal();
return self.error(e);
});
},
render: function (content) {
ui.showModal(
this.title,
[
E('p', this.description),
E('textarea', {
'id': 'widget.modal_content',
'class': 'cbi-input-textarea',
'style': 'width:100% !important; height: 60vh; min-height: 500px;',
'wrap': 'off',
'readonly': 'true',
'spellcheck': 'false'
}, content.trim()),
E('div', { 'class': 'right' }, [
E('button', { 'class': 'btn', 'click': ui.hideModal }, _('Dismiss'))
])
],
'cbi-modal'
);
}
}),
packageDialog: baseclass.extend({
__init__: function (installedList, pkgHelper) {
this.installed = Array.isArray(installedList) ? installedList : [];
this.pkgHelper = pkgHelper;
this.sections = {
serial: [
{ name: 'sms-tool', label: 'sms-tool' },
{ name: 'kmod-usb-serial', label: 'kmod-usb-serial' },
{ name: 'kmod-usb-serial-option', label: 'kmod-usb-serial-option' }
],
ecm: [
{ name: 'wget-ssl', label: 'wget-ssl' }
],
uqmi: [
{ name: 'uqmi', label: 'uqmi' },
{ name: 'kmod-usb-net-qmi-wwan', label: 'kmod-usb-net-qmi-wwan' },
{ name: 'kmod-usb-net-cdc-mbim', label: 'kmod-usb-net-cdc-mbim' }
],
ModemManager: [
{ name: 'modemmanager', label: 'modemmanager' }
]
};
},
_isInstalled: function (name) {
let list = this.installed || [];
return list.find(function (s) { return s && s.indexOf(name) !== -1; }) ? true : false;
},
_row: function (pkgName, installed) {
let title = E('label', { 'class': 'cbi-value-title' }, pkgName);
let btn = installed
? E('button', { 'class': 'edit btn', 'disabled': true }, _('Installed'))
: E('button', {
'class': 'btn cbi-button-positive',
'click': (function (name, ctx) { return function () { ctx.pkgHelper.openInstallerSearch(name); }; })(pkgName, this)
}, _('Install…'));
let field = E('div', { 'class': 'cbi-value-field' }, [ btn ]);
return E('div', { 'class': 'cbi-value' }, [ title, field ]);
},
_sectionHeader: function (text) {
return E('div', {}, [ E('h4', {}, text) ]);
},
show: function () {
ui.showModal(_('Required packages'), [
E('p', { 'class': 'spinning' }, _('Loading package data…'))
]);
let self = this;
let loadInstalled = Promise.resolve().then(function () {
if (self.installed && self.installed.length)
return self.installed;
if (typeof checkPackages === 'function')
return checkPackages().then(function (list) { self.installed = list || []; return self.installed; });
return [];
});
loadInstalled.then(function () {
let sectionOrder = ['serial', 'ecm', 'uqmi', 'ModemManager'];
let node = [];
node.push(E('p', {}, _('Please select method for reading data from the modem and install the necessary packages.')));
for (let si = 0; si < sectionOrder.length; si++) {
let key = sectionOrder[si];
let pkgs = self.sections[key];
if (!pkgs || pkgs.length === 0) continue;
node.push(self._sectionHeader(_(key)));
for (let i = 0; i < pkgs.length; i++) {
let p = pkgs[i];
let installed = self._isInstalled(p.name);
node.push(self._row(p.label, installed));
}
}
let footer = E('div', { 'class': 'right' }, [
E('div', { 'class': 'btn cbi-button-neutral', 'click': ui.hideModal }, _('Dismiss'))
]);
let body = [
E('div', {}, node),
footer
];
ui.showModal(_('Required packages'), body);
}).catch(function (err) {
ui.showModal(_('Required packages'), [
E('p', {}, _('Failed to load package data.')),
E('pre', {}, (err && err.toString) ? err.toString() : String(err)),
E('div', { 'class': 'right' }, [
E('div', { 'class': 'btn cbi-button-neutral', 'click': ui.hideModal }, _('Close'))
])
]);
});
}
}),
checkPackages: function() {
return fs.exec_direct('/usr/bin/opkg', ['list-installed'], 'text')
.catch(function () {
return fs.exec_direct('/usr/libexec/opkg-call', ['list-installed'], 'text')
.catch(function () {
return fs.exec_direct('/usr/libexec/package-manager-call', ['list-installed'], 'text')
.catch(function () {
return '';
});
});
})
.then(function (data) {
data = (data || '').trim();
return data ? data.split('\n') : [];
});
},
_isPackageInstalled: function(pkgName) {
return this.checkPackages().then(function(installedPackages) {
return installedPackages.some(function(pkg) {
return pkg.includes(pkgName);
});
});
},
render: function (data) {
let showModemDialog = new this.fileModemDialog(
this.modemPath,
_('Modems found'),
_("List of found modems. Not all modems may be shown.")
);
let installedList = Array.isArray(data[3]) ? data[3] : [];
let showPackageDialog = new this.packageDialog(installedList, this.pkg);
// Modemz config info
let dfmodems = uci.sections('defmodems', 'defmodems');
if (!dfmodems || dfmodems.length === 0) {
ui.addNotification(null, E('p', {}, [
_('Proper Modem Configuration.'),
E('br'), E('br'),
E('b', _('User should remember:')),
E('ul', {}, [
E('li', _('1. For a specific modem, only one protocol should be set for communication with modem.')),
E('li', _('2. Only one package can read data from modem (at the same time).')),
E('li', _('3. Reading data from modem too often may cause it to freeze (MM and uqmi need more time).'))
])
]), 'info');
}
fs.write('/etc/modem/modemlist.json', '');
let dlines = data[1].trim().split(/\n/).map(function (line) {
return line.replace(/^<\d+>/, '');
});
let devslist = dlines.join('\n');
let alldevs = devslist.split('\n\n');
let results = [];
for (let ai = 0; ai < alldevs.length; ai++) {
let modem = alldevs[ai];
let lines = modem.split('\n');
let vendor = '';
let pid = '';
let manufacturer = '';
let product = '';
let serialnumber = '';
let driver = '';
let bus = '';
for (let li = 0; li < lines.length; li++) {
let line = lines[li];
if (line.indexOf('Driver=hub') !== -1 || line.indexOf('Driver=usb-storage') !== -1 || line.indexOf('Driver=usblp') !== -1) {
driver = line.split('Driver=')[1].trim();
break;
}
if (line.indexOf('Spd=') !== -1) {
let mSpd = line.match(/Spd=([^ ]+)/);
if (mSpd && mSpd[1]) bus = mSpd[1].trim();
}
if (line.indexOf('Vendor=') !== -1) {
let mVen = line.match(/Vendor=([^ ]+)/);
if (mVen && mVen[1]) vendor = mVen[1].trim();
}
if (line.indexOf('ProdID=') !== -1) {
let mPid = line.match(/ProdID=([^ ]+)/);
if (mPid && mPid[1]) pid = mPid[1].trim();
}
if (line.indexOf('Manufacturer=') !== -1) {
let mMan = line.match(/Manufacturer=(.*)/);
if (mMan && mMan[1]) manufacturer = mMan[1].trim();
}
if (line.indexOf('Product=') !== -1) {
let mPro = line.match(/Product=(.*)/);
if (mPro && mPro[1]) product = mPro[1].trim();
}
if (line.indexOf('SerialNumber=') !== -1) {
let mSn = line.match(/SerialNumber=(.*)/);
if (mSn && mSn[1]) serialnumber = mSn[1].trim();
}
}
if (driver === '' && (manufacturer !== '' || product !== '')) {
results.push({
Manufacturer: manufacturer,
Product: product,
Vendor: vendor,
ProdID: pid,
Bus_speed: bus,
Serial_Number: serialnumber
});
}
}
let outputJSON = JSON.stringify(results, null, 2);
let countm = Object.keys(results).length;
fs.write('/etc/modem/modemlist.json', outputJSON + '\n');
let m, s, o;
m = new form.Map('defmodems', _('Defined modems'),
_('Interface to define the available modems. The list of modems will make it easier for the user to switch between modems in LuCI.')
);
s = m.section(form.TypedSection, 'general', _(''));
s.anonymous = true;
s.addremove = false;
o = s.option(form.Button, '_show_modem_btn', _('Show modems found'),
_('Currently, only modems connected via USB are searched for.')
);
o.onclick = function () { showModemDialog.show(); };
o.inputtitle = _('Show');
o.inputstyle = 'edit btn';
o = s.option(form.Button, '_show_package_btn', _('Check required packages'),
_('Before configuring the modem, check if all required packages are installed in the system.')
);
o.onclick = function () { showPackageDialog.show(); };
o.inputtitle = _('Check');
o.inputstyle = 'cbi-button cbi-button-action important';
s = m.section(form.GridSection, 'defmodems', _('Modem(s)'));
s.anonymous = true;
s.addremove = true;
s.sortable = true;
s.nodescriptions = true;
s.addbtntitle = _('Add new modem settings...');
s.tab('general', _('Modem Settings'));
o = s.taboption('general', form.Value, 'modem', _('Manufacturer / Product'),
_("Enter modem name manually if the suggested name is not obvious. \
<br /><br /><b>Important</b> \
<br />The modem name is only searched for modems connected via USB.")
);
for (let i2 = 0; i2 < countm; i2++) {
o.value(results[i2].Manufacturer + ' ' + results[i2].Product);
}
o.placeholder = _('Please select a modem');
o.textvalue = getmodem.bind(o);
o.rmempty = false;
o = s.taboption('general', form.Flag, 'skip_pkgcheck', _('Skip package verification'),
_('Enable this option to skip checking for installed packages and allow selecting any method from the list below.')
);
o.rmempty = false;
o.default = '0';
o.modalonly = true;
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'skip_pkgcheck', value);
return form.Flag.prototype.write.apply(this, [section_id, value]);
};
o.onchange = function (ev, section_id, value) {
let selectEl = document.getElementById('widget.cbid.defmodems.' + section_id + '.modemdata');
if (!selectEl) return;
let skip = (value == '1');
let keys = skip ? modemdata_all_keys : modemdata_base_keylist;
let labels = skip ? modemdata_all_labels : modemdata_base_vallist;
let current = selectEl.value;
selectEl.innerHTML = '';
for (let i = 0; i < keys.length; i++) {
selectEl.appendChild(E('option', { 'value': keys[i] }, [ labels[i] ]));
}
if (keys.indexOf(current) !== -1) {
selectEl.value = current;
} else if (keys.length) {
selectEl.value = keys[0];
}
selectEl.dispatchEvent(new Event('change', { bubbles: true }));
};
o = s.taboption('general', form.ListValue, 'modemdata', _('Reading data via'),
_('Select method for reading data from the modem. <br /> \
<br />serial port: <br /> \
Select one of the available ttyUSBX / ttyACMx / wwan0atX / mhi_DUN ports.<br /> \
<br />ecm: <br /> \
Enter the IP address 192.168.X.X under which the modem (Huawei) is available.<br /> \
<br />uqmi: <br /> \
Select one of the available cdc-wdmX ports.<br /> \
<br />ModemManager: <br /> \
Select one of the searched modem identifiers.')
);
let have_smstool = installedList.find(function (s) { return s && s.indexOf('sms-tool') !== -1; }) ? true : false;
let have_serial = installedList.find(function (s) { return s && s.indexOf('kmod-usb-serial') !== -1; }) ? true : false;
let have_serialoption = installedList.find(function (s) { return s && s.indexOf('kmod-usb-serial-option') !== -1; }) ? true : false;
let have_ecm = installedList.find(function (s) { return s && s.indexOf('wget-ssl') !== -1; }) ? true : false;
let have_uqmi = installedList.find(function (s) { return s && s.indexOf('uqmi') !== -1; }) ? true : false;
let have_uqmiqmi = installedList.find(function (s) { return s && s.indexOf('kmod-usb-net-qmi-wwan') !== -1; }) ? true : false;
let have_uqmimbim = installedList.find(function (s) { return s && s.indexOf('kmod-usb-net-cdc-mbim') !== -1; }) ? true : false;
let have_mm = installedList.find(function (s) { return s && s.indexOf('modemmanager') !== -1; }) ? true : false;
if (have_serial && have_serialoption && have_smstool) {
o.value('serial', _('serial'));
}
if (have_ecm) {
o.value('ecm', _('ecm'));
}
if ((have_uqmi && have_uqmiqmi) || (have_uqmi && have_uqmimbim)) {
o.value('uqmi', _('uqmi'));
}
if (have_mm) {
o.value('mm', _('modemmanager'));
}
o.exclude = s.section;
o.nocreate = true;
o.rmempty = false;
let modemdata_base_keylist = (o.keylist || []).slice();
let modemdata_base_vallist = (o.vallist || []).slice();
let modemdata_all_keys = ['serial', 'ecm', 'uqmi', 'mm'];
let modemdata_all_labels = [_('serial'), _('ecm'), _('uqmi'), _('modemmanager')];
o.renderWidget = function (section_id, option_index, cfgvalue) {
let skip = (uci.get('defmodems', section_id, 'skip_pkgcheck') == '1');
if (skip) {
this.keylist = modemdata_all_keys.slice();
this.vallist = modemdata_all_labels.slice();
} else {
this.keylist = modemdata_base_keylist.slice();
this.vallist = modemdata_base_vallist.slice();
}
return form.ListValue.prototype.renderWidget.apply(this, arguments);
};
o = s.taboption('general', form.Value, 'comm_port', _('Port / IP / Modem identifier'));
o.rmempty = false;
o.textvalue = getmodemdata.bind(o);
o.modalonly = false;
o = s.taboption('general', form.Value, 'comm_serial', _('Port for communication'));
o.depends('modemdata', 'serial');
data[0].sort(function (a, b) { return a.name > b.name; });
data[0].forEach(function (dev) {
if (dev.name.match(/^ttyUSB/) || dev.name.match(/^ttyACM/) || dev.name.match(/^mhi_/) || dev.name.match(/^wwan/)) {
o.value('/dev/' + dev.name);
}
});
o.placeholder = _('Please select a port');
o.rmempty = false;
o.modalonly = true;
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'comm_port', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o = s.taboption('general', form.Flag, 'forced_plmn_serial', _('Force PLMN'), _('Force reading PLMN from file.'));
o.rmempty = false;
o.modalonly = true;
o.depends('modemdata', 'serial');
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'forced_plmn', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o = s.taboption('general', form.Value, 'comm_ecm', _('IP adress'));
o.depends('modemdata', 'ecm');
o.placeholder = _('Enter IP adress');
o.rmempty = false;
o.modalonly = true;
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'comm_port', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o.validate = function (section_id, value) {
if (!/^[0-9.]+$/.test(value)) {
return _('Only numbers and dots are allowed');
}
let mIp = value.match(/^192\.168\.(\d{1,3})\.(\d{1,3})$/);
if (mIp) {
let p3 = parseInt(mIp[1], 10);
let p4 = parseInt(mIp[2], 10);
if (p3 >= 0 && p3 <= 255 && p4 >= 0 && p4 <= 255) {
return true;
}
}
return _('Enter a valid IP address in the format 192.168.X.X');
};
o = s.taboption('general', form.Value, 'comm_uqmi', _('Port for communication'));
o.depends('modemdata', 'uqmi');
data[0].sort(function (a, b) { return a.name > b.name; });
data[0].forEach(function (dev) {
if (dev.name.match(/^cdc-wdm/)) {
o.value('/dev/' + dev.name);
}
});
o.placeholder = _('Please select a QMI/MBIM port');
o.rmempty = false;
o.modalonly = true;
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'comm_port', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o = s.taboption('general', form.Flag, 'forced_plmn_uqmi', _('Force PLMN'), _('Force reading PLMN from file.'));
o.rmempty = false;
o.modalonly = true;
o.depends('modemdata', 'uqmi');
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'forced_plmn', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o = s.taboption('general', form.Flag, 'onproxy', _('MBIM device'), _('Select if you want to read from MBIM.'));
o.rmempty = false;
o.modalonly = true;
o.depends('modemdata', 'uqmi');
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'onproxy', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o = s.taboption('general', form.Value, 'comm_mm', _('Modem id'));
o.depends('modemdata', 'mm');
data[0].sort(function (a, b) { return a.name > b.name; });
try {
let mmData = JSON.parse(data[2]);
if (mmData['modem-list'] && Array.isArray(mmData['modem-list'])) {
mmData['modem-list'].forEach(function (modem) {
o.value(modem, _('modem ') + modem.split('/').pop());
});
}
} catch (e) {
console.error('MMCLI scan error:', e);
}
o.placeholder = _('Please select a modem id');
o.rmempty = false;
o.modalonly = true;
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'comm_port', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o = s.taboption('general', form.Flag, 'forced_plmn_mm', _('Force PLMN'), _('Force reading PLMN from file.'));
o.rmempty = false;
o.modalonly = true;
o.depends('modemdata', 'mm');
o.write = function (section_id, value) {
uci.set('defmodems', section_id, 'forced_plmn', value);
return form.Value.prototype.write.apply(this, [section_id, value]);
};
o = s.taboption('general', widgets.NetworkSelect, 'network', _('Assigned interface'), _('Interface assigned to modem.'));
o.rmempty = false;
o.default = 'wan';
o = s.taboption('general', form.Value, 'user_desc', _('User description'));
o.rmempty = true;
o.modalonly = true;
o.placeholder = _('Optional');
o = s.taboption('general', form.Value, 'user_desc', _('User description (optional)'));
o.rawhtml = true;
o.remove = function () {};
o.modalonly = false;
o.textvalue = usrdesc.bind(o);
return m.render();
}
});
@@ -0,0 +1,537 @@
'use strict';
'require baseclass';
'require form';
'require fs';
'require uci';
'require ui';
'require view';
'require tools.widgets as widgets';
/*
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
MIT License
*/
// 添加诊断页面样式
function addDiagnosticsStyles() {
if (document.getElementById('modemdata-diagnostics-styles')) return;
const style = document.createElement('style');
style.id = 'modemdata-diagnostics-styles';
style.type = 'text/css';
style.textContent = `
/* 强制重置 table 默认样式 - 使用更具体的选择器 */
table.table,
.cbi-map table.table {
display: grid !important;
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) !important;
gap: 16px !important;
width: 100% !important;
max-width: 100% !important;
border: none !important;
border-spacing: 0 !important;
border-collapse: initial !important;
table-layout: initial !important;
margin-top: 1em !important;
}
/* 让 tr 透传,使 td 直接成为 grid 子项 */
table.table tr,
table.table .tr,
.cbi-map table.table tr {
display: contents !important;
}
/* 重置 td 的默认 table-cell 显示 */
table.table td,
table.table .td,
.cbi-map table.table td {
display: flex !important;
}
/* 每个诊断项卡片样式 */
.table .td.left {
display: flex !important;
flex-direction: column !important;
align-items: flex-start !important;
background: var(--modemdata-card-bg) !important;
border: 1px solid var(--modemdata-card-border) !important;
border-radius: 8px !important;
padding: 16px !important;
min-height: 150px !important;
transition: all 0.2s ease !important;
box-sizing: border-box !important;
}
.table .td.left:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
:root[data-darkmode="true"] .table .td.left:hover,
body.dark .table .td.left:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.4);
}
/* 隐藏空单元格 */
.table .td.left:empty {
display: none !important;
}
/* 标题 - 左对齐 */
.table .cbi-value-title {
font-weight: 600 !important;
font-size: 14px !important;
margin: 0 0 10px 0 !important;
padding: 0 !important;
display: block !important;
color: var(--modemdata-text-primary) !important;
line-height: 1.4 !important;
text-align: left !important;
width: 100% !important;
}
/* 命令区域 - 左对齐,单行显示 */
.table .td.left p {
margin: 0 0 12px 0 !important;
padding: 0 !important;
font-size: 11px !important;
line-height: 1.4 !important;
flex: 1 !important;
text-align: left !important;
width: 100% !important;
overflow: hidden !important;
}
/* code 标签 - 单行显示,可滚动 */
.table .td.left p code {
background-color: rgba(130, 130, 130, 0.3) !important;
padding: 5px 10px !important;
border-radius: 4px !important;
font-size: 11px !important;
font-family: 'Consolas', 'Monaco', 'Courier New', monospace !important;
color: #0a0a0a !important;
font-weight: 600 !important;
border: 1px solid rgba(0, 0, 0, 0.2) !important;
display: block !important;
white-space: nowrap !important;
overflow-x: auto !important;
max-width: 100% !important;
}
:root[data-darkmode="true"] .table .td.left p code,
body.dark .table .td.left p code {
background-color: rgba(210, 210, 210, 0.25) !important;
color: #f5f5f5 !important;
border-color: rgba(255, 255, 255, 0.25) !important;
}
/* 按钮 - 左对齐,固定宽度 */
.diag-action {
display: flex !important;
margin-top: auto !important;
width: 100% !important;
justify-content: flex-start !important;
}
.diag-action .cbi-button {
width: auto !important;
min-width: 90px !important;
padding: 6px 20px !important;
display: inline-block !important;
text-align: center !important;
}
/* 修复下拉框文字被遮挡问题 */
.cbi-input-select {
line-height: 1.6 !important;
padding: 8px 12px !important;
min-height: 40px !important;
font-size: 14px !important;
}
.cbi-input-select option {
line-height: 1.8 !important;
padding: 8px 12px !important;
min-height: 36px !important;
font-size: 14px !important;
}
/* 响应式断点优化 */
@media (max-width: 669px) {
table.table,
.cbi-map table.table {
grid-template-columns: 1fr !important;
}
}
@media (min-width: 670px) {
table.table,
.cbi-map table.table {
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) !important;
}
}
/* 输出对话框样式 */
#cmd_modal_output {
font-family: 'Consolas', 'Monaco', 'Courier New', monospace;
font-size: 12px;
line-height: 1.5;
background-color: #f5f5f5;
border: 1px solid #ddd;
color: #333;
}
:root[data-darkmode="true"] #cmd_modal_output,
body.dark #cmd_modal_output {
background-color: #1a1a1a;
border-color: #444;
color: #e0e0e0;
}
/* 模态框按钮样式 */
.cbi-modal .right .btn {
margin-left: 8px;
min-width: 80px;
}
/* Modem 选择器样式 */
#mselect {
font-size: 13px;
padding: 8px 12px;
border-radius: 4px;
border: 1px solid #ccc;
transition: border-color 0.2s ease;
}
#mselect:focus {
border-color: #007bff;
outline: none;
box-shadow: 0 0 0 3px rgba(0, 123, 255, 0.1);
}
:root[data-darkmode="true"] #mselect,
body.dark #mselect {
background-color: rgba(255, 255, 255, 0.05);
border-color: rgba(255, 255, 255, 0.2);
color: #e0e0e0;
}
`;
document.head.appendChild(style);
// 强制清理 table 的 inline styles,确保 Grid 布局生效
function cleanTableStyles() {
document.querySelectorAll('table.table').forEach(function(table) {
table.removeAttribute('style');
table.style.cssText = 'display: grid !important; grid-template-columns: repeat(auto-fill, minmax(320px, 1fr)) !important; gap: 16px !important; width: 100% !important;';
});
document.querySelectorAll('table.table td, table.table .td').forEach(function(td) {
td.removeAttribute('style');
if (td.classList.contains('left')) {
td.style.cssText = 'display: flex !important;';
}
});
}
// 多次尝试清理,确保生效
setTimeout(cleanTableStyles, 50);
setTimeout(cleanTableStyles, 200);
setTimeout(cleanTableStyles, 500);
}
let commandOutputDialog = baseclass.extend({
__init__: function(title, content){ this.title = title; this.content = content; },
render: function(){
let self = this;
ui.showModal(this.title, [
E('p', _('Command result')),
E('textarea', {
'id': 'cmd_modal_output',
'class': 'cbi-input-textarea',
'style': 'width:100% !important; height:60vh; min-height:500px;',
'readonly': true,
'wrap': 'off',
'spellcheck': 'false'
}, this.content.trim()),
E('div', { 'class': 'right' }, [
E('button', {
'class': 'btn cbi-button-remove',
'click': ui.createHandlerFn(this, function(){
document.getElementById('cmd_modal_output').value = '';
fs.write('/tmp/debug_result.txt', '')
.catch(function(e){
ui.addNotification(null, E('p', _('Unable to clear the file') + ': %s'.format(e.message)), 'error');
});
})
}, _('Clear')), ' ',
E('button', {
'class': 'cbi-button cbi-button-apply important',
'click': ui.createHandlerFn(this, function(){
let blob = new Blob([self.content], { type: 'text/plain' });
let link = document.createElement('a');
link.download = 'debug_result.txt';
link.href = URL.createObjectURL(blob);
link.click();
URL.revokeObjectURL(link.href);
})
}, _('Download')), ' ',
E('button', {
'class': 'btn',
'click': ui.hideModal
}, _('Close'))
])
], 'cbi-modal');
},
show: function(){ this.render(); }
});
return view.extend({
handleCommand: function(exec, args){
return fs.exec(exec, args).then((res)=>{
let output = [res.stdout||'', res.stderr||''].join('\n');
fs.write('/tmp/debug_result.txt', res.stdout||'');
let dialog = new commandOutputDialog(_('Diagnostics'), output);
dialog.show();
}).catch((err)=>{ ui.addNotification(null, E('p', [err])); }).finally(()=>{});
},
handleUSB: function(){ return this.handleCommand('/bin/cat', ['/sys/kernel/debug/usb/devices']); },
handleTTY: function(){ return this.handleCommand('/bin/ls', ['/dev']); },
handleDBG: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let valueParts = value.split('*'); let device = valueParts[1] || ''; let network = valueParts[2] || '';
return this.handleCommand('/bin/sh', ['/usr/bin/md_serial_ecm', device, network]);
},
handleproduct: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let device = value.split('*')[1] || '';
return this.handleCommand('/bin/sh', ['-x', '/usr/share/modemdata/product.sh', device]);
},
handlenetwork: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let network = value.split('*')[2] || '';
return this.handleCommand('/bin/sh', ['-x', '/usr/share/modemdata/network.sh', network]);
},
handleparams: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let device = value.split('*')[1] || '';
return this.handleCommand('/bin/sh', ['-x', '/usr/share/modemdata/params.sh', device]);
},
handleuqmi: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let valueParts = value.split('*'); let device = valueParts[1]||''; let network = valueParts[2]||''; let forced_plmn = valueParts[3]||''; let onproxy = (valueParts[4]||'').replace('undefined','0');
return this.handleCommand('/bin/sh', ['/usr/bin/md_uqmi', device, network, forced_plmn, onproxy]);
},
handleparuqmi: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let valueParts = value.split('*'); let device = valueParts[1]||''; let forced_plmn = valueParts[3]||''; let onproxy = (valueParts[4]||'').replace('undefined','0');
return this.handleCommand('/bin/sh', ['-x', '/usr/share/modemdata/params_qmi.sh', device, forced_plmn, onproxy]);
},
handlemm: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let valueParts = value.split('*'); let device = valueParts[1]||''; let network = valueParts[2]||''; let forced_plmn = valueParts[3]||'';
return this.handleCommand('/bin/sh', ['/usr/bin/md_modemmanager', device, network, forced_plmn]);
},
handleparamsmm: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let valueParts = value.split('*'); let device = valueParts[1]||''; let forced_plmn = valueParts[3]||'';
return this.handleCommand('/bin/sh', ['-x', '/usr/share/modemdata/params_modemmanager.sh', device, forced_plmn]);
},
handleprodmm: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : '';
let valueParts = value.split('*'); let device = valueParts[1]||'';
return this.handleCommand('/bin/sh', ['-x', '/usr/share/modemdata/product_modemmanager.sh', device]);
},
handleClear: function(){ fs.write('/tmp/debug_result.txt',''); },
handleDownload: function(){
return L.resolveDefault(fs.read_direct('/tmp/debug_result.txt'), null).then((res)=>{
if(res){ let blob = new Blob([res], { type:'text/plain' }); let link = document.createElement('a'); link.download = 'debug_result.txt'; link.href = URL.createObjectURL(blob); link.click(); URL.revokeObjectURL(link.href); }
}).catch((err)=>{ ui.addNotification(null, E('p', {}, _('Download error') + ': ' + err.message)); });
},
handleBlock: function(){
let select = document.getElementById('mselect'); let value = select ? select.value : ''; let valueParts = value.split('_'); let modemdata = valueParts[0] || '';
let buttons = document.querySelectorAll('.diag-action > .cbi-button'); for(let i=0;i<buttons.length;i++) buttons[i].removeAttribute('disabled');
if (modemdata.includes('uqmi')) {
document.getElementById('s1').setAttribute('disabled','disabled');
document.getElementById('s2').setAttribute('disabled','disabled');
document.getElementById('s3').setAttribute('disabled','disabled');
document.getElementById('m1').setAttribute('disabled','disabled');
document.getElementById('m2').setAttribute('disabled','disabled');
document.getElementById('m3').setAttribute('disabled','disabled');
}
if (modemdata.includes('serial')) {
document.getElementById('u1').setAttribute('disabled','disabled');
document.getElementById('u2').setAttribute('disabled','disabled');
document.getElementById('m1').setAttribute('disabled','disabled');
document.getElementById('m2').setAttribute('disabled','disabled');
document.getElementById('m3').setAttribute('disabled','disabled');
}
if (modemdata.includes('mm')) {
document.getElementById('s1').setAttribute('disabled','disabled');
document.getElementById('s2').setAttribute('disabled','disabled');
document.getElementById('s3').setAttribute('disabled','disabled');
document.getElementById('u1').setAttribute('disabled','disabled');
document.getElementById('u2').setAttribute('disabled','disabled');
}
},
load: function(){ return L.resolveDefault(uci.load('defmodems')); },
render: function(){
// 初始化样式
addDiagnosticsStyles();
let sections = uci.sections('defmodems','defmodems');
let result = sections.map(s=>`${s.modemdata}*${s.comm_port}*${s.network}*${s.forced_plmn}*${s.onproxy}*#[ ${s.modemdata} ] ${s.comm_port} - ${s.modem} (${s.user_desc})`).join(';');
result = result.replace("(undefined)","");
let off_s1,off_s2,off_s3,off_u1,off_u2,off_m1,off_m2,off_m3;
let modemz = E('div', { class:'cbi-section' }, [
E('div', { class:'cbi-section-descr' }, this.description),
E('div', { class:'cbi-value' }, [
E('label', { class:'cbi-value-title' }, [_('Modem')]),
E('div', { class:'cbi-value-field' }, [
E('select', { class:'cbi-input-select', id:'mselect', style:'width:100%;', change:ui.createHandlerFn(this,'handleBlock'), mousedown:ui.createHandlerFn(this,'handleBlock') },
(result||'').trim().split(/;/).map(function(cmd){ let fields = cmd.split(/#/); let name = fields[1]; let code = fields[0]; return E('option', { value:code }, name); })
)
])
])
]);
let modemdata = result.split('_')[0] || '';
if (modemdata.includes('uqmi')) {
const ids=['s1','s2','s3','m1','m2','m3'];
off_s1=ids.includes('s1')?true:false;
off_s2=ids.includes('s2')?true:false;
off_s3=ids.includes('s3')?true:false;
off_m1=ids.includes('m1')?true:false;
off_m2=ids.includes('m2')?true:false;
off_m3=ids.includes('m3')?true:false;
}
if (modemdata.includes('serial')) {
const ids=['u1','u2','m1','m2','m3'];
off_u1=ids.includes('u1')?true:false;
off_u2=ids.includes('u2')?true:false;
off_m1=ids.includes('m1')?true:false;
off_m2=ids.includes('m2')?true:false;
off_m3=ids.includes('m3')?true:false;
}
if (modemdata.includes('mm')) {
const ids=['s1','s2','s3','u1','u2'];
off_s1=ids.includes('s1')?true:false;
off_s2=ids.includes('s2')?true:false;
off_s3=ids.includes('s3')?true:false;
off_u1=ids.includes('u1')?true:false;
off_u2=ids.includes('u2')?true:false;
}
let table4 = E('table', { class:'table' }, [
E('tr', {}, [
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("USB debug information")),
E('p', {}, _("<code>cat /sys/kernel/debug/usb/devices</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button', click:ui.createHandlerFn(this,'handleUSB') }, _('Run')) ])
]),
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check ttyX ports")),
E('p', {}, _("<code>ls /dev</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button', click:ui.createHandlerFn(this,'handleTTY') }, _('Run')) ])
]),
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check") + ' ' + _("network.sh")),
E('p', {}, _("<code>sh -x /usr/share/modemdata/network.sh</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button', click:ui.createHandlerFn(this,'handlenetwork') }, _('Run')) ])
])
])
]);
let table = E('table', { class:'table' }, [
E('tr', {}, [
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check serial and ecm mode")),
E('p', {}, _("<code>sh /usr/bin/md_serial_ecm</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-add', id:'s1', disabled:off_s1, click:ui.createHandlerFn(this,'handleDBG') }, _('Run')) ])
]),
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check") + ' ' + _("product.sh")),
E('p', {}, _("<code>sh -x /usr/share/modemdata/product.sh</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-add', id:'s2', disabled:off_s2, click:ui.createHandlerFn(this,'handleproduct') }, _('Run')) ])
]),
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check") + ' ' + _("params.sh")),
E('p', {}, _("<code>sh -x /usr/share/modemdata/params.sh</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-add', id:'s3', disabled:off_s3, click:ui.createHandlerFn(this,'handleparams') }, _('Run')) ])
])
])
]);
let table2 = E('table', { class:'table' }, [
E('tr', {}, [
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check uqmi mode")),
E('p', {}, _("<code>sh -x /usr/bin/md_uqmi</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-remove', id:'u1', disabled:off_u1, click:ui.createHandlerFn(this,'handleuqmi') }, _('Run')) ])
]),
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check") + ' ' + _("params_qmi.sh")),
E('p', {}, _("<code>sh -x /usr/share/modemdata/params_qmi.sh</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-remove', id:'u2', disabled:off_u2, click:ui.createHandlerFn(this,'handleparuqmi') }, _('Run')) ])
]),
E('td', { class:'td left' }, [])
])
]);
let table3 = E('table', { class:'table' }, [
E('tr', {}, [
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check ModemManager mode")),
E('p', {}, _("<code>sh -x /usr/bin/md_modemmanager</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-apply', id:'m1', disabled:off_m1, click:ui.createHandlerFn(this,'handlemm') }, _('Run')) ])
]),
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check") + ' ' + _("params_modemmanager.sh")),
E('p', {}, _("<code>sh -x /usr/share/modemdata/params_modemmanager.sh</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-apply', id:'m2', disabled:off_m2, click:ui.createHandlerFn(this,'handleparamsmm') }, _('Run')) ])
]),
E('td', { class:'td left' }, [
E('label', { class:'cbi-value-title' }, _("Check") + ' ' + _("product_modemmanager.sh")),
E('p', {}, _("<code>sh -x /usr/share/modemdata/product_modemmanager.sh</code>")),
E('span', { class:'diag-action' }, [ E('button', { class:'cbi-button cbi-button-apply', id:'m3', disabled:off_m3, click:ui.createHandlerFn(this,'handleprodmm') }, _('Run')) ])
])
])
]);
let info = _('For more information about the modemdata package, please visit: %shttps://github.com/obsy/modemdata%s.').format('<a href="https://github.com/obsy/modemdata" target="_blank">','</a>');
return E('div', { class:'cbi-map' }, [
E('h2', {}, _('Diagnostics')),
E('div', { class:'cbi-map-descr' }, _('Run various commands to verify data read from the modem and debug scripts.') + '<br>' + info),
E('hr'), modemz, table4, table, table2, table3
]);
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});
@@ -0,0 +1,220 @@
'use strict';
'require form';
'require fs';
'require view';
'require ui';
'require uci';
'require dom';
'require tools.widgets as widgets';
/*
Copyright 2025-2026 Rafał Wabik - IceG - From eko.one.pl forum
MIT License
*/
function handleOpen(ev) {
if (ev === 'ropenissues') { window.open('https://github.com/4IceG/luci-app-modemdata/issues'); return; }
if (ev === 'copenissues') { window.open('https://github.com/obsy/modemdata/issues'); return; }
if (ev === 'opendiscussion') { window.open('https://github.com/4IceG/luci-app-modemdata/discussions'); return; }
if (ev === 'ropencoffee') { window.open('https://suppi.pl/rafalwabik'); return; }
if (ev === 'copencoffee') { window.open('https://buycoffee.to/eko.one.pl'); return; }
if (ev === 'opensupport') { window.open('https://github.com/sponsors/4IceG'); return; }
if (ev === 'opentopic') { window.open('https://eko.one.pl/forum/viewtopic.php?id=24829'); return; }
}
return view.extend({
load: function () {
return uci.load('modemdata');
},
render: function (data) {
let m, s, o;
let info = _('In the future tab will allow you to update the package from %sRafał (IceG) external repository%s.')
.format('<a href="https://github.com/4IceG/Modem-extras-apk" target="_blank">', '</a>');
m = new form.Map('modemdata', _('Package update and support'), info);
s = m.section(form.TypedSection);
s.anonymous = true;
s.tab('info', _('Modemdata Info'));
let packages = [
{
package: _('Modemdata'),
author: 'Cezary Jackiewicz (obsy)',
authorLink: 'https://github.com/obsy',
buttons: [
{
class: 'cbi-button-action important',
label: _('Buy a coffee'),
tooltip: _('Buy a coffee if you want to support the development of the project and the author'),
action: 'copencoffee',
disabled: false
},
{
class: 'cbi-button-remove',
label: _('Report a bug'),
tooltip: _('Report a bug on the package Github page'),
action: 'copenissues',
disabled: false
}
]
},
{
package: _('Luci-app-modemdata'),
author: 'Rafał Wabik (IceG)',
authorLink: 'https://github.com/4IceG',
buttons: [
{
class: 'cbi-button-action important',
label: _('Buy a coffee'),
tooltip: _('Buy a coffee if you want to support the development of the project and the author'),
action: 'ropencoffee',
disabled: false
},
{
class: 'cbi-button-action',
label: _('Become a sponsor'),
tooltip: _('Become a sponsor if you want to support the development of the project and the author'),
action: 'opensupport',
disabled: false
},
{
class: 'cbi-button-add',
label: _('Write on forum'),
tooltip: _('Write in the topic of the package on the forum eko.one.pl'),
action: 'opentopic',
disabled: false
},
{
class: 'cbi-button-neutral',
label: _('Open discussion'),
tooltip: _('Open a package discussion on Github'),
action: 'opendiscussion',
disabled: false
},
{
class: 'cbi-button-remove',
label: _('Report a bug'),
tooltip: _('Report a bug on the package Github page'),
action: 'ropenissues',
disabled: false
}
]
}
];
let rows = [];
let table = E('table', {
'class': 'table',
'style': 'border:1px solid var(--border-color-medium)!important; table-layout:fixed; border-collapse:collapse; width:100%;'
}, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th' }, _('Package name')),
E('th', { 'class': 'th' }, _('Author (package maintainer)')),
E('th', { 'class': 'th nowrap cbi-section-actions' })
])
]);
for (let i = 0; i < packages.length; i++) {
let pkg = packages[i];
let buttonElements = [];
for (let j = 0; j < pkg.buttons.length; j++) {
let btn = pkg.buttons[j];
let buttonAttrs = {
'class': 'btn ' + btn.class,
'data-tooltip': btn.tooltip
};
if (btn.disabled) {
buttonAttrs.disabled = true;
} else if (btn.action) {
buttonAttrs.click = function() { return handleOpen(btn.action); };
}
buttonElements.push(E('button', buttonAttrs, btn.label));
}
let authorCell = pkg.author ?
E('a', { 'href': pkg.authorLink, 'target': '_blank', 'style': 'color:#37c' }, pkg.author) :
'';
rows.push([
pkg.package,
authorCell,
E('div', { 'style': 'display: flex; flex-wrap: wrap; gap: 0.5em;' }, buttonElements)
]);
}
cbi_update_table(table, rows);
o = s.taboption('info', form.DummyValue, '_packages_info');
o.render = function() {
return E('div', { 'class': 'cbi-section' }, [
E('p', {}, _('Information about package authors and available support options.')),
table
]);
};
s.tab('troubleshooting', _('Troubleshooting / Hints'));
let troubleshootingData = [
// 1.
{
issue: _('If data in LuCI is not visible, and we are 100% sure that the modem has been defined/configured correctly.'),
solution: _('Go to the Diagnostics tab and run a script check. Most often, the error is caused by a strange operator name. To fix this, select the Force PLMN from file option if it is available in the modem configuration options.')
},
// 2.
{
issue: _('Package does not show data for the Fibocom FM350-GL modem.'),
solution: _('The modem is very sensitive to data read by the communication port. Selecting the wrong communication port may cause the modem to freeze or restart.') +
' ' +
'<a href="https://github.com/obsy/modemfeed/tree/master/luci/protocols/luci-proto-xmm" target="_blank" rel="noopener noreferrer">luci-proto-xmm</a>' +
' ' + _('and') + ' ' +
'<a href="https://github.com/obsy/modemfeed/tree/master/packages/net/xmm-modem" target="_blank" rel="noopener noreferrer">xmm-modem</a>' +
' ' + _('packages are recommended for proper modem support.')
}
];
let troubleshootingRows = [];
let troubleshootingTable = E('table', {
'class': 'table',
'style': 'border:1px solid var(--border-color-medium)!important; table-layout:fixed; border-collapse:collapse; width:100%;'
}, [
E('tr', { 'class': 'tr table-titles' }, [
E('th', { 'class': 'th', 'style': 'width:40%' }, _('Problem')),
E('th', { 'class': 'th' }, _('Suggested solution'))
])
]);
for (let i = 0; i < troubleshootingData.length; i++) {
let item = troubleshootingData[i];
troubleshootingRows.push([
item.issue,
E('em', {}, item.solution)
]);
}
cbi_update_table(troubleshootingTable, troubleshootingRows);
o = s.taboption('troubleshooting', form.DummyValue, '_troubleshooting_info');
o.render = function() {
return E('div', { 'class': 'cbi-section' }, [
E('p', {}, _('Common problems that may arise when using the package, and ways to solve them.')),
troubleshootingTable
]);
};
return m.render();
},
handleSaveApply: null,
handleSave: null,
handleReset: null
});