mirror of
https://github.com/mofeng-git/One-KVM.git
synced 2025-12-12 01:00:29 +08:00
exclusive region-based access for soma apis
This commit is contained in:
parent
9e1f9ae853
commit
e118d270df
33
kvmd/kvmd/aioregion.py
Normal file
33
kvmd/kvmd/aioregion.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import types
|
||||||
|
|
||||||
|
from typing import Type
|
||||||
|
|
||||||
|
|
||||||
|
# =====
|
||||||
|
class RegionIsBusyError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class AioExclusiveRegion:
|
||||||
|
def __init__(self) -> None:
|
||||||
|
self.__busy = False
|
||||||
|
|
||||||
|
def enter(self) -> None:
|
||||||
|
if not self.__busy:
|
||||||
|
self.__busy = True
|
||||||
|
return
|
||||||
|
raise RegionIsBusyError()
|
||||||
|
|
||||||
|
def exit(self) -> None:
|
||||||
|
self.__busy = False
|
||||||
|
|
||||||
|
def __enter__(self) -> None:
|
||||||
|
self.enter()
|
||||||
|
|
||||||
|
def __exit__(
|
||||||
|
self,
|
||||||
|
_exc_type: Type[BaseException],
|
||||||
|
_exc: BaseException,
|
||||||
|
_tb: types.TracebackType,
|
||||||
|
) -> None:
|
||||||
|
self.exit()
|
||||||
@ -4,6 +4,7 @@ from typing import Dict
|
|||||||
|
|
||||||
from .logging import get_logger
|
from .logging import get_logger
|
||||||
|
|
||||||
|
from . import aioregion
|
||||||
from . import gpio
|
from . import gpio
|
||||||
|
|
||||||
|
|
||||||
@ -28,7 +29,7 @@ class Atx:
|
|||||||
self.__click_delay = click_delay
|
self.__click_delay = click_delay
|
||||||
self.__long_click_delay = long_click_delay
|
self.__long_click_delay = long_click_delay
|
||||||
|
|
||||||
self.__lock = asyncio.Lock()
|
self.__region = aioregion.AioExclusiveRegion()
|
||||||
|
|
||||||
def get_state(self) -> Dict:
|
def get_state(self) -> Dict:
|
||||||
return {
|
return {
|
||||||
@ -39,22 +40,19 @@ class Atx:
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def click_power(self) -> None:
|
async def click_power(self) -> None:
|
||||||
if (await self.__click(self.__power_switch, self.__click_delay)):
|
await self.__click(self.__power_switch, self.__click_delay)
|
||||||
get_logger().info("Clicked power")
|
get_logger().info("Clicked power")
|
||||||
|
|
||||||
async def click_power_long(self) -> None:
|
async def click_power_long(self) -> None:
|
||||||
if (await self.__click(self.__power_switch, self.__long_click_delay)):
|
await self.__click(self.__power_switch, self.__long_click_delay)
|
||||||
get_logger().info("Clicked power (long press)")
|
get_logger().info("Clicked power (long press)")
|
||||||
|
|
||||||
async def click_reset(self) -> None:
|
async def click_reset(self) -> None:
|
||||||
if (await self.__click(self.__reset_switch, self.__click_delay)):
|
await self.__click(self.__reset_switch, self.__click_delay)
|
||||||
get_logger().info("Clicked reset")
|
get_logger().info("Clicked reset")
|
||||||
|
|
||||||
async def __click(self, pin: int, delay: float) -> bool:
|
async def __click(self, pin: int, delay: float) -> None:
|
||||||
if not self.__lock.locked():
|
with self.__region:
|
||||||
async with self.__lock:
|
for flag in (True, False):
|
||||||
for flag in (True, False):
|
gpio.write(pin, flag)
|
||||||
gpio.write(pin, flag)
|
await asyncio.sleep(delay)
|
||||||
await asyncio.sleep(delay)
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|||||||
@ -15,6 +15,8 @@ import pyudev
|
|||||||
import aiofiles
|
import aiofiles
|
||||||
import aiofiles.base
|
import aiofiles.base
|
||||||
|
|
||||||
|
from . import aioregion
|
||||||
|
|
||||||
from .logging import get_logger
|
from .logging import get_logger
|
||||||
|
|
||||||
|
|
||||||
@ -151,14 +153,13 @@ def _explore_device(device_path: str) -> Optional[_MassStorageDeviceInfo]:
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def _operated_and_locked(method: Callable) -> Callable:
|
def _msd_operated(method: Callable) -> Callable:
|
||||||
async def wrap(self: "MassStorageDevice", *args: Any, **kwargs: Any) -> Any:
|
async def wrap(self: "MassStorageDevice", *args: Any, **kwargs: Any) -> Any:
|
||||||
if self._device_file: # pylint: disable=protected-access
|
if self._device_file: # pylint: disable=protected-access
|
||||||
raise IsBusyError()
|
raise IsBusyError()
|
||||||
if not self._device_path: # pylint: disable=protected-access
|
if not self._device_path: # pylint: disable=protected-access
|
||||||
IsNotOperationalError()
|
IsNotOperationalError()
|
||||||
async with self._lock: # pylint: disable=protected-access
|
return (await method(self, *args, **kwargs))
|
||||||
return (await method(self, *args, **kwargs))
|
|
||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
@ -178,7 +179,7 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
|
|||||||
self.__loop = loop
|
self.__loop = loop
|
||||||
|
|
||||||
self.__device_info: Optional[_MassStorageDeviceInfo] = None
|
self.__device_info: Optional[_MassStorageDeviceInfo] = None
|
||||||
self._lock = asyncio.Lock()
|
self.__region = aioregion.AioExclusiveRegion()
|
||||||
self._device_file: Optional[aiofiles.base.AiofilesContextManager] = None
|
self._device_file: Optional[aiofiles.base.AiofilesContextManager] = None
|
||||||
self.__written = 0
|
self.__written = 0
|
||||||
|
|
||||||
@ -198,23 +199,25 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
|
|||||||
else:
|
else:
|
||||||
logger.warning("Mass-storage device is not operational")
|
logger.warning("Mass-storage device is not operational")
|
||||||
|
|
||||||
@_operated_and_locked
|
@_msd_operated
|
||||||
async def connect_to_kvm(self, no_delay: bool=False) -> None:
|
async def connect_to_kvm(self, no_delay: bool=False) -> None:
|
||||||
if self.__device_info:
|
with self.__region:
|
||||||
raise AlreadyConnectedToKvmError()
|
if self.__device_info:
|
||||||
# TODO: disable gpio
|
raise AlreadyConnectedToKvmError()
|
||||||
if not no_delay:
|
# TODO: disable gpio
|
||||||
await asyncio.sleep(self.__init_delay)
|
if not no_delay:
|
||||||
await self.__load_device_info()
|
await asyncio.sleep(self.__init_delay)
|
||||||
get_logger().info("Mass-storage device switched to KVM: %s", self.__device_info)
|
await self.__load_device_info()
|
||||||
|
get_logger().info("Mass-storage device switched to KVM: %s", self.__device_info)
|
||||||
|
|
||||||
@_operated_and_locked
|
@_msd_operated
|
||||||
async def connect_to_pc(self) -> None:
|
async def connect_to_pc(self) -> None:
|
||||||
if not self.__device_info:
|
with self.__region:
|
||||||
raise AlreadyConnectedToPcError()
|
if not self.__device_info:
|
||||||
# TODO: enable gpio
|
raise AlreadyConnectedToPcError()
|
||||||
self.__device_info = None
|
# TODO: enable gpio
|
||||||
get_logger().info("Mass-storage device switched to Server")
|
self.__device_info = None
|
||||||
|
get_logger().info("Mass-storage device switched to Server")
|
||||||
|
|
||||||
def get_state(self) -> Dict:
|
def get_state(self) -> Dict:
|
||||||
info = (self.__device_info._asdict() if self.__device_info else None)
|
info = (self.__device_info._asdict() if self.__device_info else None)
|
||||||
@ -230,36 +233,34 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
|
|||||||
}
|
}
|
||||||
|
|
||||||
async def cleanup(self) -> None:
|
async def cleanup(self) -> None:
|
||||||
async with self._lock:
|
await self.__close_device_file()
|
||||||
await self.__close_device_file()
|
# TODO: disable gpio
|
||||||
# TODO: disable gpio
|
|
||||||
|
|
||||||
@_operated_and_locked
|
@_msd_operated
|
||||||
async def __aenter__(self) -> "MassStorageDevice":
|
async def __aenter__(self) -> "MassStorageDevice":
|
||||||
if not self.__device_info:
|
if not self.__device_info:
|
||||||
raise IsNotConnectedToKvmError()
|
raise IsNotConnectedToKvmError()
|
||||||
self._device_file = await aiofiles.open(self.__device_info.path, mode="w+b", buffering=0)
|
self._device_file = await aiofiles.open(self.__device_info.path, mode="w+b", buffering=0)
|
||||||
self.__written = 0
|
self.__written = 0
|
||||||
|
self.__region.enter()
|
||||||
return self
|
return self
|
||||||
|
|
||||||
async def write_image_info(self, name: str, complete: bool) -> None:
|
async def write_image_info(self, name: str, complete: bool) -> None:
|
||||||
async with self._lock:
|
assert self._device_file
|
||||||
assert self._device_file
|
assert self.__device_info
|
||||||
assert self.__device_info
|
if self.__write_meta:
|
||||||
if self.__write_meta:
|
if self.__device_info.size - self.__written > _IMAGE_INFO_SIZE:
|
||||||
if self.__device_info.size - self.__written > _IMAGE_INFO_SIZE:
|
await self._device_file.seek(self.__device_info.size - _IMAGE_INFO_SIZE)
|
||||||
await self._device_file.seek(self.__device_info.size - _IMAGE_INFO_SIZE)
|
await self.__write_to_device_file(_make_image_info_bytes(name, self.__written, complete))
|
||||||
await self.__write_to_device_file(_make_image_info_bytes(name, self.__written, complete))
|
await self._device_file.seek(0)
|
||||||
await self._device_file.seek(0)
|
await self.__load_device_info()
|
||||||
await self.__load_device_info()
|
else:
|
||||||
else:
|
get_logger().error("Can't write image info because device is full")
|
||||||
get_logger().error("Can't write image info because device is full")
|
|
||||||
|
|
||||||
async def write_image_chunk(self, chunk: bytes) -> int:
|
async def write_image_chunk(self, chunk: bytes) -> int:
|
||||||
async with self._lock:
|
await self.__write_to_device_file(chunk)
|
||||||
await self.__write_to_device_file(chunk)
|
self.__written += len(chunk)
|
||||||
self.__written += len(chunk)
|
return self.__written
|
||||||
return self.__written
|
|
||||||
|
|
||||||
async def __aexit__(
|
async def __aexit__(
|
||||||
self,
|
self,
|
||||||
@ -267,8 +268,10 @@ class MassStorageDevice: # pylint: disable=too-many-instance-attributes
|
|||||||
_exc: BaseException,
|
_exc: BaseException,
|
||||||
_tb: types.TracebackType,
|
_tb: types.TracebackType,
|
||||||
) -> None:
|
) -> None:
|
||||||
async with self._lock:
|
try:
|
||||||
await self.__close_device_file()
|
await self.__close_device_file()
|
||||||
|
finally:
|
||||||
|
self.__region.exit()
|
||||||
|
|
||||||
async def __write_to_device_file(self, data: bytes) -> None:
|
async def __write_to_device_file(self, data: bytes) -> None:
|
||||||
assert self._device_file
|
assert self._device_file
|
||||||
|
|||||||
@ -9,10 +9,11 @@ from typing import Dict
|
|||||||
from typing import Set
|
from typing import Set
|
||||||
from typing import Callable
|
from typing import Callable
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from typing import Type
|
|
||||||
|
|
||||||
import aiohttp.web
|
import aiohttp.web
|
||||||
|
|
||||||
|
from .aioregion import RegionIsBusyError
|
||||||
|
|
||||||
from .hid import Hid
|
from .hid import Hid
|
||||||
|
|
||||||
from .atx import Atx
|
from .atx import Atx
|
||||||
@ -38,35 +39,46 @@ def _system_task(method: Callable) -> Callable:
|
|||||||
return wrap
|
return wrap
|
||||||
|
|
||||||
|
|
||||||
class _BadRequest(Exception):
|
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)
|
||||||
|
|
||||||
|
|
||||||
|
def _json_exception(msg: str, err: Exception, status: int) -> aiohttp.web.Response:
|
||||||
|
get_logger().error("%s: %s", msg, err)
|
||||||
|
return _json({
|
||||||
|
"error": type(err).__name__,
|
||||||
|
"error_msg": str(err),
|
||||||
|
}, status=status)
|
||||||
|
|
||||||
|
|
||||||
|
class BadRequest(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
def _exceptions_as_400(msg: str, exceptions: List[Type[Exception]]) -> Callable:
|
class PerformingAnotherOperation(Exception):
|
||||||
|
def __init__(self) -> None:
|
||||||
|
super().__init__("Performing another operation, please try again later")
|
||||||
|
|
||||||
|
|
||||||
|
def _wrap_exceptions_for_web(msg: str) -> Callable:
|
||||||
def make_wrapper(method: Callable) -> Callable:
|
def make_wrapper(method: Callable) -> Callable:
|
||||||
async def wrap(self: "Server", request: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def wrap(self: "Server", request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
try:
|
try:
|
||||||
return (await method(self, request))
|
try:
|
||||||
except tuple(exceptions) as err: # pylint: disable=catching-non-exception
|
return (await method(self, request))
|
||||||
get_logger().error(msg)
|
except RegionIsBusyError:
|
||||||
return aiohttp.web.json_response({
|
raise PerformingAnotherOperation()
|
||||||
"ok": False,
|
except (BadRequest, MassStorageOperationError) as err:
|
||||||
"result": {
|
return _json_exception(msg, err, 400)
|
||||||
"error": type(err).__name__,
|
except PerformingAnotherOperation as err:
|
||||||
"error_msg": str(err),
|
return _json_exception(msg, err, 409)
|
||||||
},
|
|
||||||
}, status=400)
|
|
||||||
return wrap
|
return wrap
|
||||||
return make_wrapper
|
return make_wrapper
|
||||||
|
|
||||||
|
|
||||||
def _json_200(result: Optional[Dict]=None) -> aiohttp.web.Response:
|
|
||||||
return aiohttp.web.json_response({
|
|
||||||
"ok": True,
|
|
||||||
"result": (result or {}),
|
|
||||||
})
|
|
||||||
|
|
||||||
|
|
||||||
class Server: # pylint: disable=too-many-instance-attributes
|
class Server: # pylint: disable=too-many-instance-attributes
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
@ -159,9 +171,9 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
return ws
|
return ws
|
||||||
|
|
||||||
async def __atx_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __atx_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
return _json_200(self.__atx.get_state())
|
return _json(self.__atx.get_state())
|
||||||
|
|
||||||
@_exceptions_as_400("Click error", [_BadRequest])
|
@_wrap_exceptions_for_web("Click error")
|
||||||
async def __atx_click_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __atx_click_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
button = request.query.get("button")
|
button = request.query.get("button")
|
||||||
if button == "power":
|
if button == "power":
|
||||||
@ -171,13 +183,13 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
elif button == "reset":
|
elif button == "reset":
|
||||||
await self.__atx.click_reset()
|
await self.__atx.click_reset()
|
||||||
else:
|
else:
|
||||||
raise _BadRequest("Missing or invalid 'button=%s'" % (button))
|
raise BadRequest("Missing or invalid 'button=%s'" % (button))
|
||||||
return _json_200({"clicked": button})
|
return _json({"clicked": button})
|
||||||
|
|
||||||
async def __msd_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __msd_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
return _json_200(self.__msd.get_state())
|
return _json(self.__msd.get_state())
|
||||||
|
|
||||||
@_exceptions_as_400("Mass-storage error", [MassStorageOperationError, _BadRequest])
|
@_wrap_exceptions_for_web("Mass-storage error")
|
||||||
async def __msd_connect_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __msd_connect_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
to = request.query.get("to")
|
to = request.query.get("to")
|
||||||
if to == "kvm":
|
if to == "kvm":
|
||||||
@ -187,10 +199,10 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
await self.__msd.connect_to_pc()
|
await self.__msd.connect_to_pc()
|
||||||
await self.__broadcast_event("msd_state", state="connected_to_server") # type: ignore
|
await self.__broadcast_event("msd_state", state="connected_to_server") # type: ignore
|
||||||
else:
|
else:
|
||||||
raise _BadRequest("Missing or invalid 'to=%s'" % (to))
|
raise BadRequest("Missing or invalid 'to=%s'" % (to))
|
||||||
return _json_200(self.__msd.get_state())
|
return _json(self.__msd.get_state())
|
||||||
|
|
||||||
@_exceptions_as_400("Can't write data to mass-storage device", [MassStorageOperationError, _BadRequest])
|
@_wrap_exceptions_for_web("Can't write data to mass-storage device")
|
||||||
async def __msd_write_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __msd_write_handler(self, request: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
logger = get_logger(0)
|
logger = get_logger(0)
|
||||||
reader = await request.multipart()
|
reader = await request.multipart()
|
||||||
@ -198,12 +210,12 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
try:
|
try:
|
||||||
field = await reader.next()
|
field = await reader.next()
|
||||||
if not field or field.name != "image_name":
|
if not field or field.name != "image_name":
|
||||||
raise _BadRequest("Missing 'image_name' field")
|
raise BadRequest("Missing 'image_name' field")
|
||||||
image_name = (await field.read()).decode("utf-8")[:256]
|
image_name = (await field.read()).decode("utf-8")[:256]
|
||||||
|
|
||||||
field = await reader.next()
|
field = await reader.next()
|
||||||
if not field or field.name != "image_data":
|
if not field or field.name != "image_data":
|
||||||
raise _BadRequest("Missing 'image_data' field")
|
raise BadRequest("Missing 'image_data' field")
|
||||||
|
|
||||||
async with self.__msd:
|
async with self.__msd:
|
||||||
await self.__broadcast_event("msd_state", state="busy") # type: ignore
|
await self.__broadcast_event("msd_state", state="busy") # type: ignore
|
||||||
@ -219,14 +231,14 @@ class Server: # pylint: disable=too-many-instance-attributes
|
|||||||
finally:
|
finally:
|
||||||
if written != 0:
|
if written != 0:
|
||||||
logger.info("written %d bytes to mass-storage device", written)
|
logger.info("written %d bytes to mass-storage device", written)
|
||||||
return _json_200({"written": written})
|
return _json({"written": written})
|
||||||
|
|
||||||
async def __streamer_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __streamer_state_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
return _json_200(self.__streamer.get_state())
|
return _json(self.__streamer.get_state())
|
||||||
|
|
||||||
async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
async def __streamer_reset_handler(self, _: aiohttp.web.Request) -> aiohttp.web.Response:
|
||||||
self.__reset_streamer = True
|
self.__reset_streamer = True
|
||||||
return _json_200()
|
return _json()
|
||||||
|
|
||||||
def __run_app_print(self, text: str) -> None:
|
def __run_app_print(self, text: str) -> None:
|
||||||
logger = get_logger()
|
logger = get_logger()
|
||||||
|
|||||||
@ -154,17 +154,16 @@ function clickAtxButton(el_button) {
|
|||||||
|
|
||||||
if (button && confirm(confirm_msg)) {
|
if (button && confirm(confirm_msg)) {
|
||||||
__setAtxButtonsBusy(true);
|
__setAtxButtonsBusy(true);
|
||||||
var http = new XMLHttpRequest();
|
var http = __request("POST", "/kvmd/atx/click?button=" + button, function() {
|
||||||
http.open("POST", "/kvmd/atx/click?button=" + button, true);
|
|
||||||
http.onreadystatechange = function() {
|
|
||||||
if (http.readyState == 4) {
|
if (http.readyState == 4) {
|
||||||
if (http.status != 200) {
|
if (http.status == 409) {
|
||||||
|
alert("Performing another ATX operation for other client, please try again later");
|
||||||
|
} else if (http.status != 200) {
|
||||||
alert("Click error: " + http.responseText);
|
alert("Click error: " + http.responseText);
|
||||||
}
|
}
|
||||||
__setAtxButtonsBusy(false);
|
__setAtxButtonsBusy(false);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
http.send();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,9 +180,7 @@ function __setAtxButtonsBusy(busy) {
|
|||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
function pollStreamer() {
|
function pollStreamer() {
|
||||||
var http = new XMLHttpRequest();
|
var http = __request("GET", "/streamer/?action=snapshot", function() {
|
||||||
http.open("GET", "/streamer/?action=snapshot", true);
|
|
||||||
http.onreadystatechange = function() {
|
|
||||||
if (http.readyState == 2 || http.readyState == 4) {
|
if (http.readyState == 2 || http.readyState == 4) {
|
||||||
var status = http.status;
|
var status = http.status;
|
||||||
http.onreadystatechange = null;
|
http.onreadystatechange = null;
|
||||||
@ -198,16 +195,13 @@ function pollStreamer() {
|
|||||||
pollStreamer.last = true;
|
pollStreamer.last = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
http.send();
|
|
||||||
setTimeout(pollStreamer, 2000);
|
setTimeout(pollStreamer, 2000);
|
||||||
}
|
}
|
||||||
pollStreamer.last = false;
|
pollStreamer.last = false;
|
||||||
|
|
||||||
function __refreshStreamer() {
|
function __refreshStreamer() {
|
||||||
var http = new XMLHttpRequest();
|
var http = __request("GET", "/kvmd/streamer", function() {
|
||||||
http.open("GET", "/kvmd/streamer", true);
|
|
||||||
http.onreadystatechange = function() {
|
|
||||||
if (http.readyState == 4 && http.status == 200) {
|
if (http.readyState == 4 && http.status == 200) {
|
||||||
size = JSON.parse(http.responseText).result.size;
|
size = JSON.parse(http.responseText).result.size;
|
||||||
el_stream_box = document.getElementById("stream-image");
|
el_stream_box = document.getElementById("stream-image");
|
||||||
@ -215,27 +209,31 @@ function __refreshStreamer() {
|
|||||||
el_stream_box.style.height = size.height + "px";
|
el_stream_box.style.height = size.height + "px";
|
||||||
document.getElementById("stream-image").src = "/streamer/?action=stream&time=" + new Date().getTime();
|
document.getElementById("stream-image").src = "/streamer/?action=stream&time=" + new Date().getTime();
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
http.send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function clickResetStreamerButton(el_button) {
|
function clickResetStreamerButton(el_button) {
|
||||||
__setButtonBusy(el_button, true);
|
__setButtonBusy(el_button, true);
|
||||||
var http = new XMLHttpRequest();
|
var http = __request("POST", "/kvmd/streamer/reset", function() {
|
||||||
http.open("POST", "/kvmd/streamer/reset", true);
|
|
||||||
http.onreadystatechange = function() {
|
|
||||||
if (http.readyState == 4) {
|
if (http.readyState == 4) {
|
||||||
if (http.status != 200) {
|
if (http.status != 200) {
|
||||||
alert("Can't reset streamer: " + http.responseText);
|
alert("Can't reset streamer: " + http.responseText);
|
||||||
}
|
}
|
||||||
__setButtonBusy(el_button, false);
|
__setButtonBusy(el_button, false);
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
http.send();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
function __request(method, url, callback) {
|
||||||
|
var http = new XMLHttpRequest();
|
||||||
|
http.open(method, url, true)
|
||||||
|
http.onreadystatechange = callback;
|
||||||
|
http.send();
|
||||||
|
return http;
|
||||||
|
}
|
||||||
|
|
||||||
function __setButtonBusy(el_button, busy) {
|
function __setButtonBusy(el_button, busy) {
|
||||||
el_button.disabled = busy;
|
el_button.disabled = busy;
|
||||||
el_button.style.cursor = (busy ? "wait" : "default");
|
el_button.style.cursor = (busy ? "wait" : "default");
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user