mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2026-01-29 00:51:53 +08:00
Merge remote-tracking branch 'upstream/master'
This commit is contained in:
@@ -372,6 +372,31 @@
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
<details>
|
||||
<summary i18n="kvm_text83">Web UI settings</summary>
|
||||
<div class="spoiler">
|
||||
<table class="kv">
|
||||
<tr>
|
||||
<td i18n="page-close-ask-switch">Ask page close confirmation:</td>
|
||||
<td align="right">
|
||||
<div class="switch-box">
|
||||
<input checked type="checkbox" id="page-close-ask-switch">
|
||||
<label for="page-close-ask-switch"><span class="switch-inner"></span><span class="switch"></span></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="page-full-tab-stream-switch">Expand for the entire tab by default:</td>
|
||||
<td align="right">
|
||||
<div class="switch-box">
|
||||
<input type="checkbox" id="page-full-tab-stream-switch">
|
||||
<label for="page-full-tab-stream-switch"><span class="switch-inner"></span><span class="switch"></span></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</details>
|
||||
<table class="kv">
|
||||
<tr class="feature-disabled" id="hid-connect">
|
||||
<td i18n="hid-connect-switch">Connect HID to Server:</td>
|
||||
@@ -404,8 +429,8 @@
|
||||
<td>Connect main USB to Server:</td>
|
||||
<td align="right">
|
||||
<div class="switch-box">
|
||||
<input class="gpio-switch" disabled type="checkbox" id="gpio-switch-__v3_usb_breaker__" data-channel="__v3_usb_breaker__" data-confirm-off="Turning off this switch will disconnect the main USB<br>from the server. Are you sure you want to continue?">
|
||||
<label for="gpio-switch-__v3_usb_breaker__"><span class="switch-inner"></span><span class="switch"></span></label>
|
||||
<input class="__gpio-switch-__v3_usb_breaker__ gpio-switch" disabled type="checkbox" id="__gpio-switch-__v3_usb_breaker__" data-channel="__v3_usb_breaker__" data-confirm-off="Turning off this switch will disconnect the main USB<br>from the server. Are you sure you want to continue?">
|
||||
<label for="__gpio-switch-__v3_usb_breaker__"><span class="switch-inner"></span><span class="switch"></span></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -413,17 +438,8 @@
|
||||
<td>Enable locator LED:</td>
|
||||
<td align="right">
|
||||
<div class="switch-box">
|
||||
<input class="gpio-switch" disabled type="checkbox" id="gpio-switch-__v4_locator__" data-channel="__v4_locator__" data-confirm-off="">
|
||||
<label for="gpio-switch-__v4_locator__"><span class="switch-inner"></span><span class="switch"></span></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td i18n="page-close-ask-switch">Ask page close confirmation:</td>
|
||||
<td align="right">
|
||||
<div class="switch-box">
|
||||
<input checked type="checkbox" id="page-close-ask-switch">
|
||||
<label for="page-close-ask-switch"><span class="switch-inner"></span><span class="switch"></span></label>
|
||||
<input class="__gpio-switch-__v4_locator__ gpio-switch" disabled type="checkbox" id="__gpio-switch-__v4_locator__" data-channel="__v4_locator__" data-confirm-off="">
|
||||
<label for="__gpio-switch-__v4_locator__"><span class="switch-inner"></span><span class="switch"></span></label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -622,17 +638,6 @@
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<hr>
|
||||
<table class="kv">
|
||||
<tr>
|
||||
<td class="value" i18n="kvm_text71">Note:</td>
|
||||
<td i18n="kvm_text72">• Don't close the browser page until the upload is complete.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td i18n="kvm_text73">• To speed up the upload, close the stream window.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<div class="hidden" id="msd-uploading-sub">
|
||||
<hr>
|
||||
@@ -650,6 +655,19 @@
|
||||
<div class="progress" id="msd-uploading-progress"><span class="progress-value" id="msd-uploading-progress-value"></span></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="hidden" id="msd-new-tips">
|
||||
<hr>
|
||||
<table class="kv">
|
||||
<tr>
|
||||
<td class="value" i18n="kvm_text71">Note:</td>
|
||||
<td i18n="kvm_text72">• Don't close the browser page until the upload is complete.</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td></td>
|
||||
<td i18n="kvm_text73">• To speed up the upload, close the stream window.</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<hr>
|
||||
<div class="buttons buttons-row">
|
||||
<button class="row50" disabled id="msd-connect-button" i18n="kvm_text76">Connect drive to Server</button>
|
||||
@@ -866,7 +884,7 @@
|
||||
<div id="stream-info"></div>
|
||||
<button class="window-button-exit-full-tab">▼</button>
|
||||
<div class="stream-box-offline" id="stream-box"><img id="stream-image" src="/share/png/blank-stream.png">
|
||||
<video class="hidden" id="stream-video" autoplay playsinline muted></video>
|
||||
<video class="hidden" id="stream-video" disablePictureInPicture="true" autoplay playsinline muted></video>
|
||||
<div id="stream-fullscreen-active"></div>
|
||||
</div>
|
||||
<div class="keypad" id="stream-mouse-buttons" align="center">
|
||||
@@ -2033,6 +2051,639 @@
|
||||
// If you also want to support this project,<br>
|
||||
// you can donate on <a target="_blank" href="https://www.patreon.com/pikvm">Patreon</a>
|
||||
or <a target="_blank" href="https://paypal.me/pikvm">Paypal</a>.</span>
|
||||
<ul>
|
||||
<li>A. Isenring</li>
|
||||
<li>Aaron Graubert</li>
|
||||
<li>Aaron Heise</li>
|
||||
<li>Aaron Stein</li>
|
||||
<li>Accalia</li>
|
||||
<li>Adam Goodbar</li>
|
||||
<li>Adam S</li>
|
||||
<li>Adam Stuart</li>
|
||||
<li>AdamBomb</li>
|
||||
<li>adipisicing</li>
|
||||
<li>Adrian Basham</li>
|
||||
<li>Adrian Popescu</li>
|
||||
<li>Ahmed Syed</li>
|
||||
<li>Alberto Bassi</li>
|
||||
<li>alejandro</li>
|
||||
<li>Aleksei Brusianskii</li>
|
||||
<li>Alessio Curri</li>
|
||||
<li>Alex T</li>
|
||||
<li>Alex Z</li>
|
||||
<li>Alexander Karmanov</li>
|
||||
<li>Alexander Lahuerta</li>
|
||||
<li>Alexander Martin</li>
|
||||
<li>Alexander Pankov</li>
|
||||
<li>Alexandre Jablonski</li>
|
||||
<li>Alexey Kamenskiy</li>
|
||||
<li>alm0241</li>
|
||||
<li>Alok Anand</li>
|
||||
<li>Alucard</li>
|
||||
<li>Ananthaneshan Elampoornan</li>
|
||||
<li>Andreas Marufke</li>
|
||||
<li>Andreas Schmid</li>
|
||||
<li>Andrew Brant</li>
|
||||
<li>Andrew Melton</li>
|
||||
<li>Andrew Reusch</li>
|
||||
<li>Andrew Ruan</li>
|
||||
<li>Andrzej V</li>
|
||||
<li>Andy</li>
|
||||
<li>Andy Keys</li>
|
||||
<li>Anish Patel</li>
|
||||
<li>Anix</li>
|
||||
<li>Anonymous</li>
|
||||
<li>Anthony Junk</li>
|
||||
<li>Anton Kovalenko</li>
|
||||
<li>Armen</li>
|
||||
<li>Aron Green</li>
|
||||
<li>Aron Perelman</li>
|
||||
<li>Artem Simonov</li>
|
||||
<li>Arthur Mayer</li>
|
||||
<li>Arthur Woimbée</li>
|
||||
<li>Ashlesh Chaudhari</li>
|
||||
<li>Asim Shakour</li>
|
||||
<li>Augusto Becciu</li>
|
||||
<li>AVS Computer</li>
|
||||
<li>awkspace</li>
|
||||
<li>Badal Patel</li>
|
||||
<li>baddog</li>
|
||||
<li>Bao Tin Hoang</li>
|
||||
<li>Bean Co.</li>
|
||||
<li>Bela Bargel</li>
|
||||
<li>Belf Igor</li>
|
||||
<li>Ben Gordon</li>
|
||||
<li>Ben Scott</li>
|
||||
<li>Benedikt Heine</li>
|
||||
<li>Benedikt Meier</li>
|
||||
<li>Benjamin Frewert</li>
|
||||
<li>Benjamin Melancon</li>
|
||||
<li>Benjamin Schwartz</li>
|
||||
<li>Benjamin Stegmann</li>
|
||||
<li>Benni Stauder</li>
|
||||
<li>Bernhard Fitzke</li>
|
||||
<li>Beu</li>
|
||||
<li>bikmaek</li>
|
||||
<li>bitjoe</li>
|
||||
<li>Bits and Bytes Computers LLC</li>
|
||||
<li>Bjoern Petsch</li>
|
||||
<li>Blair Hasler</li>
|
||||
<li>Blindside</li>
|
||||
<li>Blue Frog LLC</li>
|
||||
<li>Bootstrapper - Programmierung erklärt</li>
|
||||
<li>Bosco</li>
|
||||
<li>Bradford King</li>
|
||||
<li>Brainspore Networks</li>
|
||||
<li>Branden Shaulis</li>
|
||||
<li>Brandon Daniels</li>
|
||||
<li>Brian</li>
|
||||
<li>Brian Moses</li>
|
||||
<li>Brian T Mulcahy</li>
|
||||
<li>Brian Vecchiarelli</li>
|
||||
<li>Brian White</li>
|
||||
<li>Bruno Gomes</li>
|
||||
<li>Bryan Adams</li>
|
||||
<li>Bryan Montgomery</li>
|
||||
<li>Buzzer</li>
|
||||
<li>C P ELSE</li>
|
||||
<li>Calanish</li>
|
||||
<li>Cameron Hatcher</li>
|
||||
<li>Cameron Tacklind</li>
|
||||
<li>Carl Mercier</li>
|
||||
<li>Carl-Fredrik Johansson</li>
|
||||
<li>Carlos Eduardo Porter Herrera</li>
|
||||
<li>Carlos Garcia</li>
|
||||
<li>Carlos Manuel Torres</li>
|
||||
<li>cbad536</li>
|
||||
<li>César Nascimento</li>
|
||||
<li>CHINATERA LIMITED</li>
|
||||
<li>Chris Blackmon</li>
|
||||
<li>Chris Burton</li>
|
||||
<li>Chris Campbell</li>
|
||||
<li>Chris Jackson</li>
|
||||
<li>Chris Lewis</li>
|
||||
<li>Chris Rizio</li>
|
||||
<li>Christi King</li>
|
||||
<li>Christian Schlögl</li>
|
||||
<li>Christian Svensson</li>
|
||||
<li>Christof Maluck</li>
|
||||
<li>Christoph Dette</li>
|
||||
<li>Christoffer Lund</li>
|
||||
<li>Christopher Bulla</li>
|
||||
<li>Christopher Gelatt</li>
|
||||
<li>Christopher Hearn</li>
|
||||
<li>Christopher Mandlbaur</li>
|
||||
<li>Christopher Mendoza</li>
|
||||
<li>Christopher Simms</li>
|
||||
<li>Chucktastic</li>
|
||||
<li>Cihan VURAL</li>
|
||||
<li>clauskj3r</li>
|
||||
<li>Clifford Coleman</li>
|
||||
<li>Clinton Lee Taylor</li>
|
||||
<li>Cole Imhoff</li>
|
||||
<li>Colin Goodman</li>
|
||||
<li>Corey Layton</li>
|
||||
<li>Corey Lista</li>
|
||||
<li>Craig Keenan</li>
|
||||
<li>Crossfactor</li>
|
||||
<li>Cruzzer</li>
|
||||
<li>ctag</li>
|
||||
<li>Curt Sammer</li>
|
||||
<li>CyB0rgg</li>
|
||||
<li>DeMentor</li>
|
||||
<li>Desmond Whitt</li>
|
||||
<li>Daegara</li>
|
||||
<li>DailyAneurism</li>
|
||||
<li>Damon Meledones</li>
|
||||
<li>Dan Berkowitz</li>
|
||||
<li>Dan Brakeley</li>
|
||||
<li>Daniel Bowder</li>
|
||||
<li>Daniel Cabrera</li>
|
||||
<li>Daniel Davila</li>
|
||||
<li>Danilo Saft</li>
|
||||
<li>Danne</li>
|
||||
<li>Dariusz Techmański</li>
|
||||
<li>David</li>
|
||||
<li>David Brausewetter</li>
|
||||
<li>David Davis</li>
|
||||
<li>David Godibadze</li>
|
||||
<li>David Howell</li>
|
||||
<li>David Irvine</li>
|
||||
<li>David Klinkman</li>
|
||||
<li>David Niemann</li>
|
||||
<li>David Shay</li>
|
||||
<li>David Ye</li>
|
||||
<li>David York</li>
|
||||
<li>Denis</li>
|
||||
<li>Denis Andreev</li>
|
||||
<li>Denis Yatsenko</li>
|
||||
<li>Dennis Becker</li>
|
||||
<li>Dennis Joslin</li>
|
||||
<li>Dennis Lomet</li>
|
||||
<li>Derek Jarvis</li>
|
||||
<li>Derek Yap</li>
|
||||
<li>Didrik</li>
|
||||
<li>digitalbaconbits</li>
|
||||
<li>Dimitrij Jedich</li>
|
||||
<li>dixon wong</li>
|
||||
<li>dizztrukshin</li>
|
||||
<li>Dmitry Shilov</li>
|
||||
<li>DogeLabs</li>
|
||||
<li>Dominic Phoon</li>
|
||||
<li>Dominik Klonowski</li>
|
||||
<li>Donald Hays</li>
|
||||
<li>Edmon Abdul Nur</li>
|
||||
<li>Edward Wang</li>
|
||||
<li>Egan Ford</li>
|
||||
<li>Elani Ferri</li>
|
||||
<li>Elliot Woo</li>
|
||||
<li>Entt</li>
|
||||
<li>Eric Phenix</li>
|
||||
<li>Ethan Shold</li>
|
||||
<li>Eugene Sukhodolin</li>
|
||||
<li>ewook</li>
|
||||
<li>eye-catcher.com</li>
|
||||
<li>Fabian Druschke</li>
|
||||
<li>Fabiano Sidler</li>
|
||||
<li>Far Pin Solutions, LLC</li>
|
||||
<li>Felyx Gabryel</li>
|
||||
<li>Fergus McKay</li>
|
||||
<li>Finn Ebenritter</li>
|
||||
<li>floppy</li>
|
||||
<li>fo0bar</li>
|
||||
<li>Foad Yousef</li>
|
||||
<li>Foamy</li>
|
||||
<li>Foli Ayivoh</li>
|
||||
<li>Folkert Weistra</li>
|
||||
<li>Francisco Pavon</li>
|
||||
<li>Frank</li>
|
||||
<li>Frank Sander</li>
|
||||
<li>Frederick Czajka</li>
|
||||
<li>Fredrik Idréus</li>
|
||||
<li>Garrett Dangerfield</li>
|
||||
<li>Ge Men</li>
|
||||
<li>Geekworm</li>
|
||||
<li>Genkinger Andreas</li>
|
||||
<li>Geijer</li>
|
||||
<li>Geoffrey Wright</li>
|
||||
<li>George Becker</li>
|
||||
<li>Georgy Brodsky</li>
|
||||
<li>Gerald</li>
|
||||
<li>Gerardus Vernimmen</li>
|
||||
<li>Gernot Neuschröer</li>
|
||||
<li>Giovanni Fulco</li>
|
||||
<li>GK</li>
|
||||
<li>Glen Dragon</li>
|
||||
<li>Greg Winterstein</li>
|
||||
<li>Gregory Smith</li>
|
||||
<li>Gregory Treantos</li>
|
||||
<li>grewil</li>
|
||||
<li>Grey Cynic</li>
|
||||
<li>Guenter Honisch</li>
|
||||
<li>Guido Bernacchi</li>
|
||||
<li>Gustin Johnson</li>
|
||||
<li>György Tamás Vizi</li>
|
||||
<li>Haiberg GmbH</li>
|
||||
<li>Haven Zheng</li>
|
||||
<li>Heibunny</li>
|
||||
<li>Heikki Tiittanen</li>
|
||||
<li>Helio Leonardo Pinheiro e Mota</li>
|
||||
<li>Henrik Ählström</li>
|
||||
<li>Henry Hood</li>
|
||||
<li>HimKo</li>
|
||||
<li>HouseFPV</li>
|
||||
<li>Howard Simons</li>
|
||||
<li>HyunohRyu</li>
|
||||
<li>Icculus</li>
|
||||
<li>iks</li>
|
||||
<li>INFO TRX INC</li>
|
||||
<li>Invader Monks</li>
|
||||
<li>Ioannis Karageorgos</li>
|
||||
<li>Isaac</li>
|
||||
<li>IT Lifesaver</li>
|
||||
<li>Ivan Ganev</li>
|
||||
<li>Ivan Josiah Lapis</li>
|
||||
<li>Ivan Shapovalov</li>
|
||||
<li>iwbjhbweriuhf</li>
|
||||
<li>J</li>
|
||||
<li>J L</li>
|
||||
<li>Jaanus</li>
|
||||
<li>Jackson Wyatt</li>
|
||||
<li>Jacob Karaffa</li>
|
||||
<li>Jacob Morgan</li>
|
||||
<li>James Cadd</li>
|
||||
<li>James Cobb</li>
|
||||
<li>James Edwards</li>
|
||||
<li>James Kocher</li>
|
||||
<li>James Mayhugh</li>
|
||||
<li>James Noonan</li>
|
||||
<li>James Ye</li>
|
||||
<li>Jamie Murphy</li>
|
||||
<li>Jamie Scott</li>
|
||||
<li>Jan Niehusmann</li>
|
||||
<li>Jannick Oursin</li>
|
||||
<li>Jari Hiltunen</li>
|
||||
<li>Jason Crossley</li>
|
||||
<li>Jason Downey</li>
|
||||
<li>Jason Toland</li>
|
||||
<li>Jasper Backer</li>
|
||||
<li>Jay Davis</li>
|
||||
<li>Jay Isaacs</li>
|
||||
<li>Jazereel Goh</li>
|
||||
<li>Jean-Daniel Croteau</li>
|
||||
<li>Jean-Philippe Guilbault</li>
|
||||
<li>Jeff</li>
|
||||
<li>Jeff Bowman</li>
|
||||
<li>Jeff Urlwin</li>
|
||||
<li>Jennifer Herting</li>
|
||||
<li>Jennifer Rowlett</li>
|
||||
<li>Jeremy Abel</li>
|
||||
<li>Jeremy Combs</li>
|
||||
<li>Jeremy Hines</li>
|
||||
<li>Jerremy Holland</li>
|
||||
<li>Jerry Nall</li>
|
||||
<li>Jerry Y. Chen</li>
|
||||
<li>Jim Bailey</li>
|
||||
<li>Jim Harbin</li>
|
||||
<li>Jimmy Burgett</li>
|
||||
<li>Jimmy Stanley</li>
|
||||
<li>Joachim Bruening</li>
|
||||
<li>Joe Hanson</li>
|
||||
<li>Joe Hinteregger</li>
|
||||
<li>Joe Ventura</li>
|
||||
<li>Joel Jacobs</li>
|
||||
<li>Johannes Heigermose</li>
|
||||
<li>John Andersen</li>
|
||||
<li>John Copeland</li>
|
||||
<li>John F Glenn</li>
|
||||
<li>John Holmes</li>
|
||||
<li>John Kelley</li>
|
||||
<li>John McGovern</li>
|
||||
<li>Johnny Henson</li>
|
||||
<li>Jon Ferguy</li>
|
||||
<li>Jon-Eric</li>
|
||||
<li>Joni Ruuskanen</li>
|
||||
<li>Jonas Fischer</li>
|
||||
<li>Jonathan Slenders</li>
|
||||
<li>Jonathan Vaughn</li>
|
||||
<li>Joost Backer</li>
|
||||
<li>Jordan Blake</li>
|
||||
<li>Jordi Pakey-Rodriguez</li>
|
||||
<li>Joris van Embden</li>
|
||||
<li>Joseph Swift</li>
|
||||
<li>Josh Nethery</li>
|
||||
<li>Josh Ricker</li>
|
||||
<li>Josh VanDeraa</li>
|
||||
<li>Joshua Futterer</li>
|
||||
<li>Jozef Riha</li>
|
||||
<li>Jörgen Fredriksson</li>
|
||||
<li>Julian Forero</li>
|
||||
<li>Julian Schneider</li>
|
||||
<li>Julien Angelier</li>
|
||||
<li>Justin</li>
|
||||
<li>Justin Waters</li>
|
||||
<li>Kai Hadler</li>
|
||||
<li>Kamil Chyba</li>
|
||||
<li>Kari Matti Korpi</li>
|
||||
<li>Karl Dunne</li>
|
||||
<li>Karl Moos</li>
|
||||
<li>Keith Muggleton</li>
|
||||
<li>Ken Lee</li>
|
||||
<li>Kenneth Younger III</li>
|
||||
<li>Kenny Hui</li>
|
||||
<li>KeonWoo PARK</li>
|
||||
<li>Kevin Bajohr</li>
|
||||
<li>Kevin Schwartz</li>
|
||||
<li>Kevin Sherwood</li>
|
||||
<li>Kiera Kujisawa</li>
|
||||
<li>Kiran Schuler</li>
|
||||
<li>Koloman</li>
|
||||
<li>Konrad Neitzel</li>
|
||||
<li>Krzysztof Żelaśkiewicz</li>
|
||||
<li>Lance Ward</li>
|
||||
<li>Larry Meaney</li>
|
||||
<li>Lars</li>
|
||||
<li>Lars Reinhardt</li>
|
||||
<li>Lee Wilkinson</li>
|
||||
<li>LeeNX</li>
|
||||
<li>Leon Siegl</li>
|
||||
<li>Leonard Feineis</li>
|
||||
<li>Lewis Wild</li>
|
||||
<li>Liran</li>
|
||||
<li>Liviu Dimitriu</li>
|
||||
<li>Lizardo Hernandez</li>
|
||||
<li>LoCascio</li>
|
||||
<li>Lordbob75</li>
|
||||
<li>Lothar Schweikle-Droll</li>
|
||||
<li>Louis Müller</li>
|
||||
<li>LSDTripp</li>
|
||||
<li>Ľubor Slušný</li>
|
||||
<li>Luca Di Diomede</li>
|
||||
<li>Lucio De Carli</li>
|
||||
<li>Luiz Bizzio</li>
|
||||
<li>Lukas Bischof</li>
|
||||
<li>Lukas Kammerer</li>
|
||||
<li>Lukas Söder</li>
|
||||
<li>Maksim Terehin</li>
|
||||
<li>Malcolm Cameron</li>
|
||||
<li>Manfred Radeschnig</li>
|
||||
<li>Marc Khouri</li>
|
||||
<li>Marcin Wilk</li>
|
||||
<li>Marcio Zimbres</li>
|
||||
<li>Marco Rossi</li>
|
||||
<li>Marcos Wolf</li>
|
||||
<li>Marek Marczykowski-Górecki</li>
|
||||
<li>Marius</li>
|
||||
<li>Mar. Balske</li>
|
||||
<li>Mark Farrell</li>
|
||||
<li>Mark Gilbert</li>
|
||||
<li>Mark Knam</li>
|
||||
<li>Mark Robinson</li>
|
||||
<li>Markrosoft</li>
|
||||
<li>Markus Halm</li>
|
||||
<li>Markus Schicker</li>
|
||||
<li>Markus Sobczack</li>
|
||||
<li>Marshall Bjerke</li>
|
||||
<li>Marten Hermans</li>
|
||||
<li>Martin Gasser</li>
|
||||
<li>Martin Hofbauer</li>
|
||||
<li>Martin Raine</li>
|
||||
<li>Martin Suelmann</li>
|
||||
<li>Martin Wilhelmi</li>
|
||||
<li>Marvin Honderboom</li>
|
||||
<li>Mateusz Grabowski</li>
|
||||
<li>Mathias Uhl</li>
|
||||
<li>Matt Kane</li>
|
||||
<li>Matthew Cameron</li>
|
||||
<li>Mauricio Allende</li>
|
||||
<li>Max Evans</li>
|
||||
<li>Mecky</li>
|
||||
<li>Mehmet Aydoğdu</li>
|
||||
<li>Michael Bartholomew</li>
|
||||
<li>Michael Bell</li>
|
||||
<li>Michael Bombe</li>
|
||||
<li>Michael Collins</li>
|
||||
<li>Michael Copeland</li>
|
||||
<li>Michael Ho</li>
|
||||
<li>Michael Kovacs</li>
|
||||
<li>Michael Lynch</li>
|
||||
<li>Michael Pennington</li>
|
||||
<li>Michael Sage</li>
|
||||
<li>Michael Stella</li>
|
||||
<li>Michael Thalmann</li>
|
||||
<li>Michael Wu</li>
|
||||
<li>MichaelZ</li>
|
||||
<li>Michel Bissonnette</li>
|
||||
<li>Mikael Wikström</li>
|
||||
<li>Mike Mason</li>
|
||||
<li>Mikhael Mariano</li>
|
||||
<li>Milan Múčka</li>
|
||||
<li>Miles Davis</li>
|
||||
<li>Minh Tang</li>
|
||||
<li>Moez Tharani</li>
|
||||
<li>Morgan Helton</li>
|
||||
<li>Myron Weber</li>
|
||||
<li>Murad Khasawneh</li>
|
||||
<li>N Patel</li>
|
||||
<li>Nathaniel Griswold</li>
|
||||
<li>Nelson Lee</li>
|
||||
<li>nezu</li>
|
||||
<li>Nicholas Jeppson</li>
|
||||
<li>Nicholas Kopas</li>
|
||||
<li>Nicholas Walczak</li>
|
||||
<li>Nick Leffler</li>
|
||||
<li>Nick Roethemeier</li>
|
||||
<li>Nico Baumgartner</li>
|
||||
<li>Nicolai Kragh-Hansen</li>
|
||||
<li>Nigel Smith</li>
|
||||
<li>Nihal Fernando</li>
|
||||
<li>Nils Orbat</li>
|
||||
<li>Nis Wechselberg</li>
|
||||
<li>Nithin Philips</li>
|
||||
<li>Nod Swal</li>
|
||||
<li>Nolan Haynes</li>
|
||||
<li>nubbn</li>
|
||||
<li>nybble</li>
|
||||
<li>Oh Be</li>
|
||||
<li>Oliver Schwarz</li>
|
||||
<li>Oliver Zimmer</li>
|
||||
<li>Omar El-Domeiri</li>
|
||||
<li>Omar Siam</li>
|
||||
<li>Oscar</li>
|
||||
<li>Patrick</li>
|
||||
<li>Patrick Fortin-Ducharme</li>
|
||||
<li>Patrick McDowell</li>
|
||||
<li>Patrick Wagstrom</li>
|
||||
<li>Paul Bishop</li>
|
||||
<li>Paul De La Rosa</li>
|
||||
<li>Paul Pietkiewicz</li>
|
||||
<li>Paul Tan</li>
|
||||
<li>Pawel Trofimiuk</li>
|
||||
<li>Peder Madsen</li>
|
||||
<li>Peter</li>
|
||||
<li>Peter Drayton</li>
|
||||
<li>Peter Farrelly</li>
|
||||
<li>Peter Okelmann</li>
|
||||
<li>Petra Lohmann</li>
|
||||
<li>Petri Heiskanen</li>
|
||||
<li>Phil Wu</li>
|
||||
<li>Philip Edwards</li>
|
||||
<li>Philip Merricks</li>
|
||||
<li>Pierre Brassart</li>
|
||||
<li>Pierre Peine</li>
|
||||
<li>posicat</li>
|
||||
<li>pozitron03</li>
|
||||
<li>Przemysław Szypowicz</li>
|
||||
<li>P_Dmitrij</li>
|
||||
<li>Qteal</li>
|
||||
<li>Quattro Uno</li>
|
||||
<li>Quentin Peten</li>
|
||||
<li>Ralph Borchers</li>
|
||||
<li>Ranc1d</li>
|
||||
<li>Randall D Bilbrey</li>
|
||||
<li>RandomJerk</li>
|
||||
<li>Raphael Schitz</li>
|
||||
<li>Ref Chowdhury</li>
|
||||
<li>René Rathenau</li>
|
||||
<li>ReysDad</li>
|
||||
<li>Ricardo Marques</li>
|
||||
<li>Richard</li>
|
||||
<li>Richard Bernarts</li>
|
||||
<li>Richard Fancher</li>
|
||||
<li>Richard Freemantle</li>
|
||||
<li>Richard Michael</li>
|
||||
<li>Rico Cantrell</li>
|
||||
<li>Rob</li>
|
||||
<li>Rob Holden</li>
|
||||
<li>Rob Tongue</li>
|
||||
<li>Robert Klauco</li>
|
||||
<li>Robert Weemhoff</li>
|
||||
<li>Robin Gfatter</li>
|
||||
<li>Rodion DENISYUK</li>
|
||||
<li>Rohit Priyadarshi</li>
|
||||
<li>Rolfs 3D UG</li>
|
||||
<li>Ronald LeBaron</li>
|
||||
<li>Ronald Wells</li>
|
||||
<li>Ronny Haldorsen</li>
|
||||
<li>rotx</li>
|
||||
<li>Rufo Sanchez</li>
|
||||
<li>Russell Scott</li>
|
||||
<li>Ryan</li>
|
||||
<li>Ryan Peacock</li>
|
||||
<li>Samed Ozoglu</li>
|
||||
<li>Sameul Davies</li>
|
||||
<li>Samuel Cote</li>
|
||||
<li>Samuel Vetsch</li>
|
||||
<li>Samuel Walker</li>
|
||||
<li>Sarah Foster</li>
|
||||
<li>Sarten X</li>
|
||||
<li>Satish Alwani</li>
|
||||
<li>Scott</li>
|
||||
<li>Scott Gagon</li>
|
||||
<li>Scott Spicola</li>
|
||||
<li>Scott Tusing</li>
|
||||
<li>Scott Worthington</li>
|
||||
<li>Scuba</li>
|
||||
<li>Sean</li>
|
||||
<li>Sean Akers</li>
|
||||
<li>SEAT</li>
|
||||
<li>Sebastian</li>
|
||||
<li>Seonwoo Lee</li>
|
||||
<li>Sergey Lukjanov</li>
|
||||
<li>Seth Jennings</li>
|
||||
<li>Shane Selling</li>
|
||||
<li>Shawn Butts</li>
|
||||
<li>Sheran Gunasekera</li>
|
||||
<li>Shichun Chen</li>
|
||||
<li>Shin Guey Wong</li>
|
||||
<li>Simon Evans</li>
|
||||
<li>Simon Sundgaard</li>
|
||||
<li>Simplistic Realities</li>
|
||||
<li>Sirmo</li>
|
||||
<li>Snowy Maslov</li>
|
||||
<li>Solve Technology</li>
|
||||
<li>srepac</li>
|
||||
<li>Stefan Bautz</li>
|
||||
<li>Stefan Müller</li>
|
||||
<li>Stefan Stemmer</li>
|
||||
<li>Stefan Vaillant</li>
|
||||
<li>Stephan Schmidt</li>
|
||||
<li>Stephen</li>
|
||||
<li>Stephen Hocking</li>
|
||||
<li>Steve Jones</li>
|
||||
<li>Steve Kerr</li>
|
||||
<li>Steve Ovens</li>
|
||||
<li>Steve Stringham</li>
|
||||
<li>Steven Richter</li>
|
||||
<li>Stratagem Solutions Ltd</li>
|
||||
<li>Sven Breckler</li>
|
||||
<li>sudo34</li>
|
||||
<li>SuperHiTech</li>
|
||||
<li>Tango_Echo_Alpha</li>
|
||||
<li>Tarlak Desaydrone</li>
|
||||
<li>TechBear</li>
|
||||
<li>techlobo</li>
|
||||
<li>Ted</li>
|
||||
<li>Tejun Heo</li>
|
||||
<li>TheSnowedOne</li>
|
||||
<li>TheTechGiant</li>
|
||||
<li>Thomas Charisoulis</li>
|
||||
<li>Thomas Gitlin</li>
|
||||
<li>Thomas Hagenmaier</li>
|
||||
<li>Thomas Hedberg Jensen</li>
|
||||
<li>Thomas Price</li>
|
||||
<li>Thomas Søfteland</li>
|
||||
<li>Tim Lenz</li>
|
||||
<li>Tim Wilkinson</li>
|
||||
<li>Timo Brinkmann</li>
|
||||
<li>Timothee Besset</li>
|
||||
<li>TitomusPrime</li>
|
||||
<li>Tobias Schafferhans</li>
|
||||
<li>Tom Lawson</li>
|
||||
<li>Tom York</li>
|
||||
<li>Tomas Kuchta</li>
|
||||
<li>Tomáš hrubý</li>
|
||||
<li>Torsten Droste</li>
|
||||
<li>Torsten Knoll</li>
|
||||
<li>Tracy Fitch</li>
|
||||
<li>Tristan Schoening</li>
|
||||
<li>Truman Kilen</li>
|
||||
<li>turbochris</li>
|
||||
<li>tutanak</li>
|
||||
<li>Tyler</li>
|
||||
<li>Udo Schroeter</li>
|
||||
<li>Uli Fahrer</li>
|
||||
<li>Vasily Lazarev</li>
|
||||
<li>Vidru Eduard</li>
|
||||
<li>Vicente Salvador Cubedo</li>
|
||||
<li>Viktor Aschenbrenner</li>
|
||||
<li>Viktor Ekmark</li>
|
||||
<li>Vincent Chov</li>
|
||||
<li>Vlad Sterescu</li>
|
||||
<li>Volker Gropp</li>
|
||||
<li>Walli</li>
|
||||
<li>Walter_Ego</li>
|
||||
<li>William Wenzel</li>
|
||||
<li>Will Froning</li>
|
||||
<li>William Hooper</li>
|
||||
<li>William Perrin</li>
|
||||
<li>William Stearns</li>
|
||||
<li>Woojin Son</li>
|
||||
<li>xMdb</li>
|
||||
<li>Yanko Kaneti</li>
|
||||
<li>Yaroslav Kulikovskikh</li>
|
||||
<li>Yethal</li>
|
||||
<li>Yevgeniy Kuksenko</li>
|
||||
<li>Yew Kay Yan</li>
|
||||
<li>Yigal Dar</li>
|
||||
<li>Yogi</li>
|
||||
<li>YURI LEE</li>
|
||||
<li>Yurii Ostapchuk</li>
|
||||
<li>Zeljko</li>
|
||||
<li>zgen</li>
|
||||
<li>Zoltan Magyari</li>
|
||||
<li>Zsombor Vari</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</div><br>
|
||||
@@ -2049,9 +2700,17 @@
|
||||
</div>
|
||||
<iframe id="webterm-iframe" src="" style="width: 100%; height: 100%"></iframe>
|
||||
</div>
|
||||
<ul class="navbar-bg-tips">
|
||||
<li class="left">
|
||||
<pre id="kvmd-meta-tips-left"></pre>
|
||||
</li>
|
||||
<li class="right">
|
||||
<pre id="kvmd-meta-tips-right"></pre>
|
||||
</li>
|
||||
</ul>
|
||||
<ul class="footer">
|
||||
<li class="footer-left"><span id="kvmd-meta-server-host" title="Server name (see System/About)"></span> | <span id="kvmd-version-kvmd" title="KVMD version"></span> | <span id="kvmd-version-streamer" title="Streamer version"></span></li>
|
||||
<li class="footer-right"><a target="_blank" href="https://pikvm.org" i18n="index_text_10">PiKVM Project</a> | <a target="_blank" href="https://docs.pikvm.org" i18n="index_text_11">Documentation</a> | <a target="_blank" href="https://github.com/mofeng-git/One-KVM" i18n="index_text_12">One-KVM Project</a> | <a target="_blank" href="https://one-kvm.mofeng.run" i18n="index_text_13">One-KVM Documentation</a></li>
|
||||
<li class="left"><span id="kvmd-meta-server-host" title="Server name (see System/About)"></span> | <span id="kvmd-version-kvmd" title="KVMD version"></span> | <span id="kvmd-version-streamer" title="Streamer version"></span></li>
|
||||
<li class="right"><a target="_blank" href="https://pikvm.org" i18n="index_text_10">PiKVM Project</a> | <a target="_blank" href="https://docs.pikvm.org" i18n="index_text_11">Documentation</a> | <a target="_blank" href="https://github.com/mofeng-git/One-KVM" i18n="index_text_12">One-KVM Project</a> | <a target="_blank" href="https://one-kvm.mofeng.run" i18n="index_text_13">One-KVM Documentation</a></li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
@@ -11,14 +11,20 @@ block body
|
||||
include navbar.pug
|
||||
include windows.pug
|
||||
|
||||
ul(class="navbar-bg-tips")
|
||||
li(class="left")
|
||||
pre(id="kvmd-meta-tips-left")
|
||||
li(class="right")
|
||||
pre(id="kvmd-meta-tips-right")
|
||||
|
||||
ul(class="footer")
|
||||
li(class="footer-left")
|
||||
li(class="left")
|
||||
span(id="kvmd-meta-server-host" title="Server name (see System/About)")
|
||||
| |
|
||||
span(id="kvmd-version-kvmd" title="KVMD version")
|
||||
| |
|
||||
span(id="kvmd-version-streamer" title="Streamer version")
|
||||
li(class="footer-right")
|
||||
li(class="right")
|
||||
a(target="_blank" href="https://pikvm.org" i18n="index_text_10") PiKVM Project
|
||||
| |
|
||||
a(target="_blank" href="https://docs.pikvm.org" i18n="index_text_11") Documentation
|
||||
|
||||
@@ -72,14 +72,6 @@ li(id="msd-dropdown" class="right feature-disabled")
|
||||
tr(id="msd-new-part" class="hidden")
|
||||
td(i18n="kvm_text70") Upload partition:
|
||||
td(width="100%") #[select(id="msd-new-part-selector")]
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(class="value" i18n="kvm_text71") Note:
|
||||
td(i18n="kvm_text72") • Don't close the browser page until the upload is complete.
|
||||
tr
|
||||
td
|
||||
td(i18n="kvm_text73") • To speed up the upload, close the stream window.
|
||||
div(id="msd-uploading-sub" class="hidden")
|
||||
hr
|
||||
table(class="kv")
|
||||
@@ -92,6 +84,15 @@ li(id="msd-dropdown" class="right feature-disabled")
|
||||
div(class="text")
|
||||
div(id="msd-uploading-progress" class="progress")
|
||||
span(id="msd-uploading-progress-value" class="progress-value")
|
||||
div(id="msd-new-tips" class="hidden")
|
||||
hr
|
||||
table(class="kv")
|
||||
tr
|
||||
td(class="value" i18n="kvm_text71") Note:
|
||||
td(i18n="kvm_text72") • Don't close the browser page until the upload is complete.
|
||||
tr
|
||||
td
|
||||
td(i18n="kvm_text73") • To speed up the upload, close the stream window.
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(disabled id="msd-connect-button" class="row50" i18n="kvm_text76") Connect drive to Server
|
||||
|
||||
@@ -120,6 +120,14 @@ li(id="system-dropdown" class="right")
|
||||
td(id="hid-mouse-scroll-value" class="value-number")
|
||||
tr
|
||||
+menu_switch_notable("hid-mouse-dot-switch", "Show the blue dot", true, true, "hid-mouse-dot-switch")
|
||||
details
|
||||
summary(i18n="kvm_text83") Web UI settings
|
||||
div(class="spoiler")
|
||||
table(class="kv")
|
||||
tr
|
||||
+menu_switch_notable("page-close-ask-switch", "Ask page close confirmation", true, true, "page-close-ask-switch")
|
||||
tr
|
||||
+menu_switch_notable("page-full-tab-stream-switch", "Expand for the entire tab by default", true, false,"page-full-tab-stream-switch")
|
||||
table(class="kv")
|
||||
tr(id="hid-connect" class="feature-disabled")
|
||||
+menu_switch_notable("hid-connect-switch", "Connect HID to Server", true, true, "hid-connect-switch")
|
||||
@@ -132,8 +140,6 @@ li(id="system-dropdown" class="right")
|
||||
"Turning off this switch will disconnect the main USB<br>from the server. Are you sure you want to continue?")
|
||||
tr(id="v4-locator" class="feature-disabled")
|
||||
+menu_switch_notable_gpio("__v4_locator__", "Enable locator LED")
|
||||
tr
|
||||
+menu_switch_notable("page-close-ask-switch", "Ask page close confirmation", true, true, "page-close-ask-switch")
|
||||
hr
|
||||
div(class="buttons buttons-row")
|
||||
button(data-force-hide-menu data-show-window="keyboard-window" class="row50" i18n="kvm_text30") • Show keyboard
|
||||
|
||||
@@ -17,9 +17,9 @@ mixin menu_switch_notable_gpio(channel, title, confirm_off="")
|
||||
td !{title}:
|
||||
td(align="right")
|
||||
div(class="switch-box")
|
||||
input(disabled type="checkbox" id=`gpio-switch-${channel}` class="gpio-switch"
|
||||
input(disabled type="checkbox" id=`__gpio-switch-${channel}` class=`__gpio-switch-${channel} gpio-switch`
|
||||
data-channel=channel data-confirm-off=confirm_off)
|
||||
label(for=`gpio-switch-${channel}`)
|
||||
label(for=`__gpio-switch-${channel}`)
|
||||
span(class="switch-inner")
|
||||
span(class="switch")
|
||||
|
||||
|
||||
@@ -41,6 +41,638 @@ div(id="about-window" class="window")
|
||||
| // If you also want to support this project,#[br]
|
||||
| // you can donate on #[a(target="_blank" href="https://www.patreon.com/pikvm") Patreon]
|
||||
| or #[a(target="_blank" href="https://paypal.me/pikvm") Paypal].
|
||||
ul
|
||||
li A. Isenring
|
||||
li Aaron Graubert
|
||||
li Aaron Heise
|
||||
li Aaron Stein
|
||||
li Accalia
|
||||
li Adam Goodbar
|
||||
li Adam S
|
||||
li Adam Stuart
|
||||
li AdamBomb
|
||||
li adipisicing
|
||||
li Adrian Basham
|
||||
li Adrian Popescu
|
||||
li Ahmed Syed
|
||||
li Alberto Bassi
|
||||
li alejandro
|
||||
li Aleksei Brusianskii
|
||||
li Alessio Curri
|
||||
li Alex T
|
||||
li Alex Z
|
||||
li Alexander Karmanov
|
||||
li Alexander Lahuerta
|
||||
li Alexander Martin
|
||||
li Alexander Pankov
|
||||
li Alexandre Jablonski
|
||||
li Alexey Kamenskiy
|
||||
li alm0241
|
||||
li Alok Anand
|
||||
li Alucard
|
||||
li Ananthaneshan Elampoornan
|
||||
li Andreas Marufke
|
||||
li Andreas Schmid
|
||||
li Andrew Brant
|
||||
li Andrew Melton
|
||||
li Andrew Reusch
|
||||
li Andrew Ruan
|
||||
li Andrzej V
|
||||
li Andy
|
||||
li Andy Keys
|
||||
li Anish Patel
|
||||
li Anix
|
||||
li Anonymous
|
||||
li Anthony Junk
|
||||
li Anton Kovalenko
|
||||
li Armen
|
||||
li Aron Green
|
||||
li Aron Perelman
|
||||
li Artem Simonov
|
||||
li Arthur Mayer
|
||||
li Arthur Woimbée
|
||||
li Ashlesh Chaudhari
|
||||
li Asim Shakour
|
||||
li Augusto Becciu
|
||||
li AVS Computer
|
||||
li awkspace
|
||||
li Badal Patel
|
||||
li baddog
|
||||
li Bao Tin Hoang
|
||||
li Bean Co.
|
||||
li Bela Bargel
|
||||
li Belf Igor
|
||||
li Ben Gordon
|
||||
li Ben Scott
|
||||
li Benedikt Heine
|
||||
li Benedikt Meier
|
||||
li Benjamin Frewert
|
||||
li Benjamin Melancon
|
||||
li Benjamin Schwartz
|
||||
li Benjamin Stegmann
|
||||
li Benni Stauder
|
||||
li Bernhard Fitzke
|
||||
li Beu
|
||||
li bikmaek
|
||||
li bitjoe
|
||||
li Bits and Bytes Computers LLC
|
||||
li Bjoern Petsch
|
||||
li Blair Hasler
|
||||
li Blindside
|
||||
li Blue Frog LLC
|
||||
li Bootstrapper - Programmierung erklärt
|
||||
li Bosco
|
||||
li Bradford King
|
||||
li Brainspore Networks
|
||||
li Branden Shaulis
|
||||
li Brandon Daniels
|
||||
li Brian
|
||||
li Brian Moses
|
||||
li Brian T Mulcahy
|
||||
li Brian Vecchiarelli
|
||||
li Brian White
|
||||
li Bruno Gomes
|
||||
li Bryan Adams
|
||||
li Bryan Montgomery
|
||||
li Buzzer
|
||||
li C P ELSE
|
||||
li Calanish
|
||||
li Cameron Hatcher
|
||||
li Cameron Tacklind
|
||||
li Carl Mercier
|
||||
li Carl-Fredrik Johansson
|
||||
li Carlos Eduardo Porter Herrera
|
||||
li Carlos Garcia
|
||||
li Carlos Manuel Torres
|
||||
li cbad536
|
||||
li César Nascimento
|
||||
li CHINATERA LIMITED
|
||||
li Chris Blackmon
|
||||
li Chris Burton
|
||||
li Chris Campbell
|
||||
li Chris Jackson
|
||||
li Chris Lewis
|
||||
li Chris Rizio
|
||||
li Christi King
|
||||
li Christian Schlögl
|
||||
li Christian Svensson
|
||||
li Christof Maluck
|
||||
li Christoph Dette
|
||||
li Christoffer Lund
|
||||
li Christopher Bulla
|
||||
li Christopher Gelatt
|
||||
li Christopher Hearn
|
||||
li Christopher Mandlbaur
|
||||
li Christopher Mendoza
|
||||
li Christopher Simms
|
||||
li Chucktastic
|
||||
li Cihan VURAL
|
||||
li clauskj3r
|
||||
li Clifford Coleman
|
||||
li Clinton Lee Taylor
|
||||
li Cole Imhoff
|
||||
li Colin Goodman
|
||||
li Corey Layton
|
||||
li Corey Lista
|
||||
li Craig Keenan
|
||||
li Crossfactor
|
||||
li Cruzzer
|
||||
li ctag
|
||||
li Curt Sammer
|
||||
li CyB0rgg
|
||||
li DeMentor
|
||||
li Desmond Whitt
|
||||
li Daegara
|
||||
li DailyAneurism
|
||||
li Damon Meledones
|
||||
li Dan Berkowitz
|
||||
li Dan Brakeley
|
||||
li Daniel Bowder
|
||||
li Daniel Cabrera
|
||||
li Daniel Davila
|
||||
li Danilo Saft
|
||||
li Danne
|
||||
li Dariusz Techmański
|
||||
li David
|
||||
li David Brausewetter
|
||||
li David Davis
|
||||
li David Godibadze
|
||||
li David Howell
|
||||
li David Irvine
|
||||
li David Klinkman
|
||||
li David Niemann
|
||||
li David Shay
|
||||
li David Ye
|
||||
li David York
|
||||
li Denis
|
||||
li Denis Andreev
|
||||
li Denis Yatsenko
|
||||
li Dennis Becker
|
||||
li Dennis Joslin
|
||||
li Dennis Lomet
|
||||
li Derek Jarvis
|
||||
li Derek Yap
|
||||
li Didrik
|
||||
li digitalbaconbits
|
||||
li Dimitrij Jedich
|
||||
li dixon wong
|
||||
li dizztrukshin
|
||||
li Dmitry Shilov
|
||||
li DogeLabs
|
||||
li Dominic Phoon
|
||||
li Dominik Klonowski
|
||||
li Donald Hays
|
||||
li Edmon Abdul Nur
|
||||
li Edward Wang
|
||||
li Egan Ford
|
||||
li Elani Ferri
|
||||
li Elliot Woo
|
||||
li Entt
|
||||
li Eric Phenix
|
||||
li Ethan Shold
|
||||
li Eugene Sukhodolin
|
||||
li ewook
|
||||
li eye-catcher.com
|
||||
li Fabian Druschke
|
||||
li Fabiano Sidler
|
||||
li Far Pin Solutions, LLC
|
||||
li Felyx Gabryel
|
||||
li Fergus McKay
|
||||
li Finn Ebenritter
|
||||
li floppy
|
||||
li fo0bar
|
||||
li Foad Yousef
|
||||
li Foamy
|
||||
li Foli Ayivoh
|
||||
li Folkert Weistra
|
||||
li Francisco Pavon
|
||||
li Frank
|
||||
li Frank Sander
|
||||
li Frederick Czajka
|
||||
li Fredrik Idréus
|
||||
li Garrett Dangerfield
|
||||
li Ge Men
|
||||
li Geekworm
|
||||
li Genkinger Andreas
|
||||
li Geijer
|
||||
li Geoffrey Wright
|
||||
li George Becker
|
||||
li Georgy Brodsky
|
||||
li Gerald
|
||||
li Gerardus Vernimmen
|
||||
li Gernot Neuschröer
|
||||
li Giovanni Fulco
|
||||
li GK
|
||||
li Glen Dragon
|
||||
li Greg Winterstein
|
||||
li Gregory Smith
|
||||
li Gregory Treantos
|
||||
li grewil
|
||||
li Grey Cynic
|
||||
li Guenter Honisch
|
||||
li Guido Bernacchi
|
||||
li Gustin Johnson
|
||||
li György Tamás Vizi
|
||||
li Haiberg GmbH
|
||||
li Haven Zheng
|
||||
li Heibunny
|
||||
li Heikki Tiittanen
|
||||
li Helio Leonardo Pinheiro e Mota
|
||||
li Henrik Ählström
|
||||
li Henry Hood
|
||||
li HimKo
|
||||
li HouseFPV
|
||||
li Howard Simons
|
||||
li HyunohRyu
|
||||
li Icculus
|
||||
li iks
|
||||
li INFO TRX INC
|
||||
li Invader Monks
|
||||
li Ioannis Karageorgos
|
||||
li Isaac
|
||||
li IT Lifesaver
|
||||
li Ivan Ganev
|
||||
li Ivan Josiah Lapis
|
||||
li Ivan Shapovalov
|
||||
li iwbjhbweriuhf
|
||||
li J
|
||||
li J L
|
||||
li Jaanus
|
||||
li Jackson Wyatt
|
||||
li Jacob Karaffa
|
||||
li Jacob Morgan
|
||||
li James Cadd
|
||||
li James Cobb
|
||||
li James Edwards
|
||||
li James Kocher
|
||||
li James Mayhugh
|
||||
li James Noonan
|
||||
li James Ye
|
||||
li Jamie Murphy
|
||||
li Jamie Scott
|
||||
li Jan Niehusmann
|
||||
li Jannick Oursin
|
||||
li Jari Hiltunen
|
||||
li Jason Crossley
|
||||
li Jason Downey
|
||||
li Jason Toland
|
||||
li Jasper Backer
|
||||
li Jay Davis
|
||||
li Jay Isaacs
|
||||
li Jazereel Goh
|
||||
li Jean-Daniel Croteau
|
||||
li Jean-Philippe Guilbault
|
||||
li Jeff
|
||||
li Jeff Bowman
|
||||
li Jeff Urlwin
|
||||
li Jennifer Herting
|
||||
li Jennifer Rowlett
|
||||
li Jeremy Abel
|
||||
li Jeremy Combs
|
||||
li Jeremy Hines
|
||||
li Jerremy Holland
|
||||
li Jerry Nall
|
||||
li Jerry Y. Chen
|
||||
li Jim Bailey
|
||||
li Jim Harbin
|
||||
li Jimmy Burgett
|
||||
li Jimmy Stanley
|
||||
li Joachim Bruening
|
||||
li Joe Hanson
|
||||
li Joe Hinteregger
|
||||
li Joe Ventura
|
||||
li Joel Jacobs
|
||||
li Johannes Heigermose
|
||||
li John Andersen
|
||||
li John Copeland
|
||||
li John F Glenn
|
||||
li John Holmes
|
||||
li John Kelley
|
||||
li John McGovern
|
||||
li Johnny Henson
|
||||
li Jon Ferguy
|
||||
li Jon-Eric
|
||||
li Joni Ruuskanen
|
||||
li Jonas Fischer
|
||||
li Jonathan Slenders
|
||||
li Jonathan Vaughn
|
||||
li Joost Backer
|
||||
li Jordan Blake
|
||||
li Jordi Pakey-Rodriguez
|
||||
li Joris van Embden
|
||||
li Joseph Swift
|
||||
li Josh Nethery
|
||||
li Josh Ricker
|
||||
li Josh VanDeraa
|
||||
li Joshua Futterer
|
||||
li Jozef Riha
|
||||
li Jörgen Fredriksson
|
||||
li Julian Forero
|
||||
li Julian Schneider
|
||||
li Julien Angelier
|
||||
li Justin
|
||||
li Justin Waters
|
||||
li Kai Hadler
|
||||
li Kamil Chyba
|
||||
li Kari Matti Korpi
|
||||
li Karl Dunne
|
||||
li Karl Moos
|
||||
li Keith Muggleton
|
||||
li Ken Lee
|
||||
li Kenneth Younger III
|
||||
li Kenny Hui
|
||||
li KeonWoo PARK
|
||||
li Kevin Bajohr
|
||||
li Kevin Schwartz
|
||||
li Kevin Sherwood
|
||||
li Kiera Kujisawa
|
||||
li Kiran Schuler
|
||||
li Koloman
|
||||
li Konrad Neitzel
|
||||
li Krzysztof Żelaśkiewicz
|
||||
li Lance Ward
|
||||
li Larry Meaney
|
||||
li Lars
|
||||
li Lars Reinhardt
|
||||
li Lee Wilkinson
|
||||
li LeeNX
|
||||
li Leon Siegl
|
||||
li Leonard Feineis
|
||||
li Lewis Wild
|
||||
li Liran
|
||||
li Liviu Dimitriu
|
||||
li Lizardo Hernandez
|
||||
li LoCascio
|
||||
li Lordbob75
|
||||
li Lothar Schweikle-Droll
|
||||
li Louis Müller
|
||||
li LSDTripp
|
||||
li Ľubor Slušný
|
||||
li Luca Di Diomede
|
||||
li Lucio De Carli
|
||||
li Luiz Bizzio
|
||||
li Lukas Bischof
|
||||
li Lukas Kammerer
|
||||
li Lukas Söder
|
||||
li Maksim Terehin
|
||||
li Malcolm Cameron
|
||||
li Manfred Radeschnig
|
||||
li Marc Khouri
|
||||
li Marcin Wilk
|
||||
li Marcio Zimbres
|
||||
li Marco Rossi
|
||||
li Marcos Wolf
|
||||
li Marek Marczykowski-Górecki
|
||||
li Marius
|
||||
li Mar. Balske
|
||||
li Mark Farrell
|
||||
li Mark Gilbert
|
||||
li Mark Knam
|
||||
li Mark Robinson
|
||||
li Markrosoft
|
||||
li Markus Halm
|
||||
li Markus Schicker
|
||||
li Markus Sobczack
|
||||
li Marshall Bjerke
|
||||
li Marten Hermans
|
||||
li Martin Gasser
|
||||
li Martin Hofbauer
|
||||
li Martin Raine
|
||||
li Martin Suelmann
|
||||
li Martin Wilhelmi
|
||||
li Marvin Honderboom
|
||||
li Mateusz Grabowski
|
||||
li Mathias Uhl
|
||||
li Matt Kane
|
||||
li Matthew Cameron
|
||||
li Mauricio Allende
|
||||
li Max Evans
|
||||
li Mecky
|
||||
li Mehmet Aydoğdu
|
||||
li Michael Bartholomew
|
||||
li Michael Bell
|
||||
li Michael Bombe
|
||||
li Michael Collins
|
||||
li Michael Copeland
|
||||
li Michael Ho
|
||||
li Michael Kovacs
|
||||
li Michael Lynch
|
||||
li Michael Pennington
|
||||
li Michael Sage
|
||||
li Michael Stella
|
||||
li Michael Thalmann
|
||||
li Michael Wu
|
||||
li MichaelZ
|
||||
li Michel Bissonnette
|
||||
li Mikael Wikström
|
||||
li Mike Mason
|
||||
li Mikhael Mariano
|
||||
li Milan Múčka
|
||||
li Miles Davis
|
||||
li Minh Tang
|
||||
li Moez Tharani
|
||||
li Morgan Helton
|
||||
li Myron Weber
|
||||
li Murad Khasawneh
|
||||
li N Patel
|
||||
li Nathaniel Griswold
|
||||
li Nelson Lee
|
||||
li nezu
|
||||
li Nicholas Jeppson
|
||||
li Nicholas Kopas
|
||||
li Nicholas Walczak
|
||||
li Nick Leffler
|
||||
li Nick Roethemeier
|
||||
li Nico Baumgartner
|
||||
li Nicolai Kragh-Hansen
|
||||
li Nigel Smith
|
||||
li Nihal Fernando
|
||||
li Nils Orbat
|
||||
li Nis Wechselberg
|
||||
li Nithin Philips
|
||||
li Nod Swal
|
||||
li Nolan Haynes
|
||||
li nubbn
|
||||
li nybble
|
||||
li Oh Be
|
||||
li Oliver Schwarz
|
||||
li Oliver Zimmer
|
||||
li Omar El-Domeiri
|
||||
li Omar Siam
|
||||
li Oscar
|
||||
li Patrick
|
||||
li Patrick Fortin-Ducharme
|
||||
li Patrick McDowell
|
||||
li Patrick Wagstrom
|
||||
li Paul Bishop
|
||||
li Paul De La Rosa
|
||||
li Paul Pietkiewicz
|
||||
li Paul Tan
|
||||
li Pawel Trofimiuk
|
||||
li Peder Madsen
|
||||
li Peter
|
||||
li Peter Drayton
|
||||
li Peter Farrelly
|
||||
li Peter Okelmann
|
||||
li Petra Lohmann
|
||||
li Petri Heiskanen
|
||||
li Phil Wu
|
||||
li Philip Edwards
|
||||
li Philip Merricks
|
||||
li Pierre Brassart
|
||||
li Pierre Peine
|
||||
li posicat
|
||||
li pozitron03
|
||||
li Przemysław Szypowicz
|
||||
li P_Dmitrij
|
||||
li Qteal
|
||||
li Quattro Uno
|
||||
li Quentin Peten
|
||||
li Ralph Borchers
|
||||
li Ranc1d
|
||||
li Randall D Bilbrey
|
||||
li RandomJerk
|
||||
li Raphael Schitz
|
||||
li Ref Chowdhury
|
||||
li René Rathenau
|
||||
li ReysDad
|
||||
li Ricardo Marques
|
||||
li Richard
|
||||
li Richard Bernarts
|
||||
li Richard Fancher
|
||||
li Richard Freemantle
|
||||
li Richard Michael
|
||||
li Rico Cantrell
|
||||
li Rob
|
||||
li Rob Holden
|
||||
li Rob Tongue
|
||||
li Robert Klauco
|
||||
li Robert Weemhoff
|
||||
li Robin Gfatter
|
||||
li Rodion DENISYUK
|
||||
li Rohit Priyadarshi
|
||||
li Rolfs 3D UG
|
||||
li Ronald LeBaron
|
||||
li Ronald Wells
|
||||
li Ronny Haldorsen
|
||||
li rotx
|
||||
li Rufo Sanchez
|
||||
li Russell Scott
|
||||
li Ryan
|
||||
li Ryan Peacock
|
||||
li Samed Ozoglu
|
||||
li Sameul Davies
|
||||
li Samuel Cote
|
||||
li Samuel Vetsch
|
||||
li Samuel Walker
|
||||
li Sarah Foster
|
||||
li Sarten X
|
||||
li Satish Alwani
|
||||
li Scott
|
||||
li Scott Gagon
|
||||
li Scott Spicola
|
||||
li Scott Tusing
|
||||
li Scott Worthington
|
||||
li Scuba
|
||||
li Sean
|
||||
li Sean Akers
|
||||
li SEAT
|
||||
li Sebastian
|
||||
li Seonwoo Lee
|
||||
li Sergey Lukjanov
|
||||
li Seth Jennings
|
||||
li Shane Selling
|
||||
li Shawn Butts
|
||||
li Sheran Gunasekera
|
||||
li Shichun Chen
|
||||
li Shin Guey Wong
|
||||
li Simon Evans
|
||||
li Simon Sundgaard
|
||||
li Simplistic Realities
|
||||
li Sirmo
|
||||
li Snowy Maslov
|
||||
li Solve Technology
|
||||
li srepac
|
||||
li Stefan Bautz
|
||||
li Stefan Müller
|
||||
li Stefan Stemmer
|
||||
li Stefan Vaillant
|
||||
li Stephan Schmidt
|
||||
li Stephen
|
||||
li Stephen Hocking
|
||||
li Steve Jones
|
||||
li Steve Kerr
|
||||
li Steve Ovens
|
||||
li Steve Stringham
|
||||
li Steven Richter
|
||||
li Stratagem Solutions Ltd
|
||||
li Sven Breckler
|
||||
li sudo34
|
||||
li SuperHiTech
|
||||
li Tango_Echo_Alpha
|
||||
li Tarlak Desaydrone
|
||||
li TechBear
|
||||
li techlobo
|
||||
li Ted
|
||||
li Tejun Heo
|
||||
li TheSnowedOne
|
||||
li TheTechGiant
|
||||
li Thomas Charisoulis
|
||||
li Thomas Gitlin
|
||||
li Thomas Hagenmaier
|
||||
li Thomas Hedberg Jensen
|
||||
li Thomas Price
|
||||
li Thomas Søfteland
|
||||
li Tim Lenz
|
||||
li Tim Wilkinson
|
||||
li Timo Brinkmann
|
||||
li Timothee Besset
|
||||
li TitomusPrime
|
||||
li Tobias Schafferhans
|
||||
li Tom Lawson
|
||||
li Tom York
|
||||
li Tomas Kuchta
|
||||
li Tomáš hrubý
|
||||
li Torsten Droste
|
||||
li Torsten Knoll
|
||||
li Tracy Fitch
|
||||
li Tristan Schoening
|
||||
li Truman Kilen
|
||||
li turbochris
|
||||
li tutanak
|
||||
li Tyler
|
||||
li Udo Schroeter
|
||||
li Uli Fahrer
|
||||
li Vasily Lazarev
|
||||
li Vidru Eduard
|
||||
li Vicente Salvador Cubedo
|
||||
li Viktor Aschenbrenner
|
||||
li Viktor Ekmark
|
||||
li Vincent Chov
|
||||
li Vlad Sterescu
|
||||
li Volker Gropp
|
||||
li Walli
|
||||
li Walter_Ego
|
||||
li William Wenzel
|
||||
li Will Froning
|
||||
li William Hooper
|
||||
li William Perrin
|
||||
li William Stearns
|
||||
li Woojin Son
|
||||
li xMdb
|
||||
li Yanko Kaneti
|
||||
li Yaroslav Kulikovskikh
|
||||
li Yethal
|
||||
li Yevgeniy Kuksenko
|
||||
li Yew Kay Yan
|
||||
li Yigal Dar
|
||||
li Yogi
|
||||
li YURI LEE
|
||||
li Yurii Ostapchuk
|
||||
li Zeljko
|
||||
li zgen
|
||||
li Zoltan Magyari
|
||||
li Zsombor Vari
|
||||
br
|
||||
p(class="text credits")
|
||||
a(target="_blank" href="https://pikvm.org") PiKVM Project
|
||||
|
||||
@@ -15,7 +15,7 @@ div(id="stream-window" class="window window-resizable")
|
||||
button(class="window-button-exit-full-tab") ▼
|
||||
div(id="stream-box" class="stream-box-offline")
|
||||
img(id="stream-image" src=`${png_dir}/blank-stream.png`)
|
||||
video(id="stream-video" class="hidden" autoplay playsinline muted)
|
||||
video(id="stream-video" class="hidden" disablePictureInPicture="true" autoplay playsinline muted)
|
||||
div(id="stream-fullscreen-active")
|
||||
|
||||
div(id="stream-mouse-buttons" class="keypad" align="center")
|
||||
|
||||
@@ -94,7 +94,7 @@
|
||||
</div>
|
||||
</form>
|
||||
<ul class="footer">
|
||||
<li class="footer-left" i18n="footer-left">This site is actively using JavaScript.<br>
|
||||
<li class="left" i18n="footer-left">This site is actively using JavaScript.<br>
|
||||
It doesn't contain ads, but is blocked by some ad filters.<br>
|
||||
Please turn it off to continue and reload the page.
|
||||
</li>
|
||||
|
||||
@@ -33,7 +33,7 @@ block body
|
||||
td #[button(id="login-button" class="key" i18n="login") Login]
|
||||
|
||||
ul(class="footer")
|
||||
li(class="footer-left" i18n="footer-left")
|
||||
li(class="left" i18n="footer-left")
|
||||
| This site is actively using JavaScript.#[br]
|
||||
| It doesn't contain ads, but is blocked by some ad filters.#[br]
|
||||
| Please turn it off to continue and reload the page.
|
||||
|
||||
@@ -24,28 +24,6 @@ div#text-menu {
|
||||
width: 340px;
|
||||
}
|
||||
|
||||
textarea#hid-pak-text {
|
||||
display: block;
|
||||
resize: none;
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
border: var(--border-default-thin);
|
||||
border-radius: 4px;
|
||||
color: var(--cs-code-default-fg);
|
||||
background-color: var(--cs-code-default-bg);
|
||||
-webkit-appearance:none;
|
||||
}
|
||||
|
||||
textarea#hid-pak-text::-moz-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
textarea#hid-pak-text::-webkit-input-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
input#hid-recorder-new-script-file {
|
||||
display: none;
|
||||
}
|
||||
|
||||
@@ -123,6 +123,26 @@ select {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding-left: 5px;
|
||||
}
|
||||
select[size] {
|
||||
height: auto;
|
||||
padding: 5px;
|
||||
}
|
||||
select[size]::-webkit-scrollbar {
|
||||
width: 8px;
|
||||
height: 8px;
|
||||
}
|
||||
select[size]::-webkit-scrollbar-thumb {
|
||||
border-radius: 4px;
|
||||
background: var(--cs-scroll-default-bg);
|
||||
}
|
||||
@-moz-document url-prefix() {
|
||||
select[size] {
|
||||
scrollbar-width: 8px;
|
||||
scrollbar-color: var(--cs-scroll-default-bg) var(--cs-code-default-bg);
|
||||
}
|
||||
}
|
||||
select:not([size]) {
|
||||
padding-right: 25px;
|
||||
}
|
||||
button.small {
|
||||
@@ -149,22 +169,24 @@ select {
|
||||
-webkit-user-select: none;
|
||||
-moz-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
select:not([size]) {
|
||||
background-image: url("../svg/select-arrow-normal.svg");
|
||||
background-position: center right;
|
||||
background-repeat: no-repeat;
|
||||
}
|
||||
select:disabled {
|
||||
select:not([size]):disabled {
|
||||
background-image: url("../svg/select-arrow-inactive.svg") !important;
|
||||
}
|
||||
select:active {
|
||||
select:not([size]):active {
|
||||
color: var(--cs-control-intensive-fg) !important;
|
||||
background-image: url("../svg/select-arrow-intensive.svg") !important;
|
||||
}
|
||||
select option {
|
||||
select:not([size]) option {
|
||||
color: var(--cs-control-default-fg);
|
||||
background-color: var(--cs-control-default-bg);
|
||||
}
|
||||
select option.comment {
|
||||
select:not([size]) option.comment {
|
||||
color: var(--cs-control-disabled-fg);
|
||||
font-style: italic;
|
||||
}
|
||||
@@ -180,6 +202,26 @@ input[type=text], input[type=password] {
|
||||
height: 30px;
|
||||
}
|
||||
|
||||
textarea {
|
||||
display: block;
|
||||
resize: none;
|
||||
height: 120px;
|
||||
width: 100%;
|
||||
border: var(--border-default-thin);
|
||||
border-radius: 4px;
|
||||
color: var(--cs-code-default-fg);
|
||||
background-color: var(--cs-code-default-bg);
|
||||
-webkit-appearance:none;
|
||||
}
|
||||
textarea::-moz-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
textarea::-webkit-input-placeholder {
|
||||
line-height: 60px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
div.buttons-row {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
@@ -218,6 +260,30 @@ div.buttons-row {
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
table.kv {
|
||||
border-spacing: 5px;
|
||||
margin: 0 10px 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
table.kv td {
|
||||
text-align: left;
|
||||
}
|
||||
table.kv td.value {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
}
|
||||
table.kv td.value-slider {
|
||||
width: 100%;
|
||||
}
|
||||
table.kv td.value-number {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
ul.footer {
|
||||
list-style-type: none;
|
||||
bottom: 0;
|
||||
@@ -231,10 +297,10 @@ ul.footer {
|
||||
ul.footer li {
|
||||
padding: 0 10px;
|
||||
}
|
||||
ul.footer li.footer-left {
|
||||
ul.footer li.left {
|
||||
float: left;
|
||||
}
|
||||
ul.footer li.footer-right {
|
||||
ul.footer li.right {
|
||||
float: right;
|
||||
}
|
||||
ul.footer li a {
|
||||
|
||||
@@ -167,30 +167,6 @@ ul#navbar li div.menu div.text {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu table.kv {
|
||||
border-spacing: 5px;
|
||||
margin: 0 10px 0 10px;
|
||||
font-size: 12px;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td {
|
||||
text-align: left;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td.value {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td.value-slider {
|
||||
width: 100%;
|
||||
}
|
||||
ul#navbar li div.menu table.kv td.value-number {
|
||||
font-weight: bold;
|
||||
max-width: 310px;
|
||||
overflow: hidden;
|
||||
min-width: 40px;
|
||||
max-width: 40px;
|
||||
}
|
||||
|
||||
ul#navbar li div.menu div.buttons button,
|
||||
ul#navbar li div.menu div.buttons select {
|
||||
border-radius: 0;
|
||||
@@ -222,3 +198,33 @@ ul#navbar li div.menu img.sign {
|
||||
margin-right: 10px;
|
||||
height: 20px;
|
||||
}
|
||||
|
||||
ul.navbar-bg-tips {
|
||||
list-style-type: none;
|
||||
top: 50px;
|
||||
position: fixed;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
font-size: 0.7em;
|
||||
line-height: 1.5em;
|
||||
color: var(--cs-page-obscure-fg);
|
||||
z-index: -10;
|
||||
}
|
||||
ul.navbar-bg-tips li {
|
||||
padding: 0 10px;
|
||||
max-width: 20%;
|
||||
}
|
||||
ul.navbar-bg-tips li pre {
|
||||
word-break: break-word;
|
||||
white-space: break-spaces;
|
||||
text-align: justify;
|
||||
}
|
||||
ul.navbar-bg-tips li.left {
|
||||
float: left;
|
||||
}
|
||||
ul.navbar-bg-tips li.right {
|
||||
float: right;
|
||||
}
|
||||
ul.navbar-bg-tips li a {
|
||||
color: var(--cs-page-obscure-fg);
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
/* ===== main.css ===== */
|
||||
|
||||
button:enabled:hover,
|
||||
select:enabled:hover,
|
||||
select:not([size]):enabled:hover,
|
||||
input[type=file]:enabled:hover::-webkit-file-selector-button,
|
||||
input[type=file]:enabled:hover::file-selector-button {
|
||||
color: var(--cs-control-hovered-fg);
|
||||
@@ -31,7 +31,7 @@ input[type=file]:enabled:hover::file-selector-button {
|
||||
}
|
||||
|
||||
button:active,
|
||||
select:active,
|
||||
select:not([size]):active,
|
||||
input[type=file]:active::-webkit-file-selector-button,
|
||||
input[type=file]:active::file-selector-button {
|
||||
color: var(--cs-control-pressed-fg) !important;
|
||||
@@ -42,7 +42,7 @@ button.key:active,
|
||||
select.key:active {
|
||||
box-shadow: none;
|
||||
}
|
||||
select:enabled:hover {
|
||||
select:not([size]):enabled:hover {
|
||||
background-image: url("../svg/select-arrow-intensive.svg") !important;
|
||||
}
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@
|
||||
"kvm_text80":"Record video using the browser API, and will be downloaded automatically",
|
||||
"kvm_text81":"Start recording",
|
||||
"kvm_text82":"End recording",
|
||||
"kvm_text83":"Web UI settings",
|
||||
|
||||
"atx-ask-switch":"Ask click confirmation",
|
||||
"hid-recorder-loop-switch":"Infinite loop playback",
|
||||
@@ -140,5 +141,6 @@
|
||||
"msd-message-out-of-storage":"Current image is out of storage",
|
||||
"msd-message-rw-enabled":"Read-write mode is enabled",
|
||||
"msd-message-downloads":"The image is being downloaded from One-KVM",
|
||||
"msd-message-another-user-uploads":"Another user uploads an image"
|
||||
"msd-message-another-user-uploads":"Another user uploads an image",
|
||||
"page-full-tab-stream-switch":"Expand for the entire tab by default"
|
||||
}
|
||||
@@ -113,6 +113,7 @@
|
||||
"kvm_text80":"使用浏览器 API 录制视频,结束录制后视频文件会自动下载",
|
||||
"kvm_text81":"开始录制",
|
||||
"kvm_text82":"结束录制",
|
||||
"kvm_text83":"网页界面设置",
|
||||
|
||||
"atx-ask-switch":"点击二次确认",
|
||||
"hid-recorder-loop-switch":"无限循环重放",
|
||||
@@ -140,5 +141,6 @@
|
||||
"msd-message-out-of-storage":"当前镜像大小超出存储空间",
|
||||
"msd-message-rw-enabled":"读写模式以启用",
|
||||
"msd-message-downloads":"正在从 One-KVM 下载镜像",
|
||||
"msd-message-another-user-uploads":"另一个用户正在上传镜像"
|
||||
"msd-message-another-user-uploads":"另一个用户正在上传镜像",
|
||||
"page-full-tab-stream-switch":"自动全屏视频窗口"
|
||||
}
|
||||
@@ -38,13 +38,13 @@ export function main() {
|
||||
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
tools.httpGet("/api/info?fields=auth,meta,extras", function(http) {
|
||||
tools.httpGet("/api/info", {"fields": "auth,meta,extras"}, function(http) {
|
||||
if (http.status === 200) {
|
||||
let info = JSON.parse(http.responseText).result;
|
||||
|
||||
let apps = [];
|
||||
if (info.extras === null) {
|
||||
wm.error("Not all applications in the menu can be displayed<br>due an error. See KVMD logs for details.");
|
||||
wm.error("Not all applications in the menu can be displayed due an error.<br>See KVMD logs for details.");
|
||||
} else {
|
||||
apps = Object.values(info.extras).sort(function(a, b) {
|
||||
if (a.place < b.place) {
|
||||
@@ -100,7 +100,7 @@ function __makeApp(id, path, icon, name) {
|
||||
<a href="${path}">
|
||||
<div>
|
||||
<img class="svg-gray" src="${icon}">
|
||||
${name}
|
||||
${tools.escape(name)}
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
@@ -108,11 +108,11 @@ function __makeApp(id, path, icon, name) {
|
||||
}
|
||||
|
||||
function __logout() {
|
||||
tools.httpPost("/api/auth/logout", function(http) {
|
||||
tools.httpPost("/api/auth/logout", null, function(http) {
|
||||
if (http.status === 200 || http.status === 401 || http.status === 403) {
|
||||
document.location.href = "/login";
|
||||
} else {
|
||||
wm.error("Logout error:<br>", http.responseText);
|
||||
wm.error("Logout error", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ export function main() {
|
||||
}
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
tools.httpGet("/api/info", function(http) {
|
||||
tools.httpGet("/api/info", null, function(http) {
|
||||
if (http.status === 200) {
|
||||
let ipmi_port = JSON.parse(http.responseText).result.extras.ipmi.port;
|
||||
let make_item = (comment, ipmi, api) => `
|
||||
|
||||
@@ -32,59 +32,77 @@ export function Atx(__recorder) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
|
||||
var __init__ = function() {
|
||||
$("atx-power-led").title = "Power Led";
|
||||
$("atx-hdd-led").title = "Disk Activity Led";
|
||||
|
||||
tools.storage.bindSimpleSwitch($("atx-ask-switch"), "atx.ask", true);
|
||||
|
||||
for (let args of [
|
||||
["atx-power-button", "power", "Are you sure you want to press the power button?"],
|
||||
["atx-power-button-long", "power_long", `
|
||||
Are you sure you want to long press the power button?<br>
|
||||
Warning! This could cause data loss on the server.
|
||||
`],
|
||||
["atx-reset-button", "reset", `
|
||||
Are you sure you want to press the reset button?<br>
|
||||
Warning! This could case data loss on the server.
|
||||
`],
|
||||
]) {
|
||||
tools.el.setOnClick($(args[0]), () => __clickButton(args[1], args[2]));
|
||||
}
|
||||
tools.el.setOnClick($("atx-power-button"), () => __clickAtx("power"));
|
||||
tools.el.setOnClick($("atx-power-button-long"), () => __clickAtx("power_long"));
|
||||
tools.el.setOnClick($("atx-reset-button"), () => __clickAtx("reset"));
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
let buttons_enabled = false;
|
||||
if (state) {
|
||||
tools.feature.setEnabled($("atx-dropdown"), state.enabled);
|
||||
$("atx-power-led").className = (state.busy ? "led-yellow" : (state.leds.power ? "led-green" : "led-gray"));
|
||||
$("atx-hdd-led").className = (state.leds.hdd ? "led-red" : "led-gray");
|
||||
buttons_enabled = !state.busy;
|
||||
if (!__state) {
|
||||
__state = {"leds": {}};
|
||||
}
|
||||
if (state.enabled !== undefined) {
|
||||
__state.enabled = state.enabled;
|
||||
tools.feature.setEnabled($("atx-dropdown"), __state.enabled);
|
||||
}
|
||||
if (__state.enabled !== undefined) {
|
||||
if (state.busy !== undefined) {
|
||||
__state.busy = state.busy;
|
||||
__updateButtons(!__state.busy);
|
||||
}
|
||||
if (state.leds !== undefined) {
|
||||
__state.leds = state.leds;
|
||||
}
|
||||
if (state.busy !== undefined || state.leds !== undefined) {
|
||||
__updateLeds(__state.leds.power, __state.leds.hdd, __state.busy);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$("atx-power-led").className = "led-gray";
|
||||
$("atx-hdd-led").className = "led-gray";
|
||||
}
|
||||
for (let id of ["atx-power-button", "atx-power-button-long", "atx-reset-button"]) {
|
||||
tools.el.setEnabled($(id), buttons_enabled);
|
||||
__state = null;
|
||||
__updateLeds(false, false, false);
|
||||
__updateButtons(false);
|
||||
}
|
||||
};
|
||||
|
||||
var __clickButton = function(button, confirm_msg) {
|
||||
var __updateLeds = function(power, hdd, busy) {
|
||||
$("atx-power-led").className = (busy ? "led-yellow" : (power ? "led-green" : "led-gray"));
|
||||
$("atx-hdd-led").className = (hdd ? "led-red" : "led-gray");
|
||||
};
|
||||
|
||||
var __updateButtons = function(enabled) {
|
||||
for (let id of ["atx-power-button", "atx-power-button-long", "atx-reset-button"]) {
|
||||
tools.el.setEnabled($(id), enabled);
|
||||
}
|
||||
};
|
||||
|
||||
var __clickAtx = function(button) {
|
||||
let click_button = function() {
|
||||
tools.httpPost(`/api/atx/click?button=${button}`, function(http) {
|
||||
tools.httpPost("/api/atx/click", {"button": button}, function(http) {
|
||||
if (http.status === 409) {
|
||||
wm.error("Performing another ATX operation for other client.<br>Please try again later");
|
||||
wm.error("Performing another ATX operation for other client.<br>Please try again later.");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("Click error:<br>", http.responseText);
|
||||
wm.error("Click error", http.responseText);
|
||||
}
|
||||
});
|
||||
__recorder.recordAtxButtonEvent(button);
|
||||
};
|
||||
|
||||
if ($("atx-ask-switch").checked) {
|
||||
wm.confirm(confirm_msg).then(function(ok) {
|
||||
wm.confirm(`
|
||||
Are you sure you want to press the <b>${button}</b> button?<br>
|
||||
Warning! This could case data loss on the server.
|
||||
`).then(function(ok) {
|
||||
if (ok) {
|
||||
click_button();
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
"use strict";
|
||||
|
||||
|
||||
import {tools, $, $$$} from "../tools.js";
|
||||
import {tools, $, $$} from "../tools.js";
|
||||
import {wm} from "../wm.js";
|
||||
|
||||
|
||||
@@ -32,44 +32,59 @@ export function Gpio(__recorder) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
var __has_model = false;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
if (state) {
|
||||
for (let channel in state.inputs) {
|
||||
let el = $(`gpio-led-${channel}`);
|
||||
if (el) {
|
||||
__setLedState(el, state.inputs[channel].state);
|
||||
}
|
||||
if (state.model !== undefined) {
|
||||
__has_model = true;
|
||||
__updateModel(state.model);
|
||||
}
|
||||
for (let channel in state.outputs) {
|
||||
for (let type of ["switch", "button"]) {
|
||||
let el = $(`gpio-${type}-${channel}`);
|
||||
if (el) {
|
||||
tools.el.setEnabled(el, state.outputs[channel].online && !state.outputs[channel].busy);
|
||||
}
|
||||
if (__has_model && state.state !== undefined) {
|
||||
if (state.state.inputs !== undefined) {
|
||||
__updateInputs(state.state.inputs);
|
||||
}
|
||||
let el = $(`gpio-switch-${channel}`);
|
||||
if (el) {
|
||||
el.checked = state.outputs[channel].state;
|
||||
if (state.state.outputs !== undefined) {
|
||||
__updateOutputs(state.state.outputs);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (let el of $$$(".gpio-led")) {
|
||||
__has_model = false;
|
||||
for (let el of $$("__gpio-led")) {
|
||||
__setLedState(el, false);
|
||||
}
|
||||
for (let selector of [".gpio-switch", ".gpio-button"]) {
|
||||
for (let el of $$$(selector)) {
|
||||
for (let selector of ["__gpio-switch", "__gpio-button"]) {
|
||||
for (let el of $$(selector)) {
|
||||
tools.el.setEnabled(el, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
__state = state;
|
||||
};
|
||||
|
||||
self.setModel = function(model) {
|
||||
var __updateInputs = function(inputs) {
|
||||
for (let ch in inputs) {
|
||||
for (let el of $$(`__gpio-led-${ch}`)) {
|
||||
__setLedState(el, inputs[ch].state);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __updateOutputs = function(outputs) {
|
||||
for (let ch in outputs) {
|
||||
for (let type of ["switch", "button"]) {
|
||||
for (let el of $$(`__gpio-${type}-${ch}`)) {
|
||||
tools.el.setEnabled(el, (outputs[ch].online && !outputs[ch].busy));
|
||||
}
|
||||
}
|
||||
for (let el of $$(`__gpio-switch-${ch}`)) {
|
||||
el.checked = outputs[ch].state;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __updateModel = function(model) {
|
||||
tools.feature.setEnabled($("gpio-dropdown"), model.view.table.length);
|
||||
if (model.view.table.length) {
|
||||
let title = [];
|
||||
@@ -84,44 +99,36 @@ export function Gpio(__recorder) {
|
||||
$("gpio-menu-button").innerHTML = title.join(" ");
|
||||
}
|
||||
|
||||
let content = "<table class=\"kv\">";
|
||||
let html = "<table class=\"kv\">";
|
||||
for (let row of model.view.table) {
|
||||
if (row === null) {
|
||||
content += "</table><hr><table class=\"kv\">";
|
||||
html += "</table><hr><table class=\"kv\">";
|
||||
} else {
|
||||
content += "<tr>";
|
||||
html += "<tr>";
|
||||
for (let item of row) {
|
||||
if (item.type === "output") {
|
||||
item.scheme = model.scheme.outputs[item.channel];
|
||||
}
|
||||
content += `<td align="center">${__createItem(item)}</td>`;
|
||||
html += `<td align="center">${__createItem(item)}</td>`;
|
||||
}
|
||||
content += "</tr>";
|
||||
html += "</tr>";
|
||||
}
|
||||
}
|
||||
content += "</table>";
|
||||
$("gpio-menu").innerHTML = content;
|
||||
html += "</table>";
|
||||
$("gpio-menu").innerHTML = html;
|
||||
|
||||
for (let channel in model.scheme.outputs) {
|
||||
let el = $(`gpio-switch-${channel}`);
|
||||
if (el) {
|
||||
tools.el.setOnClick(el, __createAction(el, __switchChannel));
|
||||
for (let ch in model.scheme.outputs) {
|
||||
for (let el of $$(`__gpio-switch-${ch}`)) {
|
||||
tools.el.setOnClick(el, tools.partial(__switchChannel, el));
|
||||
}
|
||||
el = $(`gpio-button-${channel}`);
|
||||
if (el) {
|
||||
tools.el.setOnClick(el, __createAction(el, __pulseChannel));
|
||||
for (let el of $$(`__gpio-button-${ch}`)) {
|
||||
tools.el.setOnClick(el, tools.partial(__pulseChannel, el));
|
||||
}
|
||||
}
|
||||
|
||||
tools.feature.setEnabled($("v3-usb-breaker"), ("__v3_usb_breaker__" in model.scheme.outputs));
|
||||
tools.feature.setEnabled($("v4-locator"), ("__v4_locator__" in model.scheme.outputs));
|
||||
tools.feature.setEnabled($("system-tool-wol"), ("__wol__" in model.scheme.outputs));
|
||||
|
||||
self.setState(__state);
|
||||
};
|
||||
|
||||
var __createAction = function(el, action) {
|
||||
return () => action(el);
|
||||
};
|
||||
|
||||
var __createItem = function(item) {
|
||||
@@ -129,18 +136,28 @@ export function Gpio(__recorder) {
|
||||
return item.text;
|
||||
} else if (item.type === "input") {
|
||||
return `
|
||||
<img id="gpio-led-${item.channel}" class="gpio-led inline-lamp-big led-gray"
|
||||
src="/share/svg/led-circle.svg" data-color="${item.color}" />
|
||||
<img
|
||||
class="__gpio-led __gpio-led-${item.channel} inline-lamp-big led-gray"
|
||||
src="/share/svg/led-circle.svg"
|
||||
data-color="${item.color}"
|
||||
/>
|
||||
`;
|
||||
} else if (item.type === "output") {
|
||||
let controls = [];
|
||||
let confirm = (item.confirm ? "Are you sure you want to perform this action?" : "");
|
||||
if (item.scheme["switch"]) {
|
||||
let id = tools.makeId();
|
||||
controls.push(`
|
||||
<td><div class="switch-box">
|
||||
<input disabled type="checkbox" id="gpio-switch-${item.channel}" class="gpio-switch"
|
||||
data-channel="${item.channel}" data-confirm="${confirm}" />
|
||||
<label for="gpio-switch-${item.channel}">
|
||||
<input
|
||||
disabled
|
||||
type="checkbox"
|
||||
id="__gpio-switch-${id}"
|
||||
class="__gpio-switch __gpio-switch-${item.channel}"
|
||||
data-channel="${item.channel}"
|
||||
data-confirm="${confirm}"
|
||||
/>
|
||||
<label for="__gpio-switch-${id}">
|
||||
<span class="switch-inner"></span>
|
||||
<span class="switch"></span>
|
||||
</label>
|
||||
@@ -149,10 +166,14 @@ export function Gpio(__recorder) {
|
||||
}
|
||||
if (item.scheme.pulse.delay) {
|
||||
controls.push(`
|
||||
<td><button disabled id="gpio-button-${item.channel}" class="gpio-button"
|
||||
${item.hide ? "data-force-hide-menu" : ""}
|
||||
data-channel="${item.channel}" data-confirm="${confirm}">
|
||||
${(item.hide ? "• " : "") + item.text}
|
||||
<td><button
|
||||
disabled
|
||||
class="__gpio-button __gpio-button-${item.channel}"
|
||||
${item.hide ? "data-force-hide-menu" : ""}
|
||||
data-channel="${item.channel}"
|
||||
data-confirm="${confirm}"
|
||||
>
|
||||
${(item.hide ? "• " : "") + item.text}
|
||||
</button></td>
|
||||
`);
|
||||
}
|
||||
@@ -162,9 +183,9 @@ export function Gpio(__recorder) {
|
||||
}
|
||||
};
|
||||
|
||||
var __setLedState = function(el, state) {
|
||||
var __setLedState = function(el, on) {
|
||||
let color = el.getAttribute("data-color");
|
||||
if (state) {
|
||||
if (on) {
|
||||
el.classList.add(`led-${color}`);
|
||||
el.classList.remove("led-gray");
|
||||
} else {
|
||||
@@ -174,22 +195,20 @@ export function Gpio(__recorder) {
|
||||
};
|
||||
|
||||
var __switchChannel = function(el) {
|
||||
let channel = el.getAttribute("data-channel");
|
||||
let ch = el.getAttribute("data-channel");
|
||||
let confirm = el.getAttribute("data-confirm");
|
||||
let to = ($(`gpio-switch-${channel}`).checked ? "1" : "0");
|
||||
let to = (el.checked ? "1" : "0");
|
||||
if (to === "0" && el.hasAttribute("data-confirm-off")) {
|
||||
confirm = el.getAttribute("data-confirm-off");
|
||||
}
|
||||
let act = () => {
|
||||
__sendPost(`/api/gpio/switch?channel=${channel}&state=${to}`);
|
||||
__recorder.recordGpioSwitchEvent(channel, to);
|
||||
__sendPost("/api/gpio/switch", {"channel": ch, "state": to});
|
||||
__recorder.recordGpioSwitchEvent(ch, to);
|
||||
};
|
||||
if (confirm) {
|
||||
wm.confirm(confirm).then(function(ok) {
|
||||
wm.confirm(tools.escape(confirm)).then(function(ok) {
|
||||
if (ok) {
|
||||
act();
|
||||
} else {
|
||||
self.setState(__state); // Switch back
|
||||
}
|
||||
});
|
||||
} else {
|
||||
@@ -198,25 +217,29 @@ export function Gpio(__recorder) {
|
||||
};
|
||||
|
||||
var __pulseChannel = function(el) {
|
||||
let channel = el.getAttribute("data-channel");
|
||||
let ch = el.getAttribute("data-channel");
|
||||
let confirm = el.getAttribute("data-confirm");
|
||||
let act = () => {
|
||||
__sendPost(`/api/gpio/pulse?channel=${channel}`);
|
||||
__recorder.recordGpioPulseEvent(channel);
|
||||
__sendPost("/api/gpio/pulse", {"channel": ch});
|
||||
__recorder.recordGpioPulseEvent(ch);
|
||||
};
|
||||
if (confirm) {
|
||||
wm.confirm(confirm).then(function(ok) { if (ok) act(); });
|
||||
wm.confirm(tools.escape(confirm)).then(function(ok) {
|
||||
if (ok) {
|
||||
act();
|
||||
}
|
||||
});
|
||||
} else {
|
||||
act();
|
||||
}
|
||||
};
|
||||
|
||||
var __sendPost = function(url) {
|
||||
tools.httpPost(url, function(http) {
|
||||
var __sendPost = function(url, params) {
|
||||
tools.httpPost(url, params, function(http) {
|
||||
if (http.status === 409) {
|
||||
wm.error("Performing another operation for this GPIO channel.<br>Please try again later");
|
||||
wm.error("Performing another operation for this GPIO channel.<br>Please try again later.");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("GPIO error:<br>", http.responseText);
|
||||
wm.error("GPIO error", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
@@ -35,6 +35,7 @@ export function Hid(__getGeometry, __recorder) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __state = null;
|
||||
var __keyboard = null;
|
||||
var __mouse = null;
|
||||
|
||||
@@ -71,21 +72,6 @@ export function Hid(__getGeometry, __recorder) {
|
||||
window.addEventListener("pagehide", __releaseAll);
|
||||
window.addEventListener("blur", __releaseAll);
|
||||
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-ask-switch"), "hid.pak.ask", true);
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-secure-switch"), "hid.pak.secure", false, function(value) {
|
||||
$("hid-pak-text").style.setProperty("-webkit-text-security", (value ? "disc" : "none"));
|
||||
});
|
||||
tools.feature.setEnabled($("hid-pak-secure"), (
|
||||
tools.browser.is_chrome
|
||||
|| tools.browser.is_safari
|
||||
|| tools.browser.is_opera
|
||||
));
|
||||
|
||||
$("hid-pak-keymap-selector").addEventListener("change", function() {
|
||||
tools.storage.set("hid.pak.keymap", $("hid-pak-keymap-selector").value);
|
||||
});
|
||||
|
||||
tools.el.setOnClick($("hid-pak-button"), __clickPasteAsKeysButton);
|
||||
tools.el.setOnClick($("hid-connect-switch"), __clickConnectSwitch);
|
||||
tools.el.setOnClick($("hid-reset-button"), __clickResetButton);
|
||||
|
||||
@@ -98,8 +84,7 @@ export function Hid(__getGeometry, __recorder) {
|
||||
}
|
||||
let codes = el_shortcut.getAttribute("data-shortcut").split(" ");
|
||||
if (ask) {
|
||||
let confirm_msg = `Do you want to press <b>${codes.join(" + ")}</b>?`;
|
||||
wm.confirm(confirm_msg).then(function(ok) {
|
||||
wm.confirm("Do you want to press this hotkey?", codes.join(" + ")).then(function(ok) {
|
||||
if (ok) {
|
||||
__emitShortcut(codes);
|
||||
}
|
||||
@@ -118,10 +103,6 @@ export function Hid(__getGeometry, __recorder) {
|
||||
/************************************************************************/
|
||||
|
||||
self.setSocket = function(ws) {
|
||||
tools.el.setEnabled($("hid-pak-text"), ws);
|
||||
tools.el.setEnabled($("hid-pak-button"), ws);
|
||||
tools.el.setEnabled($("hid-reset-button"), ws);
|
||||
tools.el.setEnabled($("hid-jiggler-switch"), ws);
|
||||
if (!ws) {
|
||||
self.setState(null);
|
||||
}
|
||||
@@ -130,84 +111,135 @@ export function Hid(__getGeometry, __recorder) {
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
let has_relative_squash = false;
|
||||
|
||||
if (state) {
|
||||
tools.feature.setEnabled($("hid-jiggler"), state.jiggler.enabled);
|
||||
$("hid-jiggler-switch").checked = state.jiggler.active;
|
||||
}
|
||||
if (state && state.online) {
|
||||
let keyboard_outputs = state.keyboard.outputs.available;
|
||||
let mouse_outputs = state.mouse.outputs.available;
|
||||
if (keyboard_outputs.length) {
|
||||
if ($("hid-outputs-keyboard-box").outputs !== keyboard_outputs) {
|
||||
let html = "";
|
||||
for (let args of [
|
||||
["USB", "usb"],
|
||||
["PS/2", "ps2"],
|
||||
["Off", "disabled"],
|
||||
]) {
|
||||
if (keyboard_outputs.includes(args[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-keyboard-radio", args[0], args[1]);
|
||||
}
|
||||
}
|
||||
$("hid-outputs-keyboard-box").innerHTML = html;
|
||||
$("hid-outputs-keyboard-box").outputs = keyboard_outputs;
|
||||
tools.radio.setOnClick("hid-outputs-keyboard-radio", () => __clickOutputsRadio("keyboard"));
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-keyboard-radio", state.keyboard.outputs.active);
|
||||
if (!__state) {
|
||||
__state = {"keyboard": {}, "mouse": {}};
|
||||
}
|
||||
let has_relative = false;
|
||||
if (mouse_outputs.length) {
|
||||
if ($("hid-outputs-mouse-box").outputs !== mouse_outputs) {
|
||||
let html = "";
|
||||
for (let args of [
|
||||
["Absolute", "usb", false],
|
||||
["Abs-Win98", "usb_win98", false],
|
||||
["Relative", "usb_rel", true],
|
||||
["PS/2", "ps2", true],
|
||||
["Off", "disabled"],
|
||||
]) {
|
||||
if (mouse_outputs.includes(args[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-mouse-radio", args[0], args[1]);
|
||||
has_relative = (has_relative || args[2]);
|
||||
}
|
||||
}
|
||||
$("hid-outputs-mouse-box").innerHTML = html;
|
||||
$("hid-outputs-mouse-box").outputs = mouse_outputs;
|
||||
tools.radio.setOnClick("hid-outputs-mouse-radio", () => __clickOutputsRadio("mouse"));
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-mouse-radio", state.mouse.outputs.active);
|
||||
has_relative_squash = ["usb_rel", "ps2"].includes(state.mouse.outputs.active);
|
||||
} else {
|
||||
has_relative = !state.mouse.absolute;
|
||||
has_relative_squash = has_relative;
|
||||
if (state.enabled !== undefined) {
|
||||
__state.enabled = state.enabled; // Currently unused, always true
|
||||
}
|
||||
tools.feature.setEnabled($("hid-outputs"), (keyboard_outputs.length || mouse_outputs.length));
|
||||
tools.feature.setEnabled($("hid-outputs-keyboard"), keyboard_outputs.length);
|
||||
tools.feature.setEnabled($("hid-outputs-mouse"), mouse_outputs.length);
|
||||
tools.feature.setEnabled($("hid-mouse-squash"), has_relative);
|
||||
tools.feature.setEnabled($("hid-mouse-sens"), has_relative);
|
||||
tools.feature.setEnabled($("hid-connect"), (state.connected !== null));
|
||||
$("hid-connect-switch").checked = !!state.connected;
|
||||
}
|
||||
|
||||
tools.radio.setEnabled("hid-outputs-keyboard-radio", (state && state.online && !state.busy));
|
||||
tools.radio.setEnabled("hid-outputs-mouse-radio", (state && state.online && !state.busy));
|
||||
tools.el.setEnabled($("hid-mouse-squash-switch"), (has_relative_squash && !state.busy));
|
||||
tools.el.setEnabled($("hid-mouse-sens-slider"), (has_relative_squash && !state.busy));
|
||||
tools.el.setEnabled($("hid-connect-switch"), (state && state.online && !state.busy));
|
||||
|
||||
if (state) {
|
||||
__keyboard.setState(state.keyboard, state.online, state.busy);
|
||||
__mouse.setState(state.mouse, state.online, state.busy);
|
||||
if (__state.enabled !== undefined) {
|
||||
for (let key of ["online", "busy", "connected", "jiggler"]) {
|
||||
if (state[key] !== undefined) {
|
||||
__state[key] = state[key];
|
||||
}
|
||||
}
|
||||
for (let hid of ["keyboard", "mouse"]) {
|
||||
if (state[hid] === undefined) {
|
||||
state[hid] = {}; // Add some stubs for processing
|
||||
}
|
||||
for (let key of ["online", "outputs", (hid === "keyboard" ? "leds" : "absolute")]) {
|
||||
__state[hid][key] = state[hid][key];
|
||||
}
|
||||
}
|
||||
if (state.connected !== undefined) {
|
||||
tools.feature.setEnabled($("hid-connect"), (__state.connected !== null));
|
||||
$("hid-connect-switch").checked = !!__state.connected;
|
||||
}
|
||||
if (state.jiggler !== undefined) {
|
||||
tools.feature.setEnabled($("hid-jiggler"), __state.jiggler.enabled);
|
||||
$("hid-jiggler-switch").checked = __state.jiggler.active;
|
||||
}
|
||||
if (state.keyboard.outputs !== undefined) {
|
||||
__updateKeyboardOutputs(__state.keyboard.outputs);
|
||||
}
|
||||
if (state.mouse.outputs !== undefined) {
|
||||
__updateMouseOutputs(__state.mouse.outputs, __state.mouse.absolute); // Follows together
|
||||
}
|
||||
if (
|
||||
state.keyboard.online !== undefined || state.keyboard.leds !== undefined
|
||||
|| state.online !== undefined || state.busy !== undefined
|
||||
) {
|
||||
__keyboard.setState(__state.keyboard.online, __state.keyboard.leds, __state.online, __state.busy);
|
||||
}
|
||||
if (
|
||||
state.mouse.online !== undefined || state.mouse.absolute !== undefined
|
||||
|| state.online !== undefined || state.busy !== undefined
|
||||
) {
|
||||
__mouse.setState(__state.mouse.online, __state.mouse.absolute, __state.online, __state.busy);
|
||||
}
|
||||
if (state.online !== undefined || state.busy !== undefined) {
|
||||
tools.radio.setEnabled("hid-outputs-keyboard-radio", (__state.online && !__state.busy));
|
||||
tools.radio.setEnabled("hid-outputs-mouse-radio", (__state.online && !__state.busy));
|
||||
tools.el.setEnabled($("hid-connect-switch"), (__state.online && !__state.busy));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__state = null;
|
||||
tools.radio.setEnabled("hid-outputs-keyboard-radio", false);
|
||||
tools.radio.setEnabled("hid-outputs-mouse-radio", false);
|
||||
tools.el.setEnabled($("hid-connect-switch"), false);
|
||||
tools.el.setEnabled($("hid-mouse-squash-switch"), false);
|
||||
tools.el.setEnabled($("hid-mouse-sens-slider"), false);
|
||||
}
|
||||
tools.el.setEnabled($("hid-reset-button"), __state);
|
||||
tools.el.setEnabled($("hid-jiggler-switch"), __state);
|
||||
};
|
||||
|
||||
self.setKeymaps = function(state) {
|
||||
let el = $("hid-pak-keymap-selector");
|
||||
tools.selector.setValues(el, state.keymaps.available);
|
||||
tools.selector.setSelectedValue(el, tools.storage.get("hid.pak.keymap", state.keymaps["default"]));
|
||||
var __updateKeyboardOutputs = function(outputs) {
|
||||
let avail = outputs.available;
|
||||
if (avail.length > 0) {
|
||||
let el = $("hid-outputs-keyboard-box");
|
||||
let avail_json = JSON.stringify(avail);
|
||||
if (el.__avail_json !== avail_json) {
|
||||
let html = "";
|
||||
for (let pair of [
|
||||
["USB", "usb"],
|
||||
["PS/2", "ps2"],
|
||||
["Off", "disabled"],
|
||||
]) {
|
||||
if (avail.includes(pair[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-keyboard-radio", pair[0], pair[1]);
|
||||
}
|
||||
}
|
||||
el.innerHTML = html;
|
||||
tools.radio.setOnClick("hid-outputs-keyboard-radio", () => __clickOutputsRadio("keyboard"));
|
||||
el.__avail_json = avail_json;
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-keyboard-radio", outputs.active);
|
||||
}
|
||||
tools.feature.setEnabled($("hid-outputs-keyboard"), (avail.length > 0));
|
||||
};
|
||||
|
||||
var __updateMouseOutputs = function(outputs, absolute) {
|
||||
let has_relative = null;
|
||||
let has_relative_squash = null;
|
||||
let avail = outputs.available;
|
||||
if (avail.length > 0) {
|
||||
let el = $("hid-outputs-mouse-box");
|
||||
let avail_json = JSON.stringify(avail);
|
||||
if (el.__avail_json !== avail_json) {
|
||||
has_relative = false;
|
||||
let html = "";
|
||||
for (let pair of [
|
||||
["Absolute", "usb", false],
|
||||
["Abs-Win98", "usb_win98", false],
|
||||
["Relative", "usb_rel", true],
|
||||
["PS/2", "ps2", true],
|
||||
["Off", "disabled", false],
|
||||
]) {
|
||||
if (avail.includes(pair[1])) {
|
||||
html += tools.radio.makeItem("hid-outputs-mouse-radio", pair[0], pair[1]);
|
||||
has_relative = (has_relative || pair[2]);
|
||||
}
|
||||
}
|
||||
el.innerHTML = html;
|
||||
tools.radio.setOnClick("hid-outputs-mouse-radio", () => __clickOutputsRadio("mouse"));
|
||||
el.__avail_json = avail_json;
|
||||
}
|
||||
tools.radio.setValue("hid-outputs-mouse-radio", outputs.active);
|
||||
has_relative_squash = (["usb_rel", "ps2"].includes(outputs.active));
|
||||
} else {
|
||||
has_relative = !absolute;
|
||||
has_relative_squash = has_relative;
|
||||
}
|
||||
if (has_relative !== null) {
|
||||
tools.feature.setEnabled($("hid-mouse-squash"), has_relative);
|
||||
tools.feature.setEnabled($("hid-mouse-sens"), has_relative);
|
||||
}
|
||||
tools.feature.setEnabled($("hid-outputs-mouse"), (avail.length > 0));
|
||||
tools.el.setEnabled($("hid-mouse-squash-switch"), has_relative_squash);
|
||||
tools.el.setEnabled($("hid-mouse-sens-slider"), has_relative_squash);
|
||||
};
|
||||
|
||||
var __releaseAll = function() {
|
||||
@@ -241,72 +273,29 @@ export function Hid(__getGeometry, __recorder) {
|
||||
});
|
||||
};
|
||||
|
||||
var __clickPasteAsKeysButton = function() {
|
||||
let text = $("hid-pak-text").value;
|
||||
if (text) {
|
||||
let paste_as_keys = function() {
|
||||
tools.el.setEnabled($("hid-pak-text"), false);
|
||||
tools.el.setEnabled($("hid-pak-button"), false);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), false);
|
||||
|
||||
let keymap = $("hid-pak-keymap-selector").value;
|
||||
|
||||
tools.debug(`HID: paste-as-keys ${keymap}: ${text}`);
|
||||
|
||||
tools.httpPost(`/api/hid/print?limit=0&keymap=${keymap}`, function(http) {
|
||||
tools.el.setEnabled($("hid-pak-text"), true);
|
||||
tools.el.setEnabled($("hid-pak-button"), true);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), true);
|
||||
$("hid-pak-text").value = "";
|
||||
if (http.status === 413) {
|
||||
wm.error("Too many text for paste!");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("HID paste error:<br>", http.responseText);
|
||||
} else if (http.status === 200) {
|
||||
__recorder.recordPrintEvent(text);
|
||||
}
|
||||
}, text, "text/plain");
|
||||
};
|
||||
|
||||
if ($("hid-pak-ask-switch").checked) {
|
||||
let confirm_msg = `You're going to paste ${text.length} character${text.length ? "s" : ""}.<br>`;
|
||||
confirm_msg += "Are you sure you want to continue?";
|
||||
wm.confirm(confirm_msg).then(function(ok) {
|
||||
if (ok) {
|
||||
paste_as_keys();
|
||||
} else {
|
||||
$("hid-pak-text").value = "";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
paste_as_keys();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __clickOutputsRadio = function(hid) {
|
||||
let output = tools.radio.getValue(`hid-outputs-${hid}-radio`);
|
||||
tools.httpPost(`/api/hid/set_params?${hid}_output=${output}`, function(http) {
|
||||
tools.httpPost("/api/hid/set_params", {[`${hid}_output`]: output}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't configure HID:<br>", http.responseText);
|
||||
wm.error("Can't configure HID", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __clickJigglerSwitch = function() {
|
||||
let enabled = $("hid-jiggler-switch").checked;
|
||||
tools.httpPost(`/api/hid/set_params?jiggler=${enabled}`, function(http) {
|
||||
tools.httpPost("/api/hid/set_params", {"jiggler": enabled}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error(`Can't ${enabled ? "enabled" : "disable"} mouse juggler:<br>`, http.responseText);
|
||||
wm.error(`Can't ${enabled ? "enabled" : "disable"} mouse jiggler`, http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __clickConnectSwitch = function() {
|
||||
let connected = $("hid-connect-switch").checked;
|
||||
tools.httpPost(`/api/hid/set_connected?connected=${connected}`, function(http) {
|
||||
tools.httpPost("/api/hid/set_connected", {"connected": connected}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error(`Can't ${connected ? "connect" : "disconnect"} HID:<br>`, http.responseText);
|
||||
wm.error(`Can't ${connected ? "connect" : "disconnect"} HID`, http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -314,9 +303,9 @@ export function Hid(__getGeometry, __recorder) {
|
||||
var __clickResetButton = function() {
|
||||
wm.confirm("Are you sure you want to reset HID (keyboard & mouse)?").then(function(ok) {
|
||||
if (ok) {
|
||||
tools.httpPost("/api/hid/reset", function(http) {
|
||||
tools.httpPost("/api/hid/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("HID reset error:<br>", http.responseText);
|
||||
wm.error("HID reset error", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -65,17 +65,17 @@ export function Keyboard(__recordWsEvent) {
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
self.setState = function(state, hid_online, hid_busy) {
|
||||
self.setState = function(online, leds, hid_online, hid_busy) {
|
||||
if (!hid_online) {
|
||||
__online = null;
|
||||
} else {
|
||||
__online = (state.online && !hid_busy);
|
||||
__online = (online && !hid_busy);
|
||||
}
|
||||
__updateOnlineLeds();
|
||||
|
||||
for (let led of ["caps", "scroll", "num"]) {
|
||||
for (let el of $$$(`.hid-keyboard-${led}-led`)) {
|
||||
if (state.leds[led]) {
|
||||
if (leds[led]) {
|
||||
el.classList.add("led-green");
|
||||
el.classList.remove("led-gray");
|
||||
} else {
|
||||
|
||||
@@ -50,9 +50,14 @@ export function main() {
|
||||
|
||||
tools.el.setOnClick($("open-log-button"), () => window.open("/api/log?seek=3600&follow=1", "_blank"));
|
||||
|
||||
if (tools.config.getBool("kvm--full-tab-stream", false)) {
|
||||
wm.toggleFullTabWindow($("stream-window"), true);
|
||||
tools.storage.bindSimpleSwitch(
|
||||
$("page-full-tab-stream-switch"),
|
||||
"page.full_tab_stream",
|
||||
tools.config.getBool("kvm--full-tab-stream", false));
|
||||
if ($("page-full-tab-stream-switch").checked) {
|
||||
wm.setFullTabWindow($("stream-window"), true);
|
||||
}
|
||||
|
||||
wm.showWindow($("stream-window"));
|
||||
|
||||
new Session();
|
||||
|
||||
@@ -90,20 +90,20 @@ export function Mouse(__getGeometry, __recordWsEvent) {
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
self.setState = function(state, hid_online, hid_busy) {
|
||||
self.setState = function(online, absolute, hid_online, hid_busy) {
|
||||
if (!hid_online) {
|
||||
__online = null;
|
||||
} else {
|
||||
__online = (state.online && !hid_busy);
|
||||
__online = (online && !hid_busy);
|
||||
}
|
||||
if (!__absolute && state.absolute && __isRelativeCaptured()) {
|
||||
if (!__absolute && absolute && __isRelativeCaptured()) {
|
||||
document.exitPointerLock();
|
||||
}
|
||||
if (__absolute && !state.absolute) {
|
||||
if (__absolute && !absolute) {
|
||||
__relative_deltas = [];
|
||||
__relative_touch_pos = null;
|
||||
}
|
||||
__absolute = state.absolute;
|
||||
__absolute = absolute;
|
||||
__updateOnlineLeds();
|
||||
};
|
||||
|
||||
|
||||
@@ -35,10 +35,6 @@ export function Msd() {
|
||||
var __state = null;
|
||||
var __http = null;
|
||||
|
||||
var __parts_names_json = "";
|
||||
var __parts_names_len = 0;
|
||||
var __parts = {};
|
||||
|
||||
var __init__ = function() {
|
||||
$("msd-led").title = "Unknown state";
|
||||
|
||||
@@ -68,8 +64,201 @@ export function Msd() {
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
__state = state;
|
||||
__applyState();
|
||||
if (state) {
|
||||
if (!__state) {
|
||||
__state = {"storage": {}};
|
||||
}
|
||||
if (state.enabled !== undefined) {
|
||||
__state.enabled = state.enabled;
|
||||
tools.feature.setEnabled($("msd-dropdown"), __state.enabled);
|
||||
}
|
||||
if (__state.enabled !== undefined) {
|
||||
if (state.online !== undefined) {
|
||||
__state.online = state.online;
|
||||
}
|
||||
if (state.busy !== undefined) {
|
||||
__state.busy = state.busy;
|
||||
}
|
||||
if (state.drive) { // Null on offline, ignore
|
||||
__state.drive = state.drive;
|
||||
}
|
||||
if (state.storage) { // Null on offline, ignore
|
||||
if (state.storage.parts !== undefined) {
|
||||
__state.storage.parts = state.storage.parts;
|
||||
__updateParts(__state.storage.parts);
|
||||
}
|
||||
if (state.storage.uploading !== undefined) {
|
||||
__state.storage.uploading = state.storage.uploading;
|
||||
__updateUploading(__state.storage.uploading);
|
||||
}
|
||||
if (state.storage.downloading !== undefined) {
|
||||
__state.storage.downloading = state.storage.downloading;
|
||||
}
|
||||
if (state.storage.images !== undefined) {
|
||||
__state.storage.images = state.storage.images;
|
||||
}
|
||||
}
|
||||
if (state.drive || (state.storage && state.storage.images !== undefined)) {
|
||||
__updateImageSelector(__state.drive, __state.storage.images);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
__state = null;
|
||||
}
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __refreshControls = function() {
|
||||
__updateControls(__state && (__state.online !== undefined) ? __state : null);
|
||||
};
|
||||
|
||||
var __updateControls = function(state) {
|
||||
let o = (state && state.online);
|
||||
let d = (state ? state.drive : null);
|
||||
let s = (state ? state.storage : null);
|
||||
let busy = !!(state && state.busy);
|
||||
|
||||
tools.hidden.setVisible($("msd-message-offline"), (state && !state.online));
|
||||
tools.hidden.setVisible($("msd-message-image-broken"), (o && d.image && !d.image.complete && !s.uploading));
|
||||
tools.hidden.setVisible($("msd-message-too-big-for-cdrom"), (o && d.cdrom && d.image && d.image.size >= 2359296000));
|
||||
tools.hidden.setVisible($("msd-message-out-of-storage"), (o && d.image && !d.image.in_storage));
|
||||
tools.hidden.setVisible($("msd-message-rw-enabled"), (o && d.rw));
|
||||
tools.hidden.setVisible($("msd-message-another-user-uploads"), (o && s.uploading && !__http));
|
||||
tools.hidden.setVisible($("msd-message-downloads"), (o && s.downloading));
|
||||
|
||||
tools.el.setEnabled($("msd-image-selector"), (o && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-download-button"), (o && d.image && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-remove-button"), (o && d.image && d.image.removable && !d.connected && !busy));
|
||||
|
||||
tools.radio.setEnabled("msd-mode-radio", (o && !d.connected && !busy));
|
||||
tools.radio.setValue("msd-mode-radio", `${Number(o && d.cdrom)}`);
|
||||
|
||||
tools.el.setEnabled($("msd-rw-switch"), (o && !d.connected && !busy));
|
||||
$("msd-rw-switch").checked = (o && d.rw);
|
||||
|
||||
tools.el.setEnabled($("msd-connect-button"), (o && d.image && !d.connected && !busy));
|
||||
tools.el.setEnabled($("msd-disconnect-button"), (o && d.connected && !busy));
|
||||
|
||||
tools.el.setEnabled($("msd-select-new-button"), (o && !d.connected && !__http && !busy));
|
||||
tools.el.setEnabled($("msd-upload-new-button"),
|
||||
(o && !d.connected && (tools.input.getFile($("msd-new-file")) || $("msd-new-url").value.length > 0) && !busy));
|
||||
tools.el.setEnabled($("msd-abort-new-button"), (o && __http));
|
||||
|
||||
tools.el.setEnabled($("msd-reset-button"), (state && state.enabled && !busy));
|
||||
|
||||
tools.el.setEnabled($("msd-new-file"), (o && !d.connected && !__http && !busy));
|
||||
tools.el.setEnabled($("msd-new-url"), (o && !d.connected && !__http && !busy));
|
||||
tools.el.setEnabled($("msd-new-part-selector"), (o && !d.connected && !__http && !busy));
|
||||
|
||||
if (o && s.uploading) {
|
||||
tools.hidden.setVisible($("msd-new-sub"), false);
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
}
|
||||
tools.hidden.setVisible($("msd-uploading-sub"), (o && s.uploading));
|
||||
tools.hidden.setVisible($("msd-new-tips"), (o && s.uploading && __http));
|
||||
|
||||
let led_cls = "led-gray";
|
||||
let msg = "Unavailable";
|
||||
if (o && d.connected) {
|
||||
led_cls = "led-green";
|
||||
msg = "Connected to Server";
|
||||
} else if (o && s.uploading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Uploading new image";
|
||||
} else if (o && s.downloading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Serving the image to download";
|
||||
} else if (o) { // Sic!
|
||||
msg = "Disconnected";
|
||||
}
|
||||
$("msd-led").className = led_cls;
|
||||
$("msd-status").innerText = $("msd-led").title = msg;
|
||||
};
|
||||
|
||||
var __updateUploading = function(uploading) {
|
||||
$("msd-uploading-name").innerText = (uploading ? uploading.name : "");
|
||||
$("msd-uploading-size").innerText = (uploading ? tools.formatSize(uploading.size) : "");
|
||||
if (uploading) {
|
||||
tools.progress.setPercentOf($("msd-uploading-progress"), uploading.size, uploading.written);
|
||||
}
|
||||
};
|
||||
|
||||
var __updateParts = function(parts) {
|
||||
let names = Object.keys(parts).sort();
|
||||
{
|
||||
let writable = names.filter(name => (name === "" || parts[name].writable));
|
||||
let writable_json = JSON.stringify(writable);
|
||||
let el = $("msd-new-part-selector");
|
||||
if (el.__writable_json !== writable_json) {
|
||||
let sel = (el.value || "");
|
||||
el.options.length = 0;
|
||||
for (let name of writable) {
|
||||
let title = (name || "\u2500 Internal \u2500");
|
||||
tools.selector.addOption(el, title, name, (name === sel));
|
||||
}
|
||||
tools.hidden.setVisible($("msd-new-part"), (writable.length > 1));
|
||||
el.__writable_json = writable_json;
|
||||
}
|
||||
}
|
||||
{
|
||||
let names_json = JSON.stringify(names);
|
||||
let el = $("msd-storages");
|
||||
if (el.__names_json !== names_json) {
|
||||
el.innerHTML = names.map(name => `
|
||||
<div class="text">
|
||||
<div id="__msd-storage-${tools.makeIdByText(name)}-progress" class="progress">
|
||||
<span class="progress-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
`).join("<hr>");
|
||||
el.__names_json = names_json;
|
||||
}
|
||||
}
|
||||
for (let name of names) {
|
||||
let part = parts[name];
|
||||
let title = (
|
||||
name === ""
|
||||
? `${names.length === 1 ? "Storage: %s" : "Internal storage: %s"}` // eslint-disable-line
|
||||
: `Storage [${name}${part.writable ? "]" : ", read-only]"}: %s` // eslint-disable-line
|
||||
);
|
||||
let id = `__msd-storage-${tools.makeIdByText(name)}-progress`;
|
||||
tools.progress.setSizeOf($(id), title, part.size, part.free);
|
||||
}
|
||||
};
|
||||
|
||||
var __updateImageSelector = function(drive, images) {
|
||||
let sel = "";
|
||||
let el = $("msd-image-selector");
|
||||
el.options.length = 1;
|
||||
for (let name of Object.keys(images).sort()) {
|
||||
tools.selector.addSeparator(el);
|
||||
tools.selector.addOption(el, name, name);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(images[name]));
|
||||
if (drive.image && drive.image.name === name && drive.image.in_storage) {
|
||||
sel = name;
|
||||
}
|
||||
}
|
||||
if (drive.image && !drive.image.in_storage) {
|
||||
sel = ".__external__"; // Just some magic name
|
||||
tools.selector.addOption(el, drive.image.name, sel);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(drive.image));
|
||||
}
|
||||
el.value = sel;
|
||||
};
|
||||
|
||||
var __makeImageSelectorInfo = function(image) {
|
||||
let text = `\xA0\xA0\xA0\xA0\xA0\u2570 ${tools.formatSize(image.size)}`;
|
||||
if (!image.complete) {
|
||||
text += ", broken";
|
||||
}
|
||||
if (image.in_storage !== undefined && !image.in_storage) {
|
||||
text += ", out of storage";
|
||||
}
|
||||
let ts = new Date(image.mod_ts * 1000);
|
||||
ts = new Date(ts.getTime() - (ts.getTimezoneOffset() * 60000));
|
||||
ts = ts.toISOString().slice(0, -8).replaceAll("-", ".").replace("T", "-");
|
||||
return `${text} \u2500 ${ts}`;
|
||||
};
|
||||
|
||||
var __selectImage = function() {
|
||||
@@ -80,17 +269,17 @@ export function Msd() {
|
||||
};
|
||||
|
||||
var __clickDownloadButton = function() {
|
||||
let name = $("msd-image-selector").value;
|
||||
window.open(`/api/msd/read?image=${name}`);
|
||||
let image = encodeURIComponent($("msd-image-selector").value);
|
||||
window.open(`/api/msd/read?image=${image}`);
|
||||
};
|
||||
|
||||
var __clickRemoveButton = function() {
|
||||
let name = $("msd-image-selector").value;
|
||||
wm.confirm(`Are you sure you want to remove the image<br><b>${name}</b> from PiKVM?`).then(function(ok) {
|
||||
wm.confirm("Are you sure you want to remove this image?", name).then(function(ok) {
|
||||
if (ok) {
|
||||
tools.httpPost(`/api/msd/remove?image=${name}`, function(http) {
|
||||
tools.httpPost("/api/msd/remove", {"image": name}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't remove image:<br>", http.responseText);
|
||||
wm.error("Can't remove image", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -98,10 +287,11 @@ export function Msd() {
|
||||
};
|
||||
|
||||
var __sendParam = function(name, value) {
|
||||
tools.httpPost(`/api/msd/set_params?${name}=${encodeURIComponent(value)}`, function(http) {
|
||||
tools.httpPost("/api/msd/set_params", {[name]: value}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't configure MSD:<br>", http.responseText);
|
||||
wm.error("Can't configure Mass Storage", http.responseText);
|
||||
}
|
||||
__refreshControls();
|
||||
});
|
||||
};
|
||||
|
||||
@@ -110,80 +300,84 @@ export function Msd() {
|
||||
__http = new XMLHttpRequest();
|
||||
let prefix = encodeURIComponent($("msd-new-part-selector").value);
|
||||
if (file) {
|
||||
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${encodeURIComponent(file.name)}&remove_incomplete=1`, true);
|
||||
let image = encodeURIComponent(file.name);
|
||||
__http.open("POST", `/api/msd/write?prefix=${prefix}&image=${image}&remove_incomplete=1`, true);
|
||||
} else {
|
||||
let url = $("msd-new-url").value;
|
||||
__http.open("POST", `/api/msd/write_remote?prefix=${prefix}&url=${encodeURIComponent(url)}&remove_incomplete=1`, true);
|
||||
let url = encodeURIComponent($("msd-new-url").value);
|
||||
__http.open("POST", `/api/msd/write_remote?prefix=${prefix}&url=${url}&remove_incomplete=1`, true);
|
||||
}
|
||||
__http.upload.timeout = 7 * 24 * 3600;
|
||||
__http.onreadystatechange = __httpStateChange;
|
||||
__http.onreadystatechange = __uploadStateChange;
|
||||
__http.send(file);
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __httpStateChange = function() {
|
||||
if (__http.readyState === 4) {
|
||||
if (__http.status !== 200) {
|
||||
wm.error("Can't upload image to the Mass Storage Drive:<br>", __http.responseText);
|
||||
} else if ($("msd-new-url").value.length > 0) {
|
||||
let msg = "";
|
||||
try {
|
||||
let end = __http.responseText.lastIndexOf("\r\n");
|
||||
if (end < 0) {
|
||||
end = __http.responseText.length;
|
||||
}
|
||||
let begin = __http.responseText.lastIndexOf("\r\n", end - 2);
|
||||
if (begin < 0) {
|
||||
end = 0;
|
||||
}
|
||||
let result_str = __http.responseText.slice(begin, end);
|
||||
let result = JSON.parse(result_str);
|
||||
if (!result.ok) {
|
||||
msg = `Can't upload image to the Mass Storage Drive:<br>${result_str}`;
|
||||
}
|
||||
} catch (err) {
|
||||
msg = `Can't parse upload result:<br>${err}`;
|
||||
}
|
||||
if (msg.length > 0) {
|
||||
wm.error(msg);
|
||||
}
|
||||
}
|
||||
tools.hidden.setVisible($("msd-new-sub"), false);
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
__http = null;
|
||||
__applyState();
|
||||
var __uploadStateChange = function() {
|
||||
if (__http.readyState !== 4) {
|
||||
return;
|
||||
}
|
||||
if (__http.status !== 200) {
|
||||
wm.error("Can't upload image", __http.responseText);
|
||||
} else if ($("msd-new-url").value.length > 0) {
|
||||
let html = "";
|
||||
let msg = "";
|
||||
try {
|
||||
let end = __http.responseText.lastIndexOf("\r\n");
|
||||
if (end < 0) {
|
||||
end = __http.responseText.length;
|
||||
}
|
||||
let begin = __http.responseText.lastIndexOf("\r\n", end - 2);
|
||||
if (begin < 0) {
|
||||
end = 0;
|
||||
}
|
||||
let result_str = __http.responseText.slice(begin, end);
|
||||
let result = JSON.parse(result_str);
|
||||
if (!result.ok) {
|
||||
html = "Can't upload image";
|
||||
msg = result_str;
|
||||
}
|
||||
} catch (ex) {
|
||||
html = "Can't parse upload result";
|
||||
msg = `${ex}`;
|
||||
}
|
||||
if (html.length > 0) {
|
||||
wm.error(html, msg);
|
||||
}
|
||||
}
|
||||
tools.hidden.setVisible($("msd-new-sub"), false);
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
__http = null;
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __clickAbortNewButton = function() {
|
||||
__http.onreadystatechange = null;
|
||||
__http.abort();
|
||||
__http = null;
|
||||
tools.progress.setValue($("msd-uploading-progress"), "Aborted", 0);
|
||||
__refreshControls();
|
||||
tools.hidden.setVisible($("msd-new-sub"), true);
|
||||
};
|
||||
|
||||
var __clickConnectButton = function(connected) {
|
||||
tools.httpPost(`/api/msd/set_connected?connected=${connected}`, function(http) {
|
||||
tools.httpPost("/api/msd/set_connected", {"connected": connected}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Switch error:<br>", http.responseText);
|
||||
wm.error("Can't switch Mass Storage", http.responseText);
|
||||
}
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
});
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
tools.el.setEnabled($(`msd-${connected ? "connect" : "disconnect"}-button`), false);
|
||||
};
|
||||
|
||||
var __clickResetButton = function() {
|
||||
wm.confirm("Are you sure you want to reset Mass Storage Drive?").then(function(ok) {
|
||||
wm.confirm("Are you sure you want to reset Mass Storage?").then(function(ok) {
|
||||
if (ok) {
|
||||
tools.httpPost("/api/msd/reset", function(http) {
|
||||
tools.httpPost("/api/msd/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("MSD reset error:<br>", http.responseText);
|
||||
wm.error("Mass Storage reset error", http.responseText);
|
||||
}
|
||||
__applyState();
|
||||
});
|
||||
__applyState();
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -191,193 +385,35 @@ export function Msd() {
|
||||
var __toggleSelectSub = function() {
|
||||
let el_sub = $("msd-new-sub");
|
||||
let visible = tools.hidden.isVisible(el_sub);
|
||||
tools.hidden.setVisible(el_sub, !visible);
|
||||
if (visible) {
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
}
|
||||
tools.hidden.setVisible(el_sub, !visible);
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __selectNewFile = function() {
|
||||
let el_input = $("msd-new-file");
|
||||
let file = tools.input.getFile($("msd-new-file"));
|
||||
let el = $("msd-new-file");
|
||||
let file = tools.input.getFile(el);
|
||||
if (file) {
|
||||
$("msd-new-url").value = "";
|
||||
let part = __state.storage.parts[$("msd-new-part-selector").value];
|
||||
if (file.size > part.size) {
|
||||
wm.error("New image is too big for the MSD partition.<br>Maximum:", tools.formatSize(part.size));
|
||||
el_input.value = "";
|
||||
if (__state && __state.storage && __state.storage.parts) {
|
||||
let part = __state.storage.parts[$("msd-new-part-selector").value];
|
||||
if (part && (file.size > part.size)) {
|
||||
wm.error(`The new image is too big for the Mass Storage partition.<br>Maximum: ${tools.formatSize(part.size)}`);
|
||||
el.value = "";
|
||||
}
|
||||
}
|
||||
}
|
||||
__applyState();
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
var __selectNewUrl = function() {
|
||||
if ($("msd-new-url").value.length > 0) {
|
||||
$("msd-new-file").value = "";
|
||||
}
|
||||
__applyState();
|
||||
};
|
||||
|
||||
var __applyState = function() {
|
||||
__applyStateStatus();
|
||||
|
||||
let s = __state;
|
||||
let online = (s && s.online);
|
||||
|
||||
if (s) {
|
||||
tools.feature.setEnabled($("msd-dropdown"), s.enabled);
|
||||
tools.feature.setEnabled($("msd-reset-button"), s.enabled);
|
||||
}
|
||||
tools.hidden.setVisible($("msd-message-offline"), (s && !s.online));
|
||||
tools.hidden.setVisible($("msd-message-image-broken"), (online && s.drive.image && !s.drive.image.complete && !s.storage.uploading));
|
||||
tools.hidden.setVisible($("msd-message-too-big-for-cdrom"), (online && s.drive.cdrom && s.drive.image && s.drive.image.size >= 2359296000));
|
||||
tools.hidden.setVisible($("msd-message-out-of-storage"), (online && s.drive.image && !s.drive.image.in_storage));
|
||||
tools.hidden.setVisible($("msd-message-rw-enabled"), (online && s.drive.rw));
|
||||
tools.hidden.setVisible($("msd-message-another-user-uploads"), (online && s.storage.uploading && !__http));
|
||||
tools.hidden.setVisible($("msd-message-downloads"), (online && s.storage.downloading));
|
||||
|
||||
if (online) {
|
||||
let names = Object.keys(s.storage.parts).sort();
|
||||
let parts_names_json = JSON.stringify(names);
|
||||
if (__parts_names_json !== parts_names_json) {
|
||||
$("msd-storages").innerHTML = names.map(name => `
|
||||
<div class="text">
|
||||
<div id="msd-storage-${tools.makeIdByText(name)}-progress" class="progress">
|
||||
<span class="progress-value"></span>
|
||||
</div>
|
||||
</div>
|
||||
`).join("<hr>");
|
||||
__parts_names_json = parts_names_json;
|
||||
__parts_names_len = names.length;
|
||||
}
|
||||
__parts = s.storage.parts;
|
||||
}
|
||||
for (let name in __parts) {
|
||||
let part = __parts[name];
|
||||
let title = (
|
||||
name.length === 0
|
||||
? `${__parts_names_len === 1 ? "Storage: %s" : "Internal storage: %s"}` // eslint-disable-line
|
||||
: `Storage [${name}${part.writable ? "]" : ", read-only]"}: %s` // eslint-disable-line
|
||||
);
|
||||
let id = `msd-storage-${tools.makeIdByText(name)}-progress`;
|
||||
if (online) {
|
||||
tools.progress.setSizeOf($(id), title, part.size, part.free);
|
||||
} else {
|
||||
tools.progress.setValue($(id), title.replace("%s", "unavailable"), 0);
|
||||
}
|
||||
}
|
||||
|
||||
tools.el.setEnabled($("msd-image-selector"), (online && !s.drive.connected && !s.busy));
|
||||
__applyStateImageSelector();
|
||||
tools.el.setEnabled($("msd-download-button"), (online && s.drive.image && !s.drive.connected && !s.busy));
|
||||
tools.el.setEnabled($("msd-remove-button"), (online && s.drive.image && s.drive.image.removable && !s.drive.connected && !s.busy));
|
||||
|
||||
tools.radio.setEnabled("msd-mode-radio", (online && !s.drive.connected && !s.busy));
|
||||
tools.radio.setValue("msd-mode-radio", `${Number(online && s.drive.cdrom)}`);
|
||||
|
||||
tools.el.setEnabled($("msd-rw-switch"), (online && !s.drive.connected && !s.busy));
|
||||
$("msd-rw-switch").checked = (online && s.drive.rw);
|
||||
|
||||
tools.el.setEnabled($("msd-connect-button"), (online && s.drive.image && !s.drive.connected && !s.busy));
|
||||
tools.el.setEnabled($("msd-disconnect-button"), (online && s.drive.connected && !s.busy));
|
||||
|
||||
tools.el.setEnabled($("msd-select-new-button"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
tools.el.setEnabled($("msd-upload-new-button"),
|
||||
(online && !s.drive.connected && (tools.input.getFile($("msd-new-file")) || $("msd-new-url").value.length > 0) && !s.busy));
|
||||
tools.el.setEnabled($("msd-abort-new-button"), (online && __http));
|
||||
|
||||
tools.el.setEnabled($("msd-reset-button"), (s && s.enabled && !s.busy));
|
||||
|
||||
tools.el.setEnabled($("msd-new-file"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
tools.el.setEnabled($("msd-new-url"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
tools.el.setEnabled($("msd-new-part-selector"), (online && !s.drive.connected && !__http && !s.busy));
|
||||
if (online && !s.storage.uploading && !s.storage.downloading) {
|
||||
let parts = Object.keys(s.storage.parts).sort().filter(name => (name === "" || s.storage.parts[name].writable));
|
||||
tools.selector.setValues($("msd-new-part-selector"), parts, "\u2500 Internal \u2500");
|
||||
tools.hidden.setVisible($("msd-new-part"), (parts.length > 1));
|
||||
}
|
||||
|
||||
tools.hidden.setVisible($("msd-uploading-sub"), (online && s.storage.uploading));
|
||||
$("msd-uploading-name").innerHTML = ((online && s.storage.uploading) ? s.storage.uploading.name : "");
|
||||
$("msd-uploading-size").innerHTML = ((online && s.storage.uploading) ? tools.formatSize(s.storage.uploading.size) : "");
|
||||
if (online) {
|
||||
if (s.storage.uploading) {
|
||||
tools.progress.setPercentOf($("msd-uploading-progress"), s.storage.uploading.size, s.storage.uploading.written);
|
||||
} else if (!__http) {
|
||||
tools.progress.setValue($("msd-uploading-progress"), "Waiting for upload (press UPLOAD button) ...", 0);
|
||||
}
|
||||
} else {
|
||||
$("msd-new-file").value = "";
|
||||
$("msd-new-url").value = "";
|
||||
tools.progress.setValue($("msd-uploading-progress"), "", 0);
|
||||
}
|
||||
};
|
||||
|
||||
var __applyStateStatus = function() {
|
||||
let s = __state;
|
||||
let online = (s && s.online);
|
||||
|
||||
let led_cls = "led-gray";
|
||||
let msg = "Unavailable";
|
||||
|
||||
if (online && s.drive.connected) {
|
||||
led_cls = "led-green";
|
||||
msg = "Connected to Server";
|
||||
} else if (online && s.storage.uploading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Uploading new image";
|
||||
} else if (online && s.storage.downloading) {
|
||||
led_cls = "led-yellow-rotating-fast";
|
||||
msg = "Serving the image to download";
|
||||
} else if (online) { // Sic!
|
||||
msg = "Disconnected";
|
||||
}
|
||||
|
||||
$("msd-led").className = led_cls;
|
||||
$("msd-status").innerHTML = $("msd-led").title = msg;
|
||||
};
|
||||
|
||||
var __applyStateImageSelector = function() {
|
||||
let s = __state;
|
||||
if (!(s && s.online) || s.storage.uploading || s.storage.downloading) {
|
||||
return;
|
||||
}
|
||||
|
||||
let el = $("msd-image-selector");
|
||||
el.options.length = 1;
|
||||
|
||||
let selected = "";
|
||||
|
||||
for (let name of Object.keys(s.storage.images).sort()) {
|
||||
tools.selector.addSeparator(el);
|
||||
tools.selector.addOption(el, name, name);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(s.storage.images[name]));
|
||||
if (s.drive.image && s.drive.image.name === name && s.drive.image.in_storage) {
|
||||
selected = name;
|
||||
}
|
||||
}
|
||||
|
||||
if (s.drive.image && !s.drive.image.in_storage) {
|
||||
selected = ".__external";
|
||||
tools.selector.addOption(el, s.drive.image.name, selected);
|
||||
tools.selector.addComment(el, __makeImageSelectorInfo(s.drive.image));
|
||||
}
|
||||
|
||||
el.value = selected;
|
||||
};
|
||||
|
||||
var __makeImageSelectorInfo = function(image) {
|
||||
let info = `\xA0\xA0\xA0\xA0\xA0\u2570 ${tools.formatSize(image.size)}`;
|
||||
info += (image.complete ? "" : ", broken");
|
||||
if (image.in_storage !== undefined && !image.in_storage) {
|
||||
info += ", out of storage";
|
||||
}
|
||||
let dt = new Date(image.mod_ts * 1000);
|
||||
dt = new Date(dt.getTime() - (dt.getTimezoneOffset() * 60000));
|
||||
info += " \u2500 " + dt.toISOString().slice(0, -8).replaceAll("-", ".").replace("T", "-");
|
||||
return info;
|
||||
__refreshControls();
|
||||
};
|
||||
|
||||
__init__();
|
||||
|
||||
@@ -32,9 +32,11 @@ export function Ocr(__getGeometry) {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __enabled = null;
|
||||
|
||||
var __start_pos = null;
|
||||
var __end_pos = null;
|
||||
var __selection = null;
|
||||
var __sel = null;
|
||||
|
||||
var __init__ = function() {
|
||||
tools.el.setOnClick($("stream-ocr-button"), function() {
|
||||
@@ -54,7 +56,7 @@ export function Ocr(__getGeometry) {
|
||||
$("stream-ocr-window").onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (event.code === "Enter") {
|
||||
if (__selection) {
|
||||
if (__sel) {
|
||||
__recognizeSelection();
|
||||
wm.closeWindow($("stream-ocr-window"));
|
||||
}
|
||||
@@ -71,14 +73,29 @@ export function Ocr(__getGeometry) {
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
let enabled = (state && state.ocr.enabled && !tools.browser.is_mobile);
|
||||
if (enabled) {
|
||||
let el = $("stream-ocr-lang-selector");
|
||||
tools.selector.setValues(el, state.ocr.langs.available);
|
||||
tools.selector.setSelectedValue(el, tools.storage.get("stream.ocr.lang", state.ocr.langs["default"]));
|
||||
if (state) {
|
||||
if (state.enabled !== undefined) {
|
||||
__enabled = (state.enabled && !tools.browser.is_mobile);
|
||||
tools.feature.setEnabled($("stream-ocr"), __enabled);
|
||||
$("stream-ocr-led").className = (__enabled ? "led-gray" : "hidden");
|
||||
}
|
||||
if (__enabled && state.langs !== undefined) {
|
||||
__updateLangs(state.langs);
|
||||
}
|
||||
} else {
|
||||
__enabled = false;
|
||||
tools.feature.setEnabled($("stream-ocr"), false);
|
||||
$("stream-ocr-led").className = "hidden";
|
||||
}
|
||||
tools.feature.setEnabled($("stream-ocr"), enabled);
|
||||
$("stream-ocr-led").className = (enabled ? "led-gray" : "hidden");
|
||||
};
|
||||
|
||||
var __updateLangs = function(langs) {
|
||||
let el = $("stream-ocr-lang-selector");
|
||||
el.options.length = 0;
|
||||
for (let lang of langs.available) {
|
||||
tools.selector.addOption(el, lang, lang);
|
||||
}
|
||||
el.value = tools.storage.get("stream.ocr.lang", langs["default"]);
|
||||
};
|
||||
|
||||
var __startSelection = function(event) {
|
||||
@@ -94,23 +111,23 @@ export function Ocr(__getGeometry) {
|
||||
__end_pos = __getGlobalPosition(event);
|
||||
let width = Math.abs(__start_pos.x - __end_pos.x);
|
||||
let height = Math.abs(__start_pos.y - __end_pos.y);
|
||||
let el_selection = $("stream-ocr-selection");
|
||||
el_selection.style.left = Math.min(__start_pos.x, __end_pos.x) + "px";
|
||||
el_selection.style.top = Math.min(__start_pos.y, __end_pos.y) + "px";
|
||||
el_selection.style.width = width + "px";
|
||||
el_selection.style.height = height + "px";
|
||||
tools.hidden.setVisible(el_selection, (width > 1 || height > 1));
|
||||
let el = $("stream-ocr-selection");
|
||||
el.style.left = Math.min(__start_pos.x, __end_pos.x) + "px";
|
||||
el.style.top = Math.min(__start_pos.y, __end_pos.y) + "px";
|
||||
el.style.width = width + "px";
|
||||
el.style.height = height + "px";
|
||||
tools.hidden.setVisible(el, (width > 1 || height > 1));
|
||||
}
|
||||
};
|
||||
|
||||
var __endSelection = function(event) {
|
||||
__changeSelection(event);
|
||||
let el_selection = $("stream-ocr-selection");
|
||||
let el = $("stream-ocr-selection");
|
||||
let ok = (
|
||||
el_selection.offsetWidth > 1 && el_selection.offsetHeight > 1
|
||||
el.offsetWidth > 1 && el.offsetHeight > 1
|
||||
&& __start_pos !== null && __end_pos !== null
|
||||
);
|
||||
tools.hidden.setVisible(el_selection, ok);
|
||||
tools.hidden.setVisible(el, ok);
|
||||
if (ok) {
|
||||
let rect = $("stream-box").getBoundingClientRect();
|
||||
let rel_left = Math.min(__start_pos.x, __end_pos.x) - rect.left;
|
||||
@@ -119,14 +136,14 @@ export function Ocr(__getGeometry) {
|
||||
let rel_top = Math.min(__start_pos.y, __end_pos.y) - rect.top + offset;
|
||||
let rel_bottom = Math.max(__start_pos.y, __end_pos.y) - rect.top + offset;
|
||||
let geo = __getGeometry();
|
||||
__selection = {
|
||||
__sel = {
|
||||
"left": tools.remap(rel_left, geo.x, geo.width, 0, geo.real_width),
|
||||
"right": tools.remap(rel_right, geo.x, geo.width, 0, geo.real_width),
|
||||
"top": tools.remap(rel_top, geo.y, geo.height, 0, geo.real_height),
|
||||
"bottom": tools.remap(rel_bottom, geo.y, geo.height, 0, geo.real_height),
|
||||
};
|
||||
} else {
|
||||
__selection = null;
|
||||
__sel = null;
|
||||
}
|
||||
__start_pos = null;
|
||||
__end_pos = null;
|
||||
@@ -154,20 +171,22 @@ export function Ocr(__getGeometry) {
|
||||
tools.hidden.setVisible($("stream-ocr-selection"), false);
|
||||
__start_pos = null;
|
||||
__end_pos = null;
|
||||
__selection = null;
|
||||
__sel = null;
|
||||
};
|
||||
|
||||
var __recognizeSelection = function() {
|
||||
tools.el.setEnabled($("stream-ocr-button"), false);
|
||||
tools.el.setEnabled($("stream-ocr-lang-selector"), false);
|
||||
$("stream-ocr-led").className = "led-yellow-rotating-fast";
|
||||
|
||||
let lang = $("stream-ocr-lang-selector").value;
|
||||
let url = `/api/streamer/snapshot?ocr=1&ocr_langs=${lang}`;
|
||||
url += `&ocr_left=${__selection.left}&ocr_top=${__selection.top}`;
|
||||
url += `&ocr_right=${__selection.right}&ocr_bottom=${__selection.bottom}`;
|
||||
|
||||
tools.httpGet(url, function(http) {
|
||||
let params = {
|
||||
"ocr": 1,
|
||||
"ocr_langs": $("stream-ocr-lang-selector").value,
|
||||
"ocr_left": __sel.left,
|
||||
"ocr_top": __sel.top,
|
||||
"ocr_right": __sel.right,
|
||||
"orc_bottom": __sel.bottom,
|
||||
};
|
||||
tools.httpGet("/api/streamer/snapshot", params, function(http) {
|
||||
if (http.status === 200) {
|
||||
wm.copyTextToClipboard(http.responseText);
|
||||
} else {
|
||||
|
||||
112
web/share/js/kvm/paste.js
Normal file
112
web/share/js/kvm/paste.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/*****************************************************************************
|
||||
# #
|
||||
# KVMD - The main PiKVM daemon. #
|
||||
# #
|
||||
# Copyright (C) 2018-2024 Maxim Devaev <mdevaev@gmail.com> #
|
||||
# #
|
||||
# This program is free software: you can redistribute it and/or modify #
|
||||
# it under the terms of the GNU General Public License as published by #
|
||||
# the Free Software Foundation, either version 3 of the License, or #
|
||||
# (at your option) any later version. #
|
||||
# #
|
||||
# This program is distributed in the hope that it will be useful, #
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of #
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the #
|
||||
# GNU General Public License for more details. #
|
||||
# #
|
||||
# You should have received a copy of the GNU General Public License #
|
||||
# along with this program. If not, see <https://www.gnu.org/licenses/>. #
|
||||
# #
|
||||
*****************************************************************************/
|
||||
|
||||
|
||||
"use strict";
|
||||
|
||||
|
||||
import {tools, $} from "../tools.js";
|
||||
import {wm} from "../wm.js";
|
||||
|
||||
|
||||
export function Paste(__recorder) {
|
||||
var self = this;
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __init__ = function() {
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-ask-switch"), "hid.pak.ask", true);
|
||||
tools.storage.bindSimpleSwitch($("hid-pak-secure-switch"), "hid.pak.secure", false, function(value) {
|
||||
$("hid-pak-text").style.setProperty("-webkit-text-security", (value ? "disc" : "none"));
|
||||
});
|
||||
tools.feature.setEnabled($("hid-pak-secure"), (
|
||||
tools.browser.is_chrome
|
||||
|| tools.browser.is_safari
|
||||
|| tools.browser.is_opera
|
||||
));
|
||||
|
||||
$("hid-pak-keymap-selector").addEventListener("change", function() {
|
||||
tools.storage.set("hid.pak.keymap", $("hid-pak-keymap-selector").value);
|
||||
});
|
||||
tools.el.setOnClick($("hid-pak-button"), __clickPasteAsKeysButton);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.setState = function(state) {
|
||||
tools.el.setEnabled($("hid-pak-text"), state);
|
||||
tools.el.setEnabled($("hid-pak-button"), state);
|
||||
if (state) {
|
||||
let el = $("hid-pak-keymap-selector");
|
||||
let sel = tools.storage.get("hid.pak.keymap", state.keymaps["default"]);
|
||||
el.options.length = 0;
|
||||
for (let keymap of state.keymaps.available) {
|
||||
tools.selector.addOption(el, keymap, keymap, (keymap === sel));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __clickPasteAsKeysButton = function() {
|
||||
let text = $("hid-pak-text").value;
|
||||
if (text) {
|
||||
let paste_as_keys = function() {
|
||||
tools.el.setEnabled($("hid-pak-text"), false);
|
||||
tools.el.setEnabled($("hid-pak-button"), false);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), false);
|
||||
|
||||
let keymap = $("hid-pak-keymap-selector").value;
|
||||
|
||||
tools.debug(`HID: paste-as-keys ${keymap}: ${text}`);
|
||||
|
||||
tools.httpPost("/api/hid/print", {"limit": 0, "keymap": keymap}, function(http) {
|
||||
tools.el.setEnabled($("hid-pak-text"), true);
|
||||
tools.el.setEnabled($("hid-pak-button"), true);
|
||||
tools.el.setEnabled($("hid-pak-keymap-selector"), true);
|
||||
$("hid-pak-text").value = "";
|
||||
if (http.status === 413) {
|
||||
wm.error("Too many text for paste!");
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("HID paste error", http.responseText);
|
||||
} else if (http.status === 200) {
|
||||
__recorder.recordPrintEvent(text, keymap);
|
||||
}
|
||||
}, text, "text/plain");
|
||||
};
|
||||
|
||||
if ($("hid-pak-ask-switch").checked) {
|
||||
wm.confirm(`
|
||||
You're going to paste ${text.length} character${text.length ? "s" : ""}.<br>
|
||||
Are you sure you want to continue?
|
||||
`).then(function(ok) {
|
||||
if (ok) {
|
||||
paste_as_keys();
|
||||
} else {
|
||||
$("hid-pak-text").value = "";
|
||||
}
|
||||
});
|
||||
} else {
|
||||
paste_as_keys();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
@@ -67,8 +67,8 @@ export function Recorder() {
|
||||
__recordEvent(event);
|
||||
};
|
||||
|
||||
self.recordPrintEvent = function(text) {
|
||||
__recordEvent({"event_type": "print", "event": {"text": text}});
|
||||
self.recordPrintEvent = function(text, keymap) {
|
||||
__recordEvent({"event_type": "print", "event": {"text": text, "keymap": keymap}});
|
||||
};
|
||||
|
||||
self.recordAtxButtonEvent = function(button) {
|
||||
@@ -159,6 +159,9 @@ export function Recorder() {
|
||||
|
||||
} else if (event.event_type === "print") {
|
||||
__checkType(event.event.text, "string", "Non-string print text");
|
||||
if (event.event.keymap) {
|
||||
__checkType(event.event.keymap, "string", "Non-string keymap");
|
||||
}
|
||||
|
||||
} else if (event.event_type === "key") {
|
||||
__checkType(event.event.key, "string", "Non-string key code");
|
||||
@@ -214,8 +217,8 @@ export function Recorder() {
|
||||
|
||||
__events = events;
|
||||
__events_time = events_time;
|
||||
} catch (err) {
|
||||
wm.error(`Invalid script: ${err}`);
|
||||
} catch (ex) {
|
||||
wm.error("Invalid script", `${ex}`);
|
||||
}
|
||||
|
||||
el_input.value = "";
|
||||
@@ -280,12 +283,16 @@ export function Recorder() {
|
||||
return;
|
||||
|
||||
} else if (event.event_type === "print") {
|
||||
tools.httpPost("/api/hid/print?limit=0", function(http) {
|
||||
let params = {"limit": 0};
|
||||
if (event.event.keymap) {
|
||||
params["keymap"] = event.event.keymap;
|
||||
}
|
||||
tools.httpPost("/api/hid/print", params, function(http) {
|
||||
if (http.status === 413) {
|
||||
wm.error("Too many text for paste!");
|
||||
__stopProcess();
|
||||
} else if (http.status !== 200) {
|
||||
wm.error("HID paste error:<br>", http.responseText);
|
||||
wm.error("HID paste error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
@@ -294,9 +301,9 @@ export function Recorder() {
|
||||
return;
|
||||
|
||||
} else if (event.event_type === "atx_button") {
|
||||
tools.httpPost(`/api/atx/click?button=${event.event.button}`, function(http) {
|
||||
tools.httpPost("/api/atx/click", {"button": event.event.button}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("ATX error:<br>", http.responseText);
|
||||
wm.error("ATX error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
@@ -306,14 +313,16 @@ export function Recorder() {
|
||||
|
||||
} else if (["gpio_switch", "gpio_pulse"].includes(event.event_type)) {
|
||||
let path = "/api/gpio";
|
||||
let params = {"channel": event.event.channel};
|
||||
if (event.event_type === "gpio_switch") {
|
||||
path += `/switch?channel=${event.event.channel}&state=${event.event.to}`;
|
||||
path += "/switch";
|
||||
params["state"] = event.event.to;
|
||||
} else { // gpio_pulse
|
||||
path += `/pulse?channel=${event.event.channel}`;
|
||||
path += "/pulse";
|
||||
}
|
||||
tools.httpPost(path, function(http) {
|
||||
tools.httpPost(path, params, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("GPIO error:<br>", http.responseText);
|
||||
wm.error("GPIO error", http.responseText);
|
||||
__stopProcess();
|
||||
} else if (http.status === 200) {
|
||||
__play_timer = setTimeout(() => __runEvents(index + 1, time), 0);
|
||||
|
||||
@@ -28,6 +28,7 @@ import {wm} from "../wm.js";
|
||||
|
||||
import {Recorder} from "./recorder.js";
|
||||
import {Hid} from "./hid.js";
|
||||
import {Paste} from "./paste.js";
|
||||
import {Atx} from "./atx.js";
|
||||
import {Msd} from "./msd.js";
|
||||
import {Streamer} from "./stream.js";
|
||||
@@ -48,6 +49,7 @@ export function Session() {
|
||||
var __streamer = new Streamer();
|
||||
var __recorder = new Recorder();
|
||||
var __hid = new Hid(__streamer.getGeometry, __recorder);
|
||||
var __paste = new Paste(__recorder);
|
||||
var __atx = new Atx(__recorder);
|
||||
var __msd = new Msd();
|
||||
var __gpio = new Gpio(__recorder);
|
||||
@@ -57,30 +59,42 @@ export function Session() {
|
||||
var __info_fan_state = null;
|
||||
|
||||
var __init__ = function() {
|
||||
__startSession();
|
||||
__streamer.ensureDeps(() => __startSession());
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __setAboutInfoMeta = function(state) {
|
||||
var __setInfoState = function(state) {
|
||||
for (let key of Object.keys(state)) {
|
||||
switch (key) {
|
||||
case "meta": __setInfoStateMeta(state.meta); break;
|
||||
case "hw": __setInfoStateHw(state.hw); break;
|
||||
case "fan": __setInfoStateFan(state.fan); break;
|
||||
case "system": __setInfoStateSystem(state.system); break;
|
||||
case "extras": __setInfoStateExtras(state.extras); break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var __setInfoStateMeta = function(state) {
|
||||
if (state !== null) {
|
||||
let text = JSON.stringify(state, undefined, 4).replace(/ /g, " ").replace(/\n/g, "<br>");
|
||||
$("about-meta").innerHTML = `
|
||||
<span class="code-comment">// The PiKVM metadata.<br>
|
||||
// You can get this JSON using handle <a target="_blank" href="/api/info?fields=meta">/api/info?fields=meta</a>.<br>
|
||||
// In the standard configuration this data<br>
|
||||
// is specified in the file /etc/kvmd/meta.yaml.</span><br>
|
||||
<br>
|
||||
${text}
|
||||
`;
|
||||
$("kvmd-meta-json").innerText = JSON.stringify(state, undefined, 4);
|
||||
|
||||
if (state.server && state.server.host) {
|
||||
$("kvmd-meta-server-host").innerHTML = `Server: ${state.server.host}`;
|
||||
$("kvmd-meta-server-host").innerText = `Server: ${state.server.host}`;
|
||||
document.title = `PiKVM Session: ${state.server.host}`;
|
||||
} else {
|
||||
$("kvmd-meta-server-host").innerHTML = "";
|
||||
$("kvmd-meta-server-host").innerText = "";
|
||||
document.title = "PiKVM Session";
|
||||
}
|
||||
|
||||
if (state.tips && state.tips.left) {
|
||||
$("kvmd-meta-tips-left").innerText = `${state.tips.left}`;
|
||||
}
|
||||
if (state.tips && state.tips.right) {
|
||||
$("kvmd-meta-tips-right").innerText = `${state.tips.right}`;
|
||||
}
|
||||
|
||||
// Don't use this option, it may be removed in any time
|
||||
if (state.web && state.web.confirm_session_exit === false) {
|
||||
window.onbeforeunload = null; // See main.js
|
||||
@@ -88,7 +102,7 @@ export function Session() {
|
||||
}
|
||||
};
|
||||
|
||||
var __setAboutInfoHw = function(state) {
|
||||
var __setInfoStateHw = function(state) {
|
||||
if (state.health.throttling !== null) {
|
||||
let flags = state.health.throttling.parsed_flags;
|
||||
let ignore_past = state.health.throttling.ignore_past;
|
||||
@@ -105,7 +119,7 @@ export function Session() {
|
||||
__renderAboutInfoHardware();
|
||||
};
|
||||
|
||||
var __setAboutInfoFan = function(state) {
|
||||
var __setInfoStateFan = function(state) {
|
||||
let failed = false;
|
||||
let failed_past = false;
|
||||
if (state.monitored) {
|
||||
@@ -207,11 +221,11 @@ export function Session() {
|
||||
}
|
||||
};
|
||||
|
||||
var __colored = function(color, text) {
|
||||
return `<font color="${color}">${text}</font>`;
|
||||
var __colored = function(color, html) {
|
||||
return `<font color="${color}">${html}</font>`;
|
||||
};
|
||||
|
||||
var __setAboutInfoSystem = function(state) {
|
||||
var __setInfoStateSystem = function(state) {
|
||||
$("about-version").innerHTML = `
|
||||
KVMD: <span class="code-comment">${state.kvmd.version}</span><br>
|
||||
<hr>
|
||||
@@ -221,8 +235,8 @@ export function Session() {
|
||||
${state.kernel.system} kernel:
|
||||
${__formatUname(state.kernel)}
|
||||
`;
|
||||
$("kvmd-version-kvmd").innerHTML = state.kvmd.version;
|
||||
$("kvmd-version-streamer").innerHTML = state.streamer.version;
|
||||
$("kvmd-version-kvmd").innerText = state.kvmd.version;
|
||||
$("kvmd-version-streamer").innerText = state.streamer.version;
|
||||
};
|
||||
|
||||
var __formatStreamerFeatures = function(features) {
|
||||
@@ -244,14 +258,14 @@ export function Session() {
|
||||
};
|
||||
|
||||
var __formatUl = function(pairs) {
|
||||
let text = "<ul>";
|
||||
let html = "";
|
||||
for (let pair of pairs) {
|
||||
text += `<li>${pair[0]}: <span class="code-comment">${pair[1]}</span></li>`;
|
||||
html += `<li>${pair[0]}: <span class="code-comment">${pair[1]}</span></li>`;
|
||||
}
|
||||
return text + "</ul>";
|
||||
return `<ul>${html}</ul>`;
|
||||
};
|
||||
|
||||
var __setExtras = function(state) {
|
||||
var __setInfoStateExtras = function(state) {
|
||||
let show_hook = null;
|
||||
let close_hook = null;
|
||||
let has_webterm = (state.webterm && (state.webterm.enabled || state.webterm.started));
|
||||
@@ -269,20 +283,15 @@ export function Session() {
|
||||
tools.feature.setEnabled($("system-tool-webterm"), has_webterm);
|
||||
$("webterm-window").show_hook = show_hook;
|
||||
$("webterm-window").close_hook = close_hook;
|
||||
|
||||
__streamer.setJanusEnabled(
|
||||
(state.janus && (state.janus.enabled || state.janus.started))
|
||||
|| (state.janus_static && (state.janus_static.enabled || state.janus_static.started))
|
||||
);
|
||||
};
|
||||
|
||||
var __startSession = function() {
|
||||
$("link-led").className = "led-yellow";
|
||||
$("link-led").title = "Connecting...";
|
||||
|
||||
tools.httpGet("/api/auth/check", function(http) {
|
||||
tools.httpGet("/api/auth/check", null, function(http) {
|
||||
if (http.status === 200) {
|
||||
__ws = new WebSocket(`${tools.is_https ? "wss" : "ws"}://${location.host}/api/ws`);
|
||||
__ws = new WebSocket(`${tools.is_https ? "wss" : "ws"}://${location.host}/api/ws?legacy=0`);
|
||||
__ws.sendHidEvent = (event) => __sendHidEvent(__ws, event.event_type, event.event);
|
||||
__ws.onopen = __wsOpenHandler;
|
||||
__ws.onmessage = __wsMessageHandler;
|
||||
@@ -354,19 +363,14 @@ export function Session() {
|
||||
let data = JSON.parse(event.data);
|
||||
switch (data.event_type) {
|
||||
case "pong": __missed_heartbeats = 0; break;
|
||||
case "info_meta_state": __setAboutInfoMeta(data.event); break;
|
||||
case "info_hw_state": __setAboutInfoHw(data.event); break;
|
||||
case "info_fan_state": __setAboutInfoFan(data.event); break;
|
||||
case "info_system_state": __setAboutInfoSystem(data.event); break;
|
||||
case "info_extras_state": __setExtras(data.event); break;
|
||||
case "gpio_model_state": __gpio.setModel(data.event); break;
|
||||
case "info_state": __setInfoState(data.event); break;
|
||||
case "gpio_state": __gpio.setState(data.event); break;
|
||||
case "hid_keymaps_state": __hid.setKeymaps(data.event); break;
|
||||
case "hid_state": __hid.setState(data.event); break;
|
||||
case "hid_keymaps_state": __paste.setState(data.event); break;
|
||||
case "atx_state": __atx.setState(data.event); break;
|
||||
case "msd_state": __msd.setState(data.event); break;
|
||||
case "streamer_state": __streamer.setState(data.event); break;
|
||||
case "streamer_ocr_state": __ocr.setState(data.event); break;
|
||||
case "ocr_state": __ocr.setState(data.event); break;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -389,13 +393,14 @@ export function Session() {
|
||||
__ping_timer = null;
|
||||
}
|
||||
|
||||
__ocr.setState(null);
|
||||
__gpio.setState(null);
|
||||
__hid.setSocket(null);
|
||||
__recorder.setSocket(null);
|
||||
__hid.setSocket(null); // auto setState(null);
|
||||
__paste.setState(null);
|
||||
__atx.setState(null);
|
||||
__msd.setState(null);
|
||||
__streamer.setState(null);
|
||||
__ocr.setState(null);
|
||||
__recorder.setSocket(null);
|
||||
__ws = null;
|
||||
|
||||
setTimeout(function() {
|
||||
@@ -411,8 +416,8 @@ export function Session() {
|
||||
throw new Error("Too many missed heartbeats");
|
||||
}
|
||||
__ws.send("{\"event_type\": \"ping\", \"event\": {}}");
|
||||
} catch (err) {
|
||||
__wsErrorHandler(err.message);
|
||||
} catch (ex) {
|
||||
__wsErrorHandler(ex.message);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -35,11 +35,11 @@ export function Streamer() {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
var __janus_enabled = null;
|
||||
var __janus_imported = null;
|
||||
var __streamer = null;
|
||||
|
||||
var __state = null;
|
||||
var __resolution = {"width": 640, "height": 480};
|
||||
var __res = {"width": 640, "height": 480};
|
||||
|
||||
var __init__ = function() {
|
||||
__streamer = new MjpegStreamer(__setActive, __setInactive, __setInfo);
|
||||
@@ -47,22 +47,22 @@ export function Streamer() {
|
||||
$("stream-led").title = "Stream inactive";
|
||||
|
||||
tools.slider.setParams($("stream-quality-slider"), 5, 100, 5, 80, function(value) {
|
||||
$("stream-quality-value").innerHTML = `${value}%`;
|
||||
$("stream-quality-value").innerText = `${value}%`;
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-quality-slider"), 1000, (value) => __sendParam("quality", value));
|
||||
|
||||
tools.slider.setParams($("stream-h264-bitrate-slider"), 25, 20000, 25, 5000, function(value) {
|
||||
$("stream-h264-bitrate-value").innerHTML = value;
|
||||
$("stream-h264-bitrate-value").innerText = value;
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-h264-bitrate-slider"), 1000, (value) => __sendParam("h264_bitrate", value));
|
||||
|
||||
tools.slider.setParams($("stream-h264-gop-slider"), 0, 60, 1, 30, function(value) {
|
||||
$("stream-h264-gop-value").innerHTML = value;
|
||||
$("stream-h264-gop-value").innerText = value;
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-h264-gop-slider"), 1000, (value) => __sendParam("h264_gop", value));
|
||||
|
||||
tools.slider.setParams($("stream-desired-fps-slider"), 0, 120, 1, 0, function(value) {
|
||||
$("stream-desired-fps-value").innerHTML = (value === 0 ? "Unlimited" : value);
|
||||
$("stream-desired-fps-value").innerText = (value === 0 ? "Unlimited" : value);
|
||||
});
|
||||
tools.slider.setOnUpDelayed($("stream-desired-fps-slider"), 1000, (value) => __sendParam("desired_fps", value));
|
||||
|
||||
@@ -86,7 +86,7 @@ export function Streamer() {
|
||||
tools.slider.setParams($("stream-audio-volume-slider"), 0, 100, 1, 0, function(value) {
|
||||
$("stream-video").muted = !value;
|
||||
$("stream-video").volume = value / 100;
|
||||
$("stream-audio-volume-value").innerHTML = value + "%";
|
||||
$("stream-audio-volume-value").innerText = value + "%";
|
||||
if (__streamer.getMode() === "janus") {
|
||||
let allow_audio = !$("stream-video").muted;
|
||||
if (__streamer.isAudioAllowed() !== allow_audio) {
|
||||
@@ -110,6 +110,13 @@ export function Streamer() {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.ensureDeps = function(callback) {
|
||||
JanusStreamer.ensure_janus(function(avail) {
|
||||
__janus_imported = avail;
|
||||
callback();
|
||||
});
|
||||
};
|
||||
|
||||
self.getGeometry = function() {
|
||||
// Первоначально обновление геометрии считалось через ResizeObserver.
|
||||
// Но оно не ловило некоторые события, например в последовательности:
|
||||
@@ -132,90 +139,103 @@ export function Streamer() {
|
||||
};
|
||||
};
|
||||
|
||||
self.setJanusEnabled = function(enabled) {
|
||||
let has_webrtc = JanusStreamer.is_webrtc_available();
|
||||
let has_h264 = JanusStreamer.is_h264_available();
|
||||
|
||||
let set_enabled = function(imported) {
|
||||
tools.hidden.setVisible($("stream-message-no-webrtc"), enabled && !has_webrtc);
|
||||
tools.hidden.setVisible($("stream-message-no-h264"), enabled && !has_h264);
|
||||
__janus_enabled = (enabled && has_webrtc && imported); // Don't check has_h264 for sure
|
||||
tools.feature.setEnabled($("stream-mode"), __janus_enabled);
|
||||
tools.info(
|
||||
`Stream: Janus WebRTC state: enabled=${enabled},`
|
||||
+ ` webrtc=${has_webrtc}, h264=${has_h264}, imported=${imported}`
|
||||
);
|
||||
let mode = (__janus_enabled ? tools.storage.get("stream.mode", "janus") : "mjpeg");
|
||||
tools.radio.clickValue("stream-mode-radio", mode);
|
||||
if (!__janus_enabled) {
|
||||
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
||||
}
|
||||
self.setState(__state);
|
||||
};
|
||||
|
||||
if (enabled && has_webrtc) {
|
||||
JanusStreamer.ensure_janus(set_enabled);
|
||||
} else {
|
||||
set_enabled(false);
|
||||
}
|
||||
};
|
||||
|
||||
self.setState = function(state) {
|
||||
__state = state;
|
||||
if (__janus_enabled !== null) {
|
||||
__applyState(wm.isWindowVisible($("stream-window")) ? __state : null);
|
||||
if (state) {
|
||||
if (!__state) {
|
||||
__state = {};
|
||||
}
|
||||
if (state.features !== undefined) {
|
||||
__state.features = state.features;
|
||||
__state.limits = state.limits; // Following together with features
|
||||
}
|
||||
if (__state.features !== undefined && state.streamer !== undefined) {
|
||||
__state.streamer = state.streamer;
|
||||
__setControlsEnabled(!!state.streamer);
|
||||
}
|
||||
} else {
|
||||
__state = null;
|
||||
__setControlsEnabled(false);
|
||||
}
|
||||
let visible = wm.isWindowVisible($("stream-window"));
|
||||
__applyState((visible && __state && __state.features) ? state : null);
|
||||
};
|
||||
|
||||
var __applyState = function(state) {
|
||||
if (state) {
|
||||
tools.feature.setEnabled($("stream-quality"), state.features.quality && (state.streamer === null || state.streamer.encoder.quality > 0));
|
||||
tools.feature.setEnabled($("stream-h264-bitrate"), state.features.h264 && __janus_enabled);
|
||||
tools.feature.setEnabled($("stream-h264-gop"), state.features.h264 && __janus_enabled);
|
||||
tools.feature.setEnabled($("stream-resolution"), state.features.resolution);
|
||||
if (__janus_imported === null) {
|
||||
alert("__janus_imported is null, please report");
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.streamer) {
|
||||
tools.el.setEnabled($("stream-quality-slider"), true);
|
||||
tools.slider.setValue($("stream-quality-slider"), state.streamer.encoder.quality);
|
||||
if (!state) {
|
||||
__streamer.stopStream();
|
||||
return;
|
||||
}
|
||||
|
||||
if (state.features.h264 && __janus_enabled) {
|
||||
__setLimitsAndValue($("stream-h264-bitrate-slider"), state.limits.h264_bitrate, state.streamer.h264.bitrate);
|
||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), true);
|
||||
if (state.features) {
|
||||
let f = state.features;
|
||||
let l = state.limits;
|
||||
let has_webrtc = JanusStreamer.is_webrtc_available();
|
||||
let has_h264 = JanusStreamer.is_h264_available();
|
||||
let has_janus = (__janus_imported && f.h264 && has_webrtc); // Don't check has_h264 for sure
|
||||
|
||||
__setLimitsAndValue($("stream-h264-gop-slider"), state.limits.h264_gop, state.streamer.h264.gop);
|
||||
tools.el.setEnabled($("stream-h264-gop-slider"), true);
|
||||
tools.info(
|
||||
`Stream: Janus WebRTC state: features.h264=${f.h264},`
|
||||
+ ` webrtc=${has_webrtc}, h264=${has_h264}, janus_imported=${__janus_imported}`
|
||||
);
|
||||
|
||||
tools.hidden.setVisible($("stream-message-no-webrtc"), __janus_imported && f.h264 && !has_webrtc);
|
||||
tools.hidden.setVisible($("stream-message-no-h264"), __janus_imported && f.h264 && !has_h264);
|
||||
|
||||
tools.slider.setRange($("stream-desired-fps-slider"), l.desired_fps.min, l.desired_fps.max);
|
||||
if (f.resolution) {
|
||||
let el = $("stream-resolution-selector");
|
||||
el.options.length = 0;
|
||||
for (let res of l.available_resolutions) {
|
||||
tools.selector.addOption(el, res, res);
|
||||
}
|
||||
|
||||
__setLimitsAndValue($("stream-desired-fps-slider"), state.limits.desired_fps, state.streamer.source.desired_fps);
|
||||
tools.el.setEnabled($("stream-desired-fps-slider"), true);
|
||||
|
||||
let resolution_str = __makeStringResolution(state.streamer.source.resolution);
|
||||
if (__makeStringResolution(__resolution) !== resolution_str) {
|
||||
__resolution = state.streamer.source.resolution;
|
||||
}
|
||||
|
||||
if (state.features.resolution) {
|
||||
let el = $("stream-resolution-selector");
|
||||
if (!state.limits.available_resolutions.includes(resolution_str)) {
|
||||
state.limits.available_resolutions.push(resolution_str);
|
||||
}
|
||||
tools.selector.setValues(el, state.limits.available_resolutions);
|
||||
tools.selector.setSelectedValue(el, resolution_str);
|
||||
tools.el.setEnabled(el, true);
|
||||
}
|
||||
|
||||
} else {
|
||||
tools.el.setEnabled($("stream-quality-slider"), false);
|
||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), false);
|
||||
tools.el.setEnabled($("stream-h264-gop-slider"), false);
|
||||
tools.el.setEnabled($("stream-desired-fps-slider"), false);
|
||||
tools.el.setEnabled($("stream-resolution-selector"), false);
|
||||
$("stream-resolution-selector").options.length = 0;
|
||||
}
|
||||
if (has_janus) {
|
||||
tools.slider.setRange($("stream-h264-bitrate-slider"), l.h264_bitrate.min, l.h264_bitrate.max);
|
||||
tools.slider.setRange($("stream-h264-gop-slider"), l.h264_gop.min, l.h264_gop.max);
|
||||
}
|
||||
|
||||
__streamer.ensureStream(state.streamer);
|
||||
// tools.feature.setEnabled($("stream-quality"), f.quality); // Only on s.encoder.quality
|
||||
tools.feature.setEnabled($("stream-resolution"), f.resolution);
|
||||
tools.feature.setEnabled($("stream-h264-bitrate"), has_janus);
|
||||
tools.feature.setEnabled($("stream-h264-gop"), has_janus);
|
||||
tools.feature.setEnabled($("stream-mode"), has_janus);
|
||||
if (!has_janus) {
|
||||
tools.feature.setEnabled($("stream-audio"), false);
|
||||
}
|
||||
|
||||
} else {
|
||||
__streamer.stopStream();
|
||||
let mode = (has_janus ? tools.storage.get("stream.mode", "janus") : "mjpeg");
|
||||
tools.radio.clickValue("stream-mode-radio", mode);
|
||||
}
|
||||
|
||||
if (state.streamer) {
|
||||
let s = state.streamer;
|
||||
__res = s.source.resolution;
|
||||
|
||||
{
|
||||
let res = `${__res.width}x${__res.height}`;
|
||||
let el = $("stream-resolution-selector");
|
||||
if (!tools.selector.hasValue(el, res)) {
|
||||
tools.selector.addOption(el, res, res);
|
||||
}
|
||||
el.value = res;
|
||||
}
|
||||
tools.slider.setValue($("stream-quality-slider"), Math.max(s.encoder.quality, 1));
|
||||
tools.slider.setValue($("stream-desired-fps-slider"), s.source.desired_fps);
|
||||
if (s.h264 && s.h264.bitrate) {
|
||||
tools.slider.setValue($("stream-h264-bitrate-slider"), s.h264.bitrate);
|
||||
tools.slider.setValue($("stream-h264-gop-slider"), s.h264.gop); // Following together with gop
|
||||
}
|
||||
|
||||
tools.feature.setEnabled($("stream-quality"), (s.encoder.quality > 0));
|
||||
|
||||
__streamer.ensureStream(s);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -229,16 +249,24 @@ export function Streamer() {
|
||||
$("stream-led").title = "Stream inactive";
|
||||
};
|
||||
|
||||
var __setControlsEnabled = function(enabled) {
|
||||
tools.el.setEnabled($("stream-quality-slider"), enabled);
|
||||
tools.el.setEnabled($("stream-desired-fps-slider"), enabled);
|
||||
tools.el.setEnabled($("stream-resolution-selector"), enabled);
|
||||
tools.el.setEnabled($("stream-h264-bitrate-slider"), enabled);
|
||||
tools.el.setEnabled($("stream-h264-gop-slider"), enabled);
|
||||
};
|
||||
|
||||
var __setInfo = function(is_active, online, text) {
|
||||
$("stream-box").classList.toggle("stream-box-offline", !online);
|
||||
let el_grab = document.querySelector("#stream-window-header .window-grab");
|
||||
let el_info = $("stream-info");
|
||||
let title = `${__streamer.getName()} – `;
|
||||
let title = `${__streamer.getName()} - `;
|
||||
if (is_active) {
|
||||
if (!online) {
|
||||
title += "No signal / ";
|
||||
}
|
||||
title += __makeStringResolution(__resolution);
|
||||
title += `${__res.width}x${__res.height}`;
|
||||
if (text.length > 0) {
|
||||
title += " / " + text;
|
||||
}
|
||||
@@ -249,12 +277,7 @@ export function Streamer() {
|
||||
title += "Inactive";
|
||||
}
|
||||
}
|
||||
el_grab.innerHTML = el_info.innerHTML = title;
|
||||
};
|
||||
|
||||
var __setLimitsAndValue = function(el, limits, value) {
|
||||
tools.slider.setRange(el, limits.min, limits.max);
|
||||
tools.slider.setValue(el, value);
|
||||
el_grab.innerText = el_info.innerText = title;
|
||||
};
|
||||
|
||||
var __resetStream = function(mode=null) {
|
||||
@@ -274,7 +297,7 @@ export function Streamer() {
|
||||
tools.feature.setEnabled($("stream-audio"), false); // Enabling in stream_janus.js
|
||||
}
|
||||
if (wm.isWindowVisible($("stream-window"))) {
|
||||
__streamer.ensureStream(__state ? __state.streamer : null);
|
||||
__streamer.ensureStream((__state && __state.streamer !== undefined) ? __state.streamer : null);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -298,12 +321,12 @@ export function Streamer() {
|
||||
};
|
||||
|
||||
var __clickResetButton = function() {
|
||||
wm.confirm("Are you sure you want to reset stream?").then(function (ok) {
|
||||
wm.confirm("Are you sure you want to reset stream?").then(function(ok) {
|
||||
if (ok) {
|
||||
__resetStream();
|
||||
tools.httpPost("/api/streamer/reset", function(http) {
|
||||
tools.httpPost("/api/streamer/reset", null, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't reset stream:<br>", http.responseText);
|
||||
wm.error("Can't reset stream", http.responseText);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -382,16 +405,12 @@ export function Streamer() {
|
||||
};
|
||||
|
||||
var __sendParam = function(name, value) {
|
||||
tools.httpPost(`/api/streamer/set_params?${name}=${value}`, function(http) {
|
||||
tools.httpPost("/api/streamer/set_params", {[name]: value}, function(http) {
|
||||
if (http.status !== 200) {
|
||||
wm.error("Can't configure stream:<br>", http.responseText);
|
||||
wm.error("Can't configure stream", http.responseText);
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
var __makeStringResolution = function(resolution) {
|
||||
return `${resolution.width}x${resolution.height}`;
|
||||
};
|
||||
|
||||
__init__();
|
||||
}
|
||||
|
||||
@@ -248,6 +248,13 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
|
||||
// Janus 0.x
|
||||
"media": {"audioSend": false, "videoSend": false, "data": false},
|
||||
|
||||
// Chrome is playing OPUS as mono without this hack
|
||||
// - https://issues.webrtc.org/issues/41481053 - IT'S NOT FIXED!
|
||||
// - https://github.com/ossrs/srs/pull/2683/files
|
||||
"customizeSdp": function(jsep) {
|
||||
jsep.sdp = jsep.sdp.replace("useinbandfec=1", "useinbandfec=1;stereo=1");
|
||||
},
|
||||
|
||||
"success": function(jsep) {
|
||||
__logInfo("Got SDP:", jsep);
|
||||
__sendStart(jsep);
|
||||
@@ -376,7 +383,7 @@ export function JanusStreamer(__setActive, __setInactive, __setInfo, __orient, _
|
||||
};
|
||||
|
||||
var __isOnline = function() {
|
||||
return !!(__state && __state.source && __state.source.online);
|
||||
return !!(__state && __state.source.online);
|
||||
};
|
||||
|
||||
var __sendWatch = function() {
|
||||
@@ -428,8 +435,8 @@ JanusStreamer.ensure_janus = function(callback) {
|
||||
callback(true);
|
||||
},
|
||||
});
|
||||
}).catch((err) => {
|
||||
tools.error("Stream: Can't import Janus module:", err);
|
||||
}).catch((ex) => {
|
||||
tools.error("Stream: Can't import Janus module:", ex);
|
||||
callback(false);
|
||||
});
|
||||
} else {
|
||||
|
||||
@@ -117,10 +117,10 @@ export function MjpegStreamer(__setActive, __setInactive, __setInfo) {
|
||||
};
|
||||
|
||||
var __findId = function() {
|
||||
let stream_client = tools.cookies.get("stream_client");
|
||||
if (__id.length === 0 && stream_client && stream_client.startsWith(__key + "/")) {
|
||||
__logInfo("Found acceptable stream_client cookie:", stream_client);
|
||||
__id = stream_client.slice(stream_client.indexOf("/") + 1);
|
||||
let sc = tools.cookies.get("stream_client");
|
||||
if (__id.length === 0 && sc && sc.startsWith(__key + "/")) {
|
||||
__logInfo("Found acceptable stream_client cookie:", sc);
|
||||
__id = sc.slice(sc.indexOf("/") + 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -51,7 +51,7 @@ function __login() {
|
||||
} else {
|
||||
let passwd = $("passwd-input").value + $("code-input").value;
|
||||
let body = `user=${encodeURIComponent(user)}&passwd=${encodeURIComponent(passwd)}`;
|
||||
tools.httpPost("/api/auth/login", function(http) {
|
||||
tools.httpPost("/api/auth/login", null, function(http) {
|
||||
if (http.status === 200) {
|
||||
document.location.href = "/";
|
||||
} else if (http.status === 403) {
|
||||
@@ -59,12 +59,12 @@ function __login() {
|
||||
} else {
|
||||
let error = "";
|
||||
if (http.status === 400) {
|
||||
try { error = JSON.parse(http.responseText)["result"]["error"]; } catch (_) { /* Nah */ }
|
||||
try { error = JSON.parse(http.responseText)["result"]["error"]; } catch { /* Nah */ }
|
||||
}
|
||||
if (error === "ValidatorError") {
|
||||
wm.error("Invalid characters in credentials").then(__tryAgain);
|
||||
} else {
|
||||
wm.error("Login error:<br>", http.responseText).then(__tryAgain);
|
||||
wm.error("Login error", http.responseText).then(__tryAgain);
|
||||
}
|
||||
}
|
||||
}, body, "application/x-www-form-urlencoded");
|
||||
|
||||
@@ -39,7 +39,13 @@ export var tools = new function() {
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.httpRequest = function(method, url, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest = function(method, url, params, callback, body=null, content_type=null, timeout=15000) {
|
||||
if (params) {
|
||||
params = new URLSearchParams(params);
|
||||
if (params) {
|
||||
url += "?" + params;
|
||||
}
|
||||
}
|
||||
let http = new XMLHttpRequest();
|
||||
http.open(method, url, true);
|
||||
if (content_type) {
|
||||
@@ -54,16 +60,27 @@ export var tools = new function() {
|
||||
http.send(body);
|
||||
};
|
||||
|
||||
self.httpGet = function(url, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("GET", url, callback, body, content_type, timeout);
|
||||
self.httpGet = function(url, params, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("GET", url, params, callback, body, content_type, timeout);
|
||||
};
|
||||
|
||||
self.httpPost = function(url, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("POST", url, callback, body, content_type, timeout);
|
||||
self.httpPost = function(url, params, callback, body=null, content_type=null, timeout=15000) {
|
||||
self.httpRequest("POST", url, params, callback, body, content_type, timeout);
|
||||
};
|
||||
|
||||
/************************************************************************/
|
||||
|
||||
self.escape = function(text) {
|
||||
return text.replace(
|
||||
/[^0-9A-Za-z ]/g,
|
||||
ch => "&#" + ch.charCodeAt(0) + ";"
|
||||
);
|
||||
};
|
||||
|
||||
self.partial = function(func, ...args) {
|
||||
return () => func(...args);
|
||||
};
|
||||
|
||||
self.upperFirst = function(text) {
|
||||
return text[0].toUpperCase() + text.slice(1);
|
||||
};
|
||||
@@ -87,6 +104,10 @@ export var tools = new function() {
|
||||
return Math.floor(Math.random() * (max - min + 1)) + min;
|
||||
};
|
||||
|
||||
self.formatHex = function(value) {
|
||||
return `0x${value.toString(16).toUpperCase()}`;
|
||||
};
|
||||
|
||||
self.formatSize = function(size) {
|
||||
if (size > 0) {
|
||||
let index = Math.floor( Math.log(size) / Math.log(1024) );
|
||||
@@ -283,34 +304,18 @@ export var tools = new function() {
|
||||
option.className = "comment";
|
||||
el.add(option);
|
||||
},
|
||||
"addSeparator": function(el) {
|
||||
"addSeparator": function(el, repeat=30) {
|
||||
if (!self.browser.is_mobile) {
|
||||
self.selector.addComment(el, "\u2500".repeat(30));
|
||||
self.selector.addComment(el, "\u2500".repeat(repeat));
|
||||
}
|
||||
},
|
||||
|
||||
"setValues": function(el, values, empty_title=null) {
|
||||
if (values.constructor == Object) {
|
||||
values = Object.keys(values).sort();
|
||||
}
|
||||
let values_json = JSON.stringify(values);
|
||||
if (el.__values_json !== values_json) {
|
||||
el.options.length = 0;
|
||||
for (let value of values) {
|
||||
let title = value;
|
||||
if (title.length === 0 && empty_title !== null) {
|
||||
title = empty_title;
|
||||
}
|
||||
self.selector.addOption(el, title, value);
|
||||
"hasValue": function(el, value) {
|
||||
for (let el_op of el.options) {
|
||||
if (el_op.value === value) {
|
||||
return true;
|
||||
}
|
||||
el.__values_json = values_json;
|
||||
el.__values = values;
|
||||
}
|
||||
},
|
||||
"setSelectedValue": function(el, value) {
|
||||
if (el.__values && el.__values.includes(value)) {
|
||||
el.value = value;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
@@ -31,7 +31,7 @@ export function main() {
|
||||
}
|
||||
|
||||
function __loadKvmdInfo() {
|
||||
tools.httpGet("/api/info", function(http) {
|
||||
tools.httpGet("/api/info", null, function(http) {
|
||||
if (http.status === 200) {
|
||||
let vnc_port = JSON.parse(http.responseText).result.extras.vnc.port;
|
||||
$("vnc-text").innerHTML = `
|
||||
|
||||
@@ -111,8 +111,8 @@ function __WindowManager() {
|
||||
let el_exit_full_tab_button = el_window.querySelector(".window-button-exit-full-tab");
|
||||
if (el_enter_full_tab_button && el_exit_full_tab_button) {
|
||||
el_enter_full_tab_button.title = "Stretch to the entire tab";
|
||||
tools.el.setOnClick(el_enter_full_tab_button, () => self.toggleFullTabWindow(el_window, true));
|
||||
tools.el.setOnClick(el_exit_full_tab_button, () => self.toggleFullTabWindow(el_window, false));
|
||||
tools.el.setOnClick(el_enter_full_tab_button, () => self.setFullTabWindow(el_window, true));
|
||||
tools.el.setOnClick(el_exit_full_tab_button, () => self.setFullTabWindow(el_window, false));
|
||||
}
|
||||
|
||||
let el_full_screen_button = el_window.querySelector(".window-header .window-button-full-screen");
|
||||
@@ -145,10 +145,10 @@ function __WindowManager() {
|
||||
/************************************************************************/
|
||||
|
||||
self.copyTextToClipboard = function(text) {
|
||||
let workaround = function(err) {
|
||||
let workaround = function(ex) {
|
||||
// https://stackoverflow.com/questions/60317969/document-execcommandcopy-not-working-even-though-the-dom-element-is-created
|
||||
let callback = function() {
|
||||
tools.error("copyTextToClipboard(): navigator.clipboard.writeText() is not working:", err);
|
||||
__modalDialog("Info", "Press OK to copy the text to the clipboard", true, false).then(function() {
|
||||
tools.error("copyTextToClipboard(): navigator.clipboard.writeText() is not working:", ex);
|
||||
tools.info("copyTextToClipboard(): Trying a workaround...");
|
||||
|
||||
let el = document.createElement("textarea");
|
||||
@@ -164,36 +164,46 @@ function __WindowManager() {
|
||||
el.setSelectionRange(0, el.value.length); // iOS
|
||||
|
||||
try {
|
||||
err = (document.execCommand("copy") ? null : "Unknown error");
|
||||
} catch (err) { // eslint-disable-line no-empty
|
||||
ex = (document.execCommand("copy") ? null : "Unknown error");
|
||||
} catch (ex) { // eslint-disable-line no-unused-vars
|
||||
}
|
||||
|
||||
// Remove the added textarea again:
|
||||
document.body.removeChild(el);
|
||||
|
||||
if (err) {
|
||||
tools.error("copyTextToClipboard(): Workaround failed:", err);
|
||||
wm.error("Can't copy text to the clipboard:<br>", err);
|
||||
if (ex) {
|
||||
tools.error("copyTextToClipboard(): Workaround failed:", ex);
|
||||
self.error("Can't copy text to the clipboard", `${ex}`);
|
||||
}
|
||||
};
|
||||
__modalDialog("Info", "Press OK to copy the text to the clipboard", true, false, callback);
|
||||
});
|
||||
};
|
||||
if (navigator.clipboard) {
|
||||
navigator.clipboard.writeText(text).then(function() {
|
||||
wm.info("The text has been copied to the clipboard");
|
||||
}, function(err) {
|
||||
workaround(err);
|
||||
self.info("The text has been copied to the clipboard");
|
||||
}, function(ex) {
|
||||
workaround(ex);
|
||||
});
|
||||
} else {
|
||||
workaround("navigator.clipboard is not available");
|
||||
}
|
||||
};
|
||||
|
||||
self.info = (...args) => __modalDialog("Info", args.join(" "), true, false);
|
||||
self.error = (...args) => __modalDialog("Error", args.join(" "), true, false);
|
||||
self.confirm = (...args) => __modalDialog("Question", args.join(" "), true, true);
|
||||
self.info = (html, ...args) => __modalCodeDialog("Info", html, args.join("\n"), true, false);
|
||||
self.error = (html, ...args) => __modalCodeDialog("Error", html, args.join("\n"), true, false);
|
||||
self.confirm = (html, ...args) => __modalCodeDialog("Question", html, args.join("\n"), true, true);
|
||||
self.modal = (header, html, ok, cancel) => __modalDialog(header, html, ok, cancel);
|
||||
|
||||
var __modalDialog = function(header, text, ok, cancel, callback=null, parent=null) {
|
||||
var __modalCodeDialog = function(header, html, code, ok, cancel) {
|
||||
let create_content = function(el_content) {
|
||||
if (code) {
|
||||
html += `<br><br><div class="code"><pre style="margin:0px">${tools.escape(code)}</pre></div>`;
|
||||
}
|
||||
el_content.innerHTML = html;
|
||||
};
|
||||
return __modalDialog(header, create_content, ok, cancel);
|
||||
};
|
||||
|
||||
var __modalDialog = function(header, html, ok, cancel, parent=null) {
|
||||
let el_active_menu = (document.activeElement && document.activeElement.closest(".menu"));
|
||||
|
||||
let el_modal = document.createElement("div");
|
||||
@@ -207,27 +217,50 @@ function __WindowManager() {
|
||||
|
||||
let el_header = document.createElement("div");
|
||||
el_header.className = "modal-header";
|
||||
el_header.innerHTML = header;
|
||||
el_header.innerText = header;
|
||||
el_window.appendChild(el_header);
|
||||
|
||||
let el_content = document.createElement("div");
|
||||
el_content.className = "modal-content";
|
||||
el_content.innerHTML = text;
|
||||
el_window.appendChild(el_content);
|
||||
|
||||
let el_buttons = document.createElement("div");
|
||||
el_buttons.classList.add("modal-buttons", "buttons-row");
|
||||
el_window.appendChild(el_buttons);
|
||||
|
||||
let el_cancel_button = null;
|
||||
let el_ok_button = null;
|
||||
if (cancel) {
|
||||
el_cancel_button = document.createElement("button");
|
||||
el_cancel_button.className = "row100";
|
||||
el_cancel_button.innerText = "Cancel";
|
||||
el_buttons.appendChild(el_cancel_button);
|
||||
}
|
||||
if (ok) {
|
||||
el_ok_button = document.createElement("button");
|
||||
el_ok_button.className = "row100";
|
||||
el_ok_button.innerText = "OK";
|
||||
el_buttons.appendChild(el_ok_button);
|
||||
}
|
||||
if (ok && cancel) {
|
||||
el_ok_button.className = "row50";
|
||||
el_cancel_button.className = "row50";
|
||||
}
|
||||
|
||||
el_window.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (ok && event.code === "Enter") {
|
||||
el_ok_button.click();
|
||||
} else if (cancel && event.code === "Escape") {
|
||||
el_cancel_button.click();
|
||||
}
|
||||
};
|
||||
|
||||
let promise = null;
|
||||
if (ok || cancel) {
|
||||
promise = new Promise(function(resolve) {
|
||||
let el_buttons = document.createElement("div");
|
||||
el_buttons.className = "modal-buttons";
|
||||
el_window.appendChild(el_buttons);
|
||||
|
||||
function close(retval) {
|
||||
if (callback) {
|
||||
callback(retval);
|
||||
}
|
||||
__closeWindow(el_window);
|
||||
el_modal.outerHTML = "";
|
||||
let index = __windows.indexOf(el_modal);
|
||||
if (index !== -1) {
|
||||
__windows.splice(index, 1);
|
||||
@@ -238,38 +271,27 @@ function __WindowManager() {
|
||||
__activateLastWindow(el_modal);
|
||||
}
|
||||
resolve(retval);
|
||||
// Так как resolve() асинхронный, надо выполнить в эвентлупе после него
|
||||
setTimeout(function() { el_modal.outerHTML = ""; }, 0);
|
||||
}
|
||||
|
||||
if (cancel) {
|
||||
var el_cancel_button = document.createElement("button");
|
||||
el_cancel_button.innerHTML = "Cancel";
|
||||
tools.el.setOnClick(el_cancel_button, () => close(false));
|
||||
el_buttons.appendChild(el_cancel_button);
|
||||
}
|
||||
if (ok) {
|
||||
var el_ok_button = document.createElement("button");
|
||||
el_ok_button.innerHTML = "OK";
|
||||
tools.el.setOnClick(el_ok_button, () => close(true));
|
||||
el_buttons.appendChild(el_ok_button);
|
||||
}
|
||||
if (ok && cancel) {
|
||||
el_ok_button.className = "row50";
|
||||
el_cancel_button.className = "row50";
|
||||
}
|
||||
|
||||
el_window.onkeyup = function(event) {
|
||||
event.preventDefault();
|
||||
if (ok && event.code === "Enter") {
|
||||
el_ok_button.click();
|
||||
} else if (cancel && event.code === "Escape") {
|
||||
el_cancel_button.click();
|
||||
}
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
__windows.push(el_modal);
|
||||
(parent || document.fullscreenElement || document.body).appendChild(el_modal);
|
||||
if (typeof html === "function") {
|
||||
// Это должно быть здесь, потому что элемент должен иметь родителя чтобы существовать
|
||||
html(el_content, el_ok_button);
|
||||
} else {
|
||||
el_content.innerHTML = html;
|
||||
}
|
||||
__activateWindow(el_modal);
|
||||
|
||||
return promise;
|
||||
@@ -312,7 +334,7 @@ function __WindowManager() {
|
||||
__activateLastWindow(el_window);
|
||||
};
|
||||
|
||||
self.toggleFullTabWindow = function(el_window, enabled) {
|
||||
self.setFullTabWindow = function(el_window, enabled) {
|
||||
el_window.classList.toggle("window-full-tab", enabled);
|
||||
__activateLastWindow(el_window);
|
||||
let el_navbar = $("navbar");
|
||||
@@ -625,7 +647,7 @@ function __WindowManager() {
|
||||
+ "In Chrome use HTTPS and enable <i>system-keyboard-lock</i><br>"
|
||||
+ "by putting at URL <i>chrome://flags/#system-keyboard-lock</i>"
|
||||
);
|
||||
__modalDialog("Keyboard lock is unsupported", msg, true, false, null, el_window);
|
||||
__modalDialog("Keyboard lock is unsupported", msg, true, false, el_window);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user