diff options
author | Devaev Maxim <[email protected]> | 2018-07-21 08:36:44 +0300 |
---|---|---|
committer | Devaev Maxim <[email protected]> | 2018-07-21 08:36:44 +0300 |
commit | ad8fb657add36da7897347472488b7d5777b82c7 (patch) | |
tree | 8a94b9050d4a5c051382f07b4bff54a4d725bbf4 /kvmd | |
parent | a4f4d281b247349f100f3cd131c79e311c53b3f8 (diff) |
msd ui
Diffstat (limited to 'kvmd')
-rw-r--r-- | kvmd/kvmd/msd.py | 5 | ||||
-rw-r--r-- | kvmd/kvmd/server.py | 12 | ||||
-rw-r--r-- | kvmd/web/css/main.css | 122 | ||||
-rw-r--r-- | kvmd/web/index.html | 87 | ||||
-rw-r--r-- | kvmd/web/js/msd.js | 118 | ||||
-rw-r--r-- | kvmd/web/svg/info.svg | 7 | ||||
-rw-r--r-- | kvmd/web/svg/warning.svg | 36 |
7 files changed, 341 insertions, 46 deletions
diff --git a/kvmd/kvmd/msd.py b/kvmd/kvmd/msd.py index 988a1c30..0746a6f6 100644 --- a/kvmd/kvmd/msd.py +++ b/kvmd/kvmd/msd.py @@ -176,6 +176,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes self.__loop = loop self.__device_info: Optional[_MassStorageDeviceInfo] = None + self.__saved_device_info: Optional[_MassStorageDeviceInfo] = None self.__region = aioregion.AioExclusiveRegion(MsdIsBusyError) self.__device_file: Optional[aiofiles.base.AiofilesContextManager] = None self.__written = 0 @@ -217,7 +218,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes get_logger().info("Mass-storage device switched to Server") def get_state(self) -> Dict: - info = (self.__device_info._asdict() if self.__device_info else None) + info = (self.__saved_device_info._asdict() if self.__saved_device_info else None) if info: info["hw"] = (info["hw"]._asdict() if info["hw"] else None) info["image"] = (info["image"]._asdict() if info["image"] else None) @@ -283,7 +284,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes device_info = await self.__loop.run_in_executor(None, _explore_device, self._device_path) if not device_info: raise MsdError("Can't explore device %r" % (self._device_path)) - self.__device_info = device_info + self.__device_info = self.__saved_device_info = device_info async def __close_device_file(self) -> None: try: diff --git a/kvmd/kvmd/server.py b/kvmd/kvmd/server.py index 17eb94c3..5eaaaba6 100644 --- a/kvmd/kvmd/server.py +++ b/kvmd/kvmd/server.py @@ -40,10 +40,14 @@ def _system_task(method: Callable) -> Callable: def _json(result: Optional[Dict]=None, status: int=200) -> aiohttp.web.Response: - return aiohttp.web.json_response({ - "ok": (True if status == 200 else False), - "result": (result or {}), - }, status=status) + return aiohttp.web.Response( + text=json.dumps({ + "ok": (True if status == 200 else False), + "result": (result or {}), + }, sort_keys=True, indent=4), + status=status, + content_type="application/json", + ) def _json_exception(msg: str, err: Exception, status: int) -> aiohttp.web.Response: diff --git a/kvmd/web/css/main.css b/kvmd/web/css/main.css index 45e4db45..71e0eefd 100644 --- a/kvmd/web/css/main.css +++ b/kvmd/web/css/main.css @@ -1,15 +1,13 @@ body { - margin: 0px; + margin: 0; color: #c3c3c3; background-color: #36393f; - font-family: sans-serif!important; + font-family: sans-serif !important; } img#logo { - height: 24px; -webkit-filter: invert(0.7); filter: invert(0.7); vertical-align: middle; - margin-right: 10px; padding: 13px 15px; } div.centered { @@ -22,7 +20,7 @@ div.centered { } ul#ctl { - box-shadow: 0 2px 4px rgba(0,0,0,0.2); + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.2); list-style-type: none; margin: 0; padding: 0; @@ -38,10 +36,10 @@ ul#ctl li a:hover:not(.active) { ul#ctl li.ctl-left { float: left; } -ul#ctl li.ctl-left-sep:last-child { +/*ul#ctl li.ctl-left-sep:last-child { float: left; border-right: 1px solid black; -} +}*/ ul#ctl li.ctl-right { float: right; } @@ -49,11 +47,11 @@ ul#ctl li.ctl-right-sep:not(last-child) { float: right; border-left: 1px solid black; } -ul#ctl p { +/*ul#ctl p { color: #c3c3c3; - margin: 0px; + margin: 0; padding: 15px 16px; -} +}*/ ul#ctl img { vertical-align: middle; margin-right: 10px; @@ -73,14 +71,14 @@ div.ctl-dropdown { display: inline-block; } div.ctl-dropdown-content { - white-space:nowrap; + white-space: nowrap; border: 1px solid #17191d; border-radius: 0 0 8px 8px; display: none; position: absolute; background-color: #282b30; - min-width: 160px; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.4); + min-width: 180px; + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.4); z-index: 9; } div.ctl-dropdown:hover .ctl-dropdown-content { @@ -91,14 +89,16 @@ div.ctl-dropdown:hover .ctl-item { } div.ctl-dropdown-content button { box-shadow: none; - border: 0; + border: none; color: #c3c3c3; - background-color: #282b30; + background-color: #36393f; display: block; width: 100%; height: 30px; font-size: 16px; text-align: left; + padding: 0 15px; + outline: none; cursor: pointer; } div.ctl-dropdown-content button:last-child { @@ -106,26 +106,62 @@ div.ctl-dropdown-content button:last-child { } div.ctl-dropdown-content button:enabled:hover { color: white; - background-color: #17191d!important; + background-color: #17191d !important; } div.ctl-dropdown-content button:disabled { - color: #36393f; - cursor: wait; + color: #6c7481; + cursor: default; +} +div.ctl-dropdown-content button:active { + color: #5e646e !important; +} +div.ctl-dropdown-content button.first { + display: inline-block; + width: 50%; + /*border-right: 1px solid #17191d;*/ + border-radius: 0 !important; +} +div.ctl-dropdown-content button.second-half-first { + display: inline-block; + width: 25%; + border-left: 1px solid #17191d; + border-radius: 0 !important; +} +div.ctl-dropdown-content button.second-half-second { + display: inline-block; + width: 25%; + border-left: 1px solid #17191d; + border-radius: 0 !important; +} +div.ctl-dropdown-content button.first-bottom { + display: inline-block; + width: 50%; + border-radius: 0 0 0 8px !important; +} +div.ctl-dropdown-content button.second-bottom { + display: inline-block; + width: 50%; + border-left: 1px solid #17191d; + border-radius: 0 0 8px 0 !important; } div.ctl-dropdown-content hr { - margin: 0px; + margin: 0; display: block; height: 0; border: 0; border-top: 1px solid #17191d; padding: 0; } +div.ctl-dropdown-content-text { + margin: 10px 15px 10px 15px; + font-size: 14px; +} div#stream-box { border: 1px solid #17191d; border-radius: 8px; box-sizing: border-box; - box-shadow: 0px 8px 16px 0px rgba(0,0,0,0.4); + box-shadow: 0 8px 16px 0 rgba(0, 0, 0, 0.4); display: inline-block; background-color: #484b51; padding: 10px; @@ -148,13 +184,6 @@ img.stream-image-inactive { filter: grayscale(100%); } -img#atx-power-led, -img#hid-mouse-led, -img#msd-led, -img#atx-hdd-led, -img#stream-led, -img#hid-keyboard-led { -} img.led-on { -webkit-filter: invert(0.5) sepia(1) saturate(5) hue-rotate(100deg); filter: invert(0.5) sepia(1) saturate(5) hue-rotate(100deg); @@ -177,3 +206,42 @@ img.led-msd-writing { @-moz-keyframes spin { 100% { -moz-transform: rotate(360deg); } } @-webkit-keyframes spin { 100% { -webkit-transform: rotate(360deg); } } @keyframes spin { 100% { -webkit-transform: rotate(360deg); transform:rotate(360deg); } } + +table#msd-info { + border-spacing: 5px; + margin: 0 10px 0 10px; + font-size: 14px; +} +table#msd-info +td#msd-current-image-name, +td#msd-current-image-size, +td#msd-storage-device, +td#msd-storage-size, +td#msd-new-image-name, +td#msd-new-image-size { + font-weight: bold; + max-width: 330px; + overflow: hidden; +} + +div#msd-progress { + background-color: #111; + height: 1.5em; + width: 100%; + position: relative; +} +div#msd-progress:before { + color: white; + content: attr(data-label); + font-size: 0.8em; + position: absolute; + text-align: center; + top: 4px; + left: 0; + right: 0; +} +div#msd-progress span#msd-progress-value { + background-color: #436a8a; + display: inline-block; + height: 100%; +} diff --git a/kvmd/web/index.html b/kvmd/web/index.html index 4812f105..3af08b90 100644 --- a/kvmd/web/index.html +++ b/kvmd/web/index.html @@ -31,7 +31,7 @@ System ↴ </a> <div class="ctl-dropdown-content"> - <button id="stream-reset-button" onclick="stream.clickResetButton(this);"href="#">Reset stream</button> + <button disabled id="stream-reset-button" onclick="stream.clickResetButton(this);">• Reset stream</button> </div> </div> </li> @@ -44,10 +44,10 @@ ATX ↴ </a> <div class="ctl-dropdown-content"> - <button id="atx-power-button" onclick="atx.clickButton(this);" href="#">Click Power <sup><i>short</i></sup></button> - <button id="atx-power-button-long" onclick="atx.clickButton(this);" href="#">Click Power <sup><i>long</i></sup></button> + <button disabled id="atx-power-button" onclick="atx.clickButton(this);">• Click Power <sup><i>short</i></sup></button> + <button disabled id="atx-power-button-long" onclick="atx.clickButton(this);">• Click Power <sup><i>long</i></sup></button> <hr> - <button id="atx-reset-button" onclick="atx.clickButton(this);"href="#">Click Reset</button> + <button disabled id="atx-reset-button" onclick="atx.clickButton(this);">• Click Reset</button> </div> </div> </li> @@ -59,9 +59,82 @@ Mass Storage ↴ </a> <div class="ctl-dropdown-content"> - О, привет!<br> - К сожалению, я пока не реализовал эту функцию,<br> - но ты можешь полюбоваться на эту великолепную менюшку :3 + <div id="msd-not-in-operate" style="display:none"> + <div class="ctl-dropdown-content-text"> + <table> + <tr> + <td><img src="svg/warning.svg" /></td> + <td><b>Mass Storage Device is not operational</b></td> + </tr> + </table> + </div> + <hr> + </div> + + <div id="msd-current-image-broken" style="display:none"> + <div class="ctl-dropdown-content-text"> + <table> + <tr> + <td><img src="svg/warning.svg" /></td> + <td><b>Current image is broken!</b><br><sub>Perhaps uploading was interrupted</sub></td> + </tr> + </table> + </div> + <hr> + </div> + + <table id="msd-info"> + <tr> + <td>Image name:</td> + <td id="msd-current-image-name"></td> + </tr> + <tr> + <td>Image size:</td> + <td id="msd-current-image-size"></td> + </tr> + <td>Storage size:</td> + <td id="msd-storage-size"></td> + </tr> + <tr> + <td>KVM Device:</td> + <td id="msd-storage-device"></td> + </tr> + </table> + <hr> + + + + <input type="file" id="msd-select-new-image-file" style="display:none;" onchange="msd.selectNewImageFile(this)" /> + <button disabled id="msd-select-new-image-button" class="first" onclick="document.getElementById('msd-select-new-image-file').click();">• Upload new image</button><button disabled id="msd-upload-new-image-button" class="second-half-first" onclick="msd.clickButton(this);">• Start</button><button disabled id="msd-abort-uploading-button" class="second-half-second" onclick="msd.clickButton(this);">• Abort</button> + <hr> + + + + <div id="msd-new-image" style="display:none"> + <table id="msd-info"> + <tr> + <td>Image name:</td> + <td id="msd-new-image-name"></td> + </tr> + <tr> + <td>Upload size:</td> + <td id="msd-new-image-size"></td> + </tr> + </table> + <hr> + <div class="ctl-dropdown-content-text"> + <div id="msd-progress"> + <span id="msd-progress-value"></span> + </div> + </div> + <hr> + <!--<div class="ctl-dropdown-content-text"> + <img src="svg/info.svg" /><b>You can't switch Mass Storage Device while uploading</b><br> + </div> + <hr>--> + </div> + + <button disabled id="msd-switch-to-kvm-button" class="first-bottom" onclick="msd.clickButton(this);">• Switch drive to KVM</button><button disabled id="msd-switch-to-server-button" class="second-bottom" onclick="msd.clickButton(this);">• Switch drive to Server</button> </div> </div> </li> diff --git a/kvmd/web/js/msd.js b/kvmd/web/js/msd.js index 5ae851ea..073d3069 100644 --- a/kvmd/web/js/msd.js +++ b/kvmd/web/js/msd.js @@ -1,4 +1,8 @@ var msd = new function() { + var __state = null; + var __upload_http = null; + var __image_file = null; + this.loadInitialState = function() { var http = tools.makeRequest("GET", "/kvmd/msd", function() { if (http.readyState === 4) { @@ -12,13 +16,115 @@ var msd = new function() { }; this.setState = function(state) { - if (state.connected_to == "server") { - cls = "led-on"; - } else if (state.busy) { - cls = "led-msd-writing"; + __state = state; + console.log(state); + __applyState(); + }; + + this.clickButton = function(el_button) { + if (el_button.id === "msd-upload-new-image-button") { + var form_data = new FormData(); + form_data.append("image_name", __image_file.name); + form_data.append("image_data", __image_file); + + __upload_http = new XMLHttpRequest(); + __upload_http.open("POST", "/kvmd/msd/write", true); + __upload_http.upload.timeout = 5000; + __upload_http.onreadystatechange = __uploadStateChange; + __upload_http.upload.onprogress = __uploadProgress; + __upload_http.send(form_data); + + } else if (el_button.id === "msd-abort-uploading-button") { + __upload_http.onreadystatechange = null; + __upload_http.upload.onprogress = null; + __upload_http.abort(); + __upload_http = null; + $("msd-progress").setAttribute("data-label", "Aborted"); + $("msd-progress-value").style.width = "0%"; + msd.loadInitialState(); + + } else if (el_button.id === "msd-switch-to-kvm-button" || el_button.id === "msd-switch-to-server-button") { + var to = (el_button.id === "msd-switch-to-kvm-button" ? "kvm" : "server"); + var http = tools.makeRequest("POST", "/kvmd/msd/connect?to=" + to, function() { + if (http.readyState === 4) { + if (http.status !== 200) { + alert("Switch error:", http.responseText); + } + } + __applyState(); + }); + __applyState(); + el_button.disabled = true; + } + }; + + this.selectNewImageFile = function(el_input) { + var image_file = (el_input.files.length ? el_input.files[0] : null); + if (image_file && image_file.size > __state.info.size) { + alert("New image is too big for your Mass Storage Device; maximum: " + __formatSize(__state.info.size)); + el_input.value = ""; + image_file = null; + } + __image_file = image_file; + $("msd-new-image").style.display = (__image_file ? "block" : "none"); + $("msd-progress").setAttribute("data-label", "Waiting for upload ..."); + $("msd-progress-value").style.width = "0%"; + $("msd-new-image-name").innerHTML = (__image_file ? __image_file.name : ""); + $("msd-new-image-size").innerHTML = (__image_file ? __formatSize(__image_file.size) : ""); + __applyState(); + }; + + var __applyState = function() { + if (__state.connected_to === "server") { + $("msd-led").className = "led-on"; + } else if (__state.busy) { + $("msd-led").className = "led-msd-writing"; } else { - cls = "led-off"; + $("msd-led").className = "led-off"; + } + + $("msd-not-in-operate").style.display = (__state.in_operate ? "none" : "block"); + $("msd-current-image-broken").style.display = ((__state.info.image && !__state.info.image.complete) ? "block" : "none"); + + $("msd-current-image-name").innerHTML = (__state.info.image ? __state.info.image.name : "None"); + $("msd-current-image-size").innerHTML = (__state.info.image ? __formatSize(__state.info.image.size) : "None"); + $("msd-storage-size").innerHTML = (__state.in_operate ? __formatSize(__state.info.size) : "Unavailable"); + $("msd-storage-device").innerHTML = (__state.in_operate ? __state.info.real : "Missing"); + + $("msd-switch-to-kvm-button").disabled = (!__state.in_operate || __state.connected_to === "kvm" || __state.busy); + $("msd-switch-to-server-button").disabled = (!__state.in_operate || __state.connected_to === "server" || __state.busy); + $("msd-select-new-image-button").disabled = (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || __upload_http); + $("msd-upload-new-image-button").disabled = (!__state.in_operate || __state.connected_to !== "kvm" || __state.busy || !__image_file); + $("msd-abort-uploading-button").disabled = (!__state.in_operate || !__upload_http); + }; + + var __formatSize = function(size) { + if (size > 0) { + var index = Math.floor( Math.log(size) / Math.log(1024) ); + return (size / Math.pow(1024, index)).toFixed(2) * 1 + " " + ["B", "kB", "MB", "GB", "TB"][index]; + } else { + return 0; + } + }; + + var __uploadStateChange = function(event) { + if (__upload_http.readyState === 4) { + if (__upload_http.status !== 200) { + alert("Can't upload image to the Mass Storage Device:", __upload_http.responseText); + } else { + $("msd-progress").setAttribute("data-label", "Done"); + $("msd-progress-value").style.width = "100%"; + } + __upload_http = null; + __applyState(); + } + }; + + var __uploadProgress = function(event) { + if(event.lengthComputable) { + var percent = Math.round((event.loaded * 100) / event.total); + $("msd-progress").setAttribute("data-label", percent + "%"); + $("msd-progress-value").style.width = percent + "%"; } - $("msd-led").className = cls; }; }; diff --git a/kvmd/web/svg/info.svg b/kvmd/web/svg/info.svg new file mode 100644 index 00000000..b28139d5 --- /dev/null +++ b/kvmd/web/svg/info.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 48 48"> + <circle cy="24" cx="24" r="24" fill="#36c"/> + <g fill="#fff"> + <circle cx="24" cy="11.6" r="4.7"/> + <path d="m17.4 18.8v2.15h1.13c2.26 0 2.26 1.38 2.26 1.38v15.1s0 1.38-2.26 1.38h-1.13v2.08h14.2v-2.08h-1.13c-2.26 0-2.26-1.38-2.26-1.38v-18.6"/> + </g> +</svg> diff --git a/kvmd/web/svg/warning.svg b/kvmd/web/svg/warning.svg new file mode 100644 index 00000000..3137b24d --- /dev/null +++ b/kvmd/web/svg/warning.svg @@ -0,0 +1,36 @@ +<?xml version="1.0" encoding="utf-8"?>
+<!-- Generator: Adobe Illustrator 17.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
+<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
+<svg version="1.0" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
+ width="265.4766846px" height="233.2780151px" viewBox="0 0 265.4766846 233.2780151"
+ enable-background="new 0 0 265.4766846 233.2780151" xml:space="preserve">
+<g id="Icon">
+ <g id="Signboard">
+ <path fill="#010100" d="M12.5276556,233.2779999c-4.4623718,0-8.6218128-2.401001-10.8545284-6.2661896
+ c-2.2306759-3.8661957-2.2306759-8.6682129-0.00102-12.5328827L121.883812,6.2666974
+ c2.2327194-3.865689,6.3911362-6.2667003,10.8545303-6.2667003s8.6218109,2.401011,10.8545227,6.2661901l120.2106934,208.2117157
+ c2.2306824,3.8656921,2.2306824,8.6677094,0.0010071,12.5323792c-2.2337341,3.8667145-6.393158,6.2677155-10.8555298,6.2677155
+ H12.5276556z"/>
+ <path fill="#FFDB00" d="M12.5274048,223.2783966c-0.8886719,0-1.75-0.4980469-2.1962891-1.2695313
+ c-0.4414063-0.765625-0.4414063-1.7626953,0.0029297-2.5341797L130.5440063,11.2666779
+ c0.4443359-0.769043,1.3056641-1.2666016,2.1943359-1.2666016s1.75,0.4975586,2.1953125,1.2675781l120.2099609,208.2099609
+ c0.4433594,0.7685547,0.4433594,1.7646484-0.0009766,2.5351563c-0.4423828,0.7666016-1.3046875,1.265625-2.1933594,1.265625
+ H12.5274048z"/>
+ </g>
+ <g id="Graphic">
+ <path id="path37169" d="M117.9776917,75.1996231c0-16.8840446,29.5213013-16.7991524,29.5213013,0l-7.7432861,88.7607346
+ c0,6.9947052-13.7927399,7.0144653-13.7927399,0L117.9776917,75.1996231z"/>
+ <ellipse cx="132.7383423" cy="189.3726196" rx="14.7001534" ry="15.0053892"/>
+ </g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+<g>
+</g>
+</svg>
|