diff --git a/connectcore-demo-example/demoserver.py b/connectcore-demo-example/demoserver.py index 78f1617..8c3570f 100755 --- a/connectcore-demo-example/demoserver.py +++ b/connectcore-demo-example/demoserver.py @@ -29,9 +29,13 @@ import socketserver import stat import subprocess +from digi.apix.bluetooth import BluetoothException, BluetoothDevice, BluetoothProfile +from digi.apix.exceptions import DigiAPIXException +from digi.apix.network import IPMode, NetworkException, NetStatus, NetworkInterface, NetworkProfile +from digi.apix.wifi import SecurityMode, WifiException, WifiInterface, WifiProfile from logging.handlers import SysLogHandler from subprocess import call, TimeoutExpired -from threading import Thread, Event +from threading import Thread # Constants. @@ -52,9 +56,15 @@ ZERO_MAC = "00:00:00:00:00:00" ZERO_IP = "0.0.0.0" NOT_AVAILABLE = "N/A" +ELEMENT_BLUETOOTH = "bluetooth" +ELEMENT_ETHERNET = "ethernet" +ELEMENT_WIFI = "wifi" + +PREFIX_ETHERNET = "eth" +PREFIX_WIFI = "wlan" + # Variables. log = logging.getLogger(APP_NAME) -stop_event = Event() last_cpu_work = 0 last_cpu_total = 0 led_status = {} @@ -113,7 +123,11 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): mca_versions = get_mca_version() info = { "uboot_version": read_proc_file("/proc/device-tree/digi,uboot,version"), - "kernel_version": "%s %s %s %s %s GNU/Linux" % (platform.system(), platform.node(), platform.release(), platform.version(), platform.machine()), + "kernel_version": "%s %s %s %s %s GNU/Linux" % (platform.system(), + platform.node(), + platform.release(), + platform.version(), + platform.machine()), "dey_version": "DEY-%s-%s" % (get_dey_version(), read_file("/etc/version")), "serial_number": read_proc_file("/proc/device-tree/digi,hwid,sn"), "device_type": read_proc_file("/proc/device-tree/digi,machine,name"), @@ -126,14 +140,57 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): "flash_size": get_storage_size(), "video_resolution": get_video_resolution(), "fw_store_path": get_fw_store_path(), - "bluetooth_mac": get_bt_mac("hci0"), - "wifi_mac": read_file("/sys/class/net/wlan0/address").strip().upper() if "wlan0" in list_net_ifaces() else ZERO_MAC, - "wifi_ip": get_iface_ip("wlan0") if "wlan0" in list_net_ifaces() else ZERO_IP, - "ethernet0_mac": read_file("/sys/class/net/eth0/address").strip().upper() if "eth0" in list_net_ifaces() else ZERO_MAC, - "ethernet0_ip": get_iface_ip("eth0") if "eth0" in list_net_ifaces() else ZERO_IP, - "ethernet1_mac": read_file("/sys/class/net/eth1/address").strip().upper() if "eth1" in list_net_ifaces() else ZERO_MAC, - "ethernet1_ip": get_iface_ip("eth1") if "eth1" in list_net_ifaces() else ZERO_IP, + "bluetooth_mac": ZERO_MAC, + "wifi_mac": ZERO_MAC, + "wifi_ip": ZERO_IP, + "ethernet0_mac": ZERO_MAC, + "ethernet0_ip": ZERO_IP, + "ethernet1_mac": ZERO_MAC, + "ethernet1_ip": ZERO_IP, } + # Fill bluetooth data. + try: + bt_devices = BluetoothDevice.list_devices() + if bt_devices and "hci0" in bt_devices: + try: + bt_device = BluetoothDevice.get("hci0") + info["bluetooth_mac"] = mac_to_human_string(bt_device.mac) + except BluetoothException as exc2: + log.error("Error reading interface 'hci0' data: %s", str(exc2)) + except DigiAPIXException as exc: + log.error("Error listing bluetooth devices: %s", str(exc)) + # Fill ethernet interfaces data. + try: + interfaces = NetworkInterface.list_interfaces() + if interfaces and "eth0" in interfaces: + try: + net_iface = NetworkInterface.get("eth0") + info["ethernet0_mac"] = str(net_iface.mac) + info["ethernet0_ip"] = str(net_iface.ipv4) + except NetworkException as exc2: + log.error("Error reading interface 'eth0' data: %s", str(exc2)) + if interfaces and "eth1" in interfaces: + try: + net_iface = NetworkInterface.get("eth1") + info["ethernet1_mac"] = mac_to_human_string(net_iface.mac) + info["ethernet1_ip"] = str(net_iface.ipv4) + except NetworkException as exc2: + log.error("Error reading interface 'eth1' data: %s", str(exc2)) + except DigiAPIXException as exc: + log.error("Error listing network interfaces: %s", str(exc)) + + # Fill WiFi interfaces data. + try: + interfaces = WifiInterface.list_interfaces() + if interfaces and "wlan0" in interfaces: + try: + net_iface = WifiInterface.get("wlan0") + info["wifi_mac"] = mac_to_human_string(net_iface.mac) + info["wifi_ip"] = str(net_iface.ipv4) + except WifiException as exc2: + log.error("Error reading interface 'wlan0' data: %s", str(exc2)) + except DigiAPIXException as exc: + log.error("Error listing network interfaces: %s", str(exc)) # Send the JSON value. self.wfile.write(json.dumps(info).encode(encoding="utf_8")) @@ -147,7 +204,7 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): mem_total = int(mem_info.get("MemTotal", "-1")) if mem_info else -1 mem_free = int(mem_info.get("MemFree", "-1")) if mem_info else -1 mem_used = (mem_total - mem_free) if (mem_total > 0 and mem_free > 0) else -1 - bt_data = get_bt_data("hci0") + status = { "system_monitor/cpu_load": get_cpu_load(), "system_monitor/cpu_temperature": get_cpu_temp(), @@ -155,18 +212,56 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): "system_monitor/uptime": get_uptime(), "system_monitor/free_memory": mem_free, "system_monitor/used_memory": mem_used, - "system_monitor/led_status": get_led_status("USER_LED"), - "system_monitor/hci0/state": bt_data.get("state", 0), - "system_monitor/hci0/rx_bytes": bt_data.get("rx_bytes", 0), - "system_monitor/hci0/tx_bytes": bt_data.get("tx_bytes", 0), + "system_monitor/led_status": get_led_status("USER_LED") } - list_ifaces = list_net_ifaces() - for name in list_ifaces: - data = get_iface_data(name) - status["system_monitor/%s/state" % name] = data["state"] - status["system_monitor/%s/rx_bytes" % name] = data["rx_bytes"] - status["system_monitor/%s/tx_bytes" % name] = data["tx_bytes"] + # Fill bluetooth devices data. + try: + bt_devices = BluetoothDevice.list_devices() or [] + for device in bt_devices: + try: + bt_device = BluetoothDevice.get(device) + statistics = bt_device.get_statistics() + status[f"system_monitor/{device}/state"] = 1 if \ + bt_device.is_enabled else 0 + status[f"system_monitor/{device}/rx_bytes"] = statistics.rx_bytes + status[f"system_monitor/{device}/tx_bytes"] = statistics.tx_bytes + except NetworkException as exc2: + log.error("Error reading bluetooth device '%s' data: %s", device, str(exc2)) + except DigiAPIXException as exc: + log.error("Error listing bluetooth devices: %s", str(exc)) + + # Fill ethernet interfaces data. + try: + interfaces = NetworkInterface.list_interfaces() or [] + for iface in interfaces: + try: + net_iface = NetworkInterface.get(iface) + statistics = net_iface.get_statistics() + status[f"system_monitor/{iface}/state"] = 1 if \ + net_iface.status == NetStatus.CONNECTED else 0 + status[f"system_monitor/{iface}/rx_bytes"] = statistics.rx_bytes + status[f"system_monitor/{iface}/tx_bytes"] = statistics.tx_bytes + except NetworkException as exc2: + log.error("Error reading interface '%s' data: %s", iface, str(exc2)) + except DigiAPIXException as exc: + log.error("Error listing network interfaces: %s", str(exc)) + + # Fill WiFi interfaces data. + try: + interfaces = WifiInterface.list_interfaces() or [] + for iface in interfaces: + try: + wifi_iface = WifiInterface.get(iface) + statistics = wifi_iface.get_statistics() + status[f"system_monitor/{iface}/state"] = 1 if \ + wifi_iface.status == NetStatus.CONNECTED else 0 + status[f"system_monitor/{iface}/rx_bytes"] = statistics.rx_bytes + status[f"system_monitor/{iface}/tx_bytes"] = statistics.tx_bytes + except WifiException as exc2: + log.error("Error reading interface '%s' data: %s", iface, str(exc2)) + except DigiAPIXException as exc: + log.error("Error listing WiFi interfaces: %s", str(exc)) # Send the JSON value. self.wfile.write(json.dumps(status).encode(encoding="utf_8")) @@ -196,6 +291,23 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): return self.wfile.write("{}".encode(encoding="utf_8")) + elif re.search("/ajax/play_music", self.path) is not None: + # Set the response headers. + self._set_headers(200) + + # Get the JSON data. + data = self.rfile.read(int(self.headers["Content-Length"])) + play = json.loads(data.decode("utf-8")).get("play", None) + music_file = json.loads(data.decode("utf-8")).get("music_file", None) + + log.debug("Play music: %s", play) + if music_file: + log.debug("Music file: %s", music_file) + + play_music(play, music_file) + + # Send the JSON value. + self.wfile.write(json.dumps({"play": play}).encode(encoding="utf_8")) elif re.search("/ajax/set_audio_volume", self.path) is not None: # Set the response headers. self._set_headers(200) @@ -212,7 +324,7 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): self.wfile.write(json.dumps({"error": error}).encode(encoding="utf_8")) return - vol, error = set_audio_volume(value) + error = set_audio_volume(value) # Send the JSON value. if error: @@ -221,7 +333,7 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): return # Send the JSON value. - self.wfile.write(json.dumps({"value": vol}).encode(encoding="utf_8")) + self.wfile.write("{}".encode(encoding="utf_8")) elif re.search("/ajax/fs_list_directory", self.path) is not None: # Set the response headers. self._set_headers(200) @@ -462,7 +574,34 @@ class RequestHandler(http.server.SimpleHTTPRequestHandler): progress = 100 if fw_process and fw_process.poll() is None: progress = "?" - self.wfile.write(json.dumps({"progress": progress, "message": "Updating firmare"}).encode(encoding="utf_8")) + self.wfile.write(json.dumps({"progress": progress, "message": "Updating firmware"}).encode(encoding="utf_8")) + elif re.search("/ajax/get_config", self.path) is not None: + # Set the response headers. + self._set_headers(200) + # Get the JSON data. + data = self.rfile.read(int(self.headers["Content-Length"])) + elements = json.loads(data.decode("utf-8")).get("elements", None) + # Fill configuration data. + config_data = get_elements_configuration(elements) + # Send the answer. + if "error" in config_data: + self.wfile.write(json.dumps(config_data).encode(encoding="utf_8")) + else: + self.wfile.write(json.dumps( + {"data": json.dumps(config_data)}).encode(encoding="utf_8")) + elif re.search("/ajax/set_config", self.path) is not None: + # Set the response headers. + self._set_headers(200) + # Get the JSON data. + data = self.rfile.read(int(self.headers["Content-Length"])) + configuration = json.loads(data.decode("utf-8")).get("configuration", None) + # Apply configuration. + result = set_configuration(configuration) + # Send the answer. + if "error" in result: + self.wfile.write(json.dumps(result).encode(encoding="utf_8")) + else: + self.wfile.write(json.dumps({"data": json.dumps(result)}).encode(encoding="utf_8")) else: # Forbidden. self._set_headers(403) @@ -507,7 +646,7 @@ def filter_by_extension(name, filters): if name.endswith(ext): return True - return False; + return False def get_uptime(): @@ -652,7 +791,7 @@ def get_video_resolution(): if res == NOT_AVAILABLE: res = read_file("/sys/class/graphics/fb0/modes") if res == NOT_AVAILABLE: - return "-" + return "No video device found" line = res.splitlines()[0] if ":" in line: @@ -737,146 +876,6 @@ def get_mca_version(): return NOT_AVAILABLE, NOT_AVAILABLE -def list_net_ifaces(): - """ - Returs a list with the names of the network interfaces. - - Returns: - List: List of network inteface names. - """ - return os.listdir("/sys/class/net") - - -def get_iface_ip(iface_name): - """ - Gets the IP address of the provided interface. - - Args: - iface_name (String): Name of the network interface to get its IP. - - Returns: - String: The IP of the interface. - """ - res = exec_cmd("ip addr show %s" % iface_name) - if res[0] == 0: - tmp = res[1].split("inet ") - if len(tmp) > 1: - return tmp[1].split("/")[0] - - log.error("Error getting IP of interface '%s': %s", iface_name, res[1]) - - return ZERO_IP - - -def get_iface_data(iface_name): - """ - Gets network interface state and statistics. - - Args: - iface_name (String): Name of the interface to get data. - - Returns: - Dictionary: The network interface state and statistics. - """ - data = { - "state": 0, - "rx_bytes": 0, - "tx_bytes": 0 - } - state = read_file("/sys/class/net/%s/operstate" % iface_name) - if state != NOT_AVAILABLE: - data["state"] = 1 if state.strip() == "up" else 0 - - stats = read_file("/proc/net/dev") - if stats == NOT_AVAILABLE: - return data - - for line in stats.splitlines(): - if not line.strip().startswith("%s: " % iface_name): - continue - fields = line.split() - data["rx_bytes"] = fields[1] - data["tx_bytes"] = fields[9] - break - - return data - - -def is_bt_available(): - """ - Checks if Bluetooth is available on the device. - - Returns: - Boolean: True if available, False otherwise. - """ - return os.path.isdir("/proc/device-tree/bluetooth") - - -def get_bt_mac(iface_name): - """ - Gets Bluetooth MAC address. - - Args: - iface_name (String): Name of the interface to get the MAC. - - Returns: - String: The Bluetooth MAC. - """ - if not is_bt_available(): - return ZERO_MAC - - res = exec_cmd("hciconfig %s" % iface_name) - if res[0] == 0: - return res[1].split("BD Address:")[1].split("ACL")[0].strip() - else: - log.error("Error getting MAC of Bluetooth interface '%s': %s", iface_name, res[1]) - return ZERO_MAC - - -def get_bt_data(iface_name): - """ - Gets Bluetooth interface state and statistics. - - Args: - iface_name (String): Name of the interface to get data. - - Returns: - Dictionary: The Bluetooth interface state and statistics. - """ - data = { - "state": 0, - "rx_bytes": 0, - "tx_bytes": 0 - } - - if not is_bt_available(): - return data - - res = exec_cmd("hciconfig %s" % iface_name) - if res[0] == 0: - info = res[1] - else: - log.error("Error getting status of Bluetooth interface '%s': %s", iface_name, res[1]) - return data - - lines = info.splitlines() - if not lines: - return data - - if len(lines) > 2 and re.fullmatch("(UP) .*", lines[2].strip()): - data["state"] = 1 - - if len(lines) > 3: - m = re.fullmatch("RX bytes:([0-9]+) .*", lines[3].strip()) - data["rx_bytes"] = m.group(1) if m else 0 - - if len(lines) > 4: - m = re.fullmatch("TX bytes:([0-9]+) .*", lines[4].strip()) - data["tx_bytes"] = m.group(1) if m else 0 - - return data - - def get_led_status(name): """ Get the LED value. @@ -951,6 +950,19 @@ def get_led_by_alias(alias): return led_loc.split(",") +def play_music(play, music_file): + """ + Sets the play music value. + + Args: + play (Boolean): `True` to play music, `False` to stop it. + music_file (String): Path of the music file to play. + """ + exec_cmd("pkill -KILL -f mpg123") + if play: + exec_cmd_nowait(f"mpg123 {music_file}") + + def set_audio_volume(value): """ Configures the audio volume. @@ -959,27 +971,277 @@ def set_audio_volume(value): value (Integer): Volume to set in percentage. Returns: - Tuple (Integer, String): Current volume value and error string if fails. + String: Error string if fails. """ - res = exec_cmd("amixer set Headphone %d%%" % value) + res = exec_cmd(f"amixer set 'Speaker' {value}% && amixer set 'Headphone' {value}%") if res[0] != 0: - return -1, res[1] + return res[1] + return None - tmp = res[1].split("Front Right:") - if len(tmp) < 1: - return -1, "Unable to get current volume" - m = re.search("\[(.+?)%\]", tmp[1]) - if not m: - return -1, "Unable to get current volume" +def get_elements_configuration(elements): + """ + Gets the configuration for the given elements. - if len(m.groups()) < 1: - return -1, "Unable to get current volume" + Args: + elements (List): Elements for which to get the configuration. + Returns: + Dictionary: The elements configuration data. + """ + data = {} + for element in elements: + if element == ELEMENT_BLUETOOTH: + data[element] = get_bluetooth_element_configuration() + elif element in (ELEMENT_ETHERNET, ELEMENT_WIFI): + data[element] = get_network_element_configuration(element) + else: + data["error"] = f"Unknown element '{element}'" + + return data + + +def get_network_element_configuration(element): + """ + Gets the configuration for the given network element. + + Args: + element (String): Network element for which to get the configuration. + + Returns: + Dictionary: The network element configuration data. + """ + data = {} + prefix = PREFIX_ETHERNET + if element == ELEMENT_WIFI: + prefix = PREFIX_WIFI try: - return int(m.group(1)), "" - except ValueError: - return -1, "Unable to get current volume" + ifaces = NetworkInterface.list_interfaces() or [] + for iface in ifaces: + if prefix in iface: + data[iface] = get_network_iface_configuration(iface) + except DigiAPIXException as exc: + data["error"] = f"Error listing network interfaces: '{str(exc)}'" + + return data + + +def get_network_iface_configuration(interface): + """ + Returns the network configuration of the given interface. + + Args: + interface (String): Network interface to get its configuration. + + Returns: + Dictionary: dictionary with the interface configuration. + """ + data = {} + try: + net_iface = NetworkInterface.get(interface) + data["enable"] = 1 if net_iface.status == NetStatus.CONNECTED else 0 + data["mac"] = mac_to_human_string(net_iface.mac) + data["type"] = 0 if net_iface.ip_mode == IPMode.STATIC else 1 + data["ip"] = str(net_iface.ipv4) + data["netmask"] = str(net_iface.netmask) + data["dns1"] = str(net_iface.dns1) + data["dns2"] = str(net_iface.dns2) + data["gateway"] = str(net_iface.gateway) + if PREFIX_WIFI in interface: + try: + wifi_iface = WifiInterface.get(interface) + data["ssid"] = wifi_iface.ssid + data["frequency"] = wifi_iface.frequency + data["channel"] = wifi_iface.channel + data["sec_mode"] = 0 if wifi_iface.sec_mode == SecurityMode.UNKNOWN \ + else wifi_iface.sec_mode.code + except WifiException as exc: + raise NetworkException({str(exc)}) from exc + except NetworkException as exc2: + data["error"] = f"Error reading interface data: {str(exc2)}" + + return data + + +def set_configuration(config_data): + """ + Applies the given configuration. + + Args: + config_data (Dictionary): Configuration to apply. + + Returns: + Dictionary: The operation result. + """ + data = {} + for element in config_data: + if element == ELEMENT_BLUETOOTH: + data[element] = set_bluetooth_element_configuration(config_data[element]) + elif element in (ELEMENT_ETHERNET, ELEMENT_WIFI): + data[element] = set_network_element_configuration(config_data[element]) + else: + data["error"] = f"Unknown element '{element}'" + + return data + + +def set_network_element_configuration(config_data): + """ + Applies the given network configuration. + + Args: + config_data (Dictionary): Network configuration to apply. + + Returns: + Dictionary: The operation result. + """ + data = {} + for iface in config_data: + data[iface] = set_network_iface_configuration(iface, config_data[iface]) + + return data + + +def set_network_iface_configuration(interface, config_data): + """ + Applies the given network configuration to the given interface. + + Args: + interface (String): Network interface to apply configuration to. + config_data (Dictionary): Network configuration to apply. + + Returns: + Dictionary: Operation result. + """ + data = {} + profile = NetworkProfile() + if PREFIX_WIFI in interface: + profile = WifiProfile() + profile.connect = config_data.get("enable", None) + profile.mode = IPMode.get(config_data["type"]) if "type" in config_data else None + profile.ipv4 = config_data.get("ip", None) + profile.netmask = config_data.get("netmask", None) + profile.gateway = config_data.get("gateway", None) + profile.dns1 = config_data.get("dns1", None) + profile.dns2 = config_data.get("dns2", None) + if PREFIX_WIFI in interface: + profile.ssid = config_data.get("ssid", None) + profile.sec_mode = (SecurityMode.get(config_data["sec_mode"]) + if "sec_mode" in config_data else None) + profile.psk = config_data.get("psk", None) + try: + if PREFIX_ETHERNET in interface: + net_iface = NetworkInterface.get(interface) + elif PREFIX_WIFI in interface: + net_iface = WifiInterface.get(interface) + else: + raise DigiAPIXException(f"Unknown interface '{interface}'") + net_iface.configure(profile) + data["status"] = 0 + except DigiAPIXException as exc2: + data["status"] = 1 + data["desc"] = f"Error configuring interface: {str(exc2)}" + + return data + + +def get_bluetooth_element_configuration(): + """ + Gets the configuration for the bluetooth element. + + Returns: + Dictionary: The bluetooth element configuration data. + """ + data = {} + try: + bt_devices = BluetoothDevice.list_devices() or [] + for bt_device in bt_devices: + data[bt_device] = get_bluetooth_device_configuration(bt_device) + except DigiAPIXException as exc: + data["error"] = f"Error listing bluetooth devices: '{str(exc)}'" + + return data + + +def get_bluetooth_device_configuration(device): + """ + Returns the configuration of the given bluetooth device. + + Args: + device (String): Bluetooth device to get its configuration. + + Returns: + Dictionary: dictionary with the device configuration. + """ + data = {} + try: + bt_device = BluetoothDevice.get(device) + data["device_id"] = bt_device.device_id + data["advert_name"] = str(bt_device.advert_name) + data["mac"] = mac_to_human_string(bt_device.mac) + data["enable"] = 1 if bt_device.is_enabled else 0 + data["running"] = 1 if bt_device.is_running else 0 + except BluetoothException as exc: + data["error"] = f"Error reading device data: {str(exc)}" + + return data + + +def set_bluetooth_element_configuration(config_data): + """ + Applies the given bluetooth configuration. + + Args: + config_data (Dictionary): Bluetooth configuration to apply. + + Returns: + Dictionary: The operation result. + """ + data = {} + for device in config_data: + data[device] = set_bluetooth_device_configuration(device, config_data[device]) + + return data + + +def set_bluetooth_device_configuration(device, config_data): + """ + Applies the given bluetooth configuration to the given device. + + Args: + device (String): Bluetooth device to apply configuration to. + config_data (Dictionary): Bluetooth configuration to apply. + + Returns: + Dictionary: Operation result. + """ + data = {} + try: + bt_device = BluetoothDevice.get(device) + profile = BluetoothProfile() + profile.enable = config_data.get("enable", None) + profile.advert_name = config_data.get("advert_name", None) + bt_device.configure(profile) + data["status"] = 0 + except DigiAPIXException as exc2: + data["status"] = 1 + data["desc"] = f"Error configuring interface: {str(exc2)}" + + return data + + +def mac_to_human_string(mac, num_bytes=6): + """ + Transforms the given MAC address into a human readable string. + + Args: + mac (:class:`.MacAddress`): The MAC address to transform. + num_bytes (Integer): The number of bytes to use in the MAC Address. + + Return: + String: The given MAC as human readable string. + """ + return ":".join(["%02X" % i for i in mac][8-num_bytes:]).upper() def read_proc_file(path): @@ -1025,6 +1287,21 @@ def exec_cmd(cmd, timeout=None): return e.returncode, e.stdout +def exec_cmd_nowait(command, *args): + """ + Executes the provided command without waiting to finish. + + Args: + command (String): The command to execute. + args (List): The list of arguments. + """ + arguments = [] + for arg in args: + arguments.extend(arg) + subprocess.Popen([command] + arguments, shell=True, stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, text=True) + + def read_file(path): """ Reads the provided file path. @@ -1124,8 +1401,6 @@ def signal_handler(signal_number, _frame): _frame: Current stack frame. """ log.debug("Signal received %d", signal_number) - if signal_number in (signal.SIGTERM, signal.SIGINT): - stop_event.set() def main(): @@ -1161,7 +1436,8 @@ def main(): server_thread.deamon = True server_thread.start() - stop_event.wait() + # Wait for termination/interrupt signal. + signal.sigwait([signal.SIGTERM, signal.SIGINT]) server.shutdown() server.server_close() diff --git a/connectcore-demo-example/index.html b/connectcore-demo-example/index.html index f229124..aa331c3 100644 --- a/connectcore-demo-example/index.html +++ b/connectcore-demo-example/index.html @@ -34,7 +34,6 @@ Digi Demo - Dashboard - +
  • + +
    + + Network +
    +
    +
  • @@ -227,12 +234,14 @@ Digi Demo - Multimedia + + diff --git a/connectcore-demo-example/network.html b/connectcore-demo-example/network.html new file mode 100644 index 0000000..231e82f --- /dev/null +++ b/connectcore-demo-example/network.html @@ -0,0 +1,508 @@ + + + + +Digi Demo - Network + + + + + + + + + + + + + + + + + + + + + + + + + +
    + + + + +
    + +
    + Loading... +
    +
    +
    +
    + + +
    +
    + + +
    +
    +
    +
    +
    No
    +
    Yes
    +
    +
    +
    +
    +
    + + Ethernet 0 +
    +
    +
    +
    Configure ethernet parameters.
    +
    + MAC Address: + +
    +
    + Connected: + +
    +
    + IP mode: + +
    +
    + IP address: + +
    +
    +
    + Subnet mask: + +
    +
    +
    + Default gateway: + +
    +
    +
    + DNS1 address: + +
    +
    +
    + DNS2 address: + +
    +
    +
    +
    Refresh
    +
    Save
    +
    +
    +
    +
    +
    + + Ethernet 1 +
    +
    + +
    +
    +
    + + Wi-Fi +
    +
    + +
    +
    +
    +
    + + + +
    + +
    +
    + + + + + + + + + + + + + + + + + + + + + diff --git a/connectcore-demo-example/static/css/general.css b/connectcore-demo-example/static/css/general.css index b57c2e8..cb27134 100644 --- a/connectcore-demo-example/static/css/general.css +++ b/connectcore-demo-example/static/css/general.css @@ -373,7 +373,7 @@ body { /* POPUP */ .popup { z-index: 999; - position: absolute; + position: fixed; transform: translate(-50%, -50%); background-color: white; padding: 20px 10px; @@ -1058,11 +1058,24 @@ body { /* AUDIO */ .audio-panel { - width: 350px; + width: 280px; } #audio_panel .slider-horizontal { - width: 180px; + width: 110px; +} + +.audio-button { + width: 32px; + height: 32px; + font-size: 24px; + text-align: center; + z-index: 99999; +} + +.audio-button:hover { + cursor: pointer; + color: var(--digi-green); } /* END AUDIO */ @@ -1869,20 +1882,25 @@ body { } /* END MULTIMEDIA VIEWER */ -/* MANAGEMENT */ -.system-monitor-param-container { +/* CONFIG CONTROLS */ +.param-container { margin-top: 10px; width: 100%; padding-left: 10px; } -.system-monitor-param-label { +.param-label { display: inline-block; position: relative; width: 170px; } -.system-monitor-input { +.param-value { + display: inline-block; + position: relative; +} + +.input-control { position: relative; -webkit-transition: none !important; -moz-transition: none !important; @@ -1892,13 +1910,100 @@ body { width: 80px !important; padding: 0px !important; margin: 0px !important; + text-align: left !important; + padding-left: 5px !important; } -.system-monitor-input-error { +.input-control-wide { + width: 180px !important; +} + +.input-control-error { background-color: #f3b1b1 !important; } -.system-monitor-error { +.input-control-disabled { + background-color: transparent !important; + border: none !important; + pointer-events: none !important; + color: #939393 !important; +} + +.select-control { + position: relative; + -webkit-transition: none !important; + -moz-transition: none !important; + -ms-transition: none !important; + -o-transition: none !important; + transition: none !important; + width: 80px !important; + padding: 0px !important; + margin: 0px !important; + text-align-last: left !important; + padding-left: 5px !important; +} + +.switch-control { + position: relative; + display: inline-block; + width: 48px; + height: 22px; + margin-bottom: 0px; +} + +.switch-control input { + opacity: 0; + width: 0; + height: 0; +} + +.slider-control { + position: absolute; + cursor: pointer; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: #ccc; + -webkit-transition: .4s; + transition: .4s; +} + +.slider-control:before { + position: absolute; + content: ""; + height: 16px; + width: 16px; + left: 3px; + bottom: 3px; + background-color: white; + -webkit-transition: .4s; + transition: .4s; +} + +input:checked + .slider-control { + background-color: #2196F3; +} + +input:focus + .slider-control { + box-shadow: 0 0 1px #2196F3; +} + +input:checked + .slider-control:before { + -webkit-transform: translateX(26px); + -ms-transform: translateX(26px); + transform: translateX(26px); +} + +.slider-control.round { + border-radius: 11px; +} + +.slider-control.round:before { + border-radius: 50%; +} + +.error-label { color: red; font-size: 12px; font-style: italic; @@ -1906,6 +2011,67 @@ body { padding-left: 10px; } +.buttons-container { + with: 100%; + position: relative; +} + +.config-button { + padding: 5px; + cursor: pointer; + margin-bottom: 0; + margin-top: 15px; + width: 100px; + float: left; + margin-left: 10px; +} + +.config-button-disabled { + cursor: default; + color: white; + background-color: #c3c3c3; + pointer-events: none; +} + +input[type=text], input[type=password], select { + background-color: #f6f6f6; + border: none; + color: #0d0d0d; + padding: 12px 30px; + text-align: center; + text-decoration: none; + display: inline-block; + font-size: 14px; + margin: 0px 0px 25px 0px; + width: 85%; + border: 2px solid #f6f6f6; + -webkit-transition: all 0.5s ease-in-out; + -moz-transition: all 0.5s ease-in-out; + -ms-transition: all 0.5s ease-in-out; + -o-transition: all 0.5s ease-in-out; + transition: all 0.5s ease-in-out; + -webkit-border-radius: 5px 5px 5px 5px; + border-radius: 5px 5px 5px 5px; +} + +input[type=text]:focus, input[type=password]:focus, select:focus { + background-color: #fff; + border-bottom: 2px solid var(--digi-green); +} + +select { + -moz-appearance:none; /* Firefox */ + -webkit-appearance:none; /* Safari and Chrome */ + appearance:none; + text-align-last: center; + background-image: url("../images/combo_arrow_gray.png"); + background-repeat: no-repeat, repeat; + background-position: right .7em top 50%; + background-size: .65em auto; +} +/* END CONFIG CONTROLS */ + +/* MANAGEMENT */ .system-monitor-help { font-size: 16px; padding-left: 5px; @@ -1927,21 +2093,6 @@ body { outline: none; } -.system-monitor-save { - padding: 5px; - cursor: pointer; - margin-bottom: 0; - margin-top: 15px; - width: 100px; -} - -.system-monitor-save-disabled { - cursor: default; - color: white; - background-color: #c3c3c3; - pointer-events: none; -} - .firmware-file-input { position: relative; margin-top: 15px; @@ -1996,13 +2147,6 @@ body { background-color: #a72a2a; } -.management-button-disabled { - cursor: default; - color: #c3c3c3; - background-color: #e3e3e3; - pointer-events: none; -} - .update-firmware-progress { position: relative; margin-top: 15px; @@ -2196,3 +2340,20 @@ body { margin-top: 5px; } /* END MANAGEMENT */ + +/* NETWORK */ +.interface-header { + padding: 15px 20px 10px 20px; + align-items: center; + font-size: 16px; + font-weight: bold; +} + +.interface-panel-container { + padding: 0px 20px 15px 20px; +} + +.interface-title { + font-size: 1.25em; +} +/* END NETWORK */ diff --git a/connectcore-demo-example/static/images/board.png b/connectcore-demo-example/static/images/board.png old mode 100755 new mode 100644 diff --git a/connectcore-demo-example/static/images/ccimx6ulsbc_board.png b/connectcore-demo-example/static/images/ccimx6ulsbc_board.png old mode 100755 new mode 100644 diff --git a/connectcore-demo-example/static/images/ccmp133_board.png b/connectcore-demo-example/static/images/ccmp133_board.png new file mode 100644 index 0000000..9f7b1a1 Binary files /dev/null and b/connectcore-demo-example/static/images/ccmp133_board.png differ diff --git a/connectcore-demo-example/static/images/ccmp157_board.png b/connectcore-demo-example/static/images/ccmp157_board.png old mode 100755 new mode 100644 diff --git a/connectcore-demo-example/static/images/led_bubble.png b/connectcore-demo-example/static/images/led_bubble.png old mode 100755 new mode 100644 diff --git a/connectcore-demo-example/static/images/led_bubble_hover.png b/connectcore-demo-example/static/images/led_bubble_hover.png old mode 100755 new mode 100644 diff --git a/connectcore-demo-example/static/js/ccimx6ulsbc.js b/connectcore-demo-example/static/js/ccimx6ulsbc.js index 788808e..2b6b995 100644 --- a/connectcore-demo-example/static/js/ccimx6ulsbc.js +++ b/connectcore-demo-example/static/js/ccimx6ulsbc.js @@ -110,17 +110,17 @@ class CCIMX6ULSBC extends ConnectCoreDevice { VIDEO_COMPONENT_AREA_WIDTH_PERCENT = 21; VIDEO_COMPONENT_AREA_HEIGHT_PERCENT = 8; - AUDIO_COMPONENT_VISIBLE = false; + AUDIO_COMPONENT_VISIBLE = true; AUDIO_COMPONENT_HAS_PANEL = true; AUDIO_COMPONENT_HAS_ARROW = true; AUDIO_COMPONENT_PANEL_ALWAYS_VISIBLE = false; - AUDIO_COMPONENT_PANEL_ORIENTATION = VALUE_BOTTOM; - AUDIO_COMPONENT_PANEL_HORIZONTAL_PERCENT = 51; - AUDIO_COMPONENT_PANEL_VERTICAL_PERCENT = 28; - AUDIO_COMPONENT_ARROW_PERCENT = 69; + AUDIO_COMPONENT_PANEL_ORIENTATION = VALUE_LEFT; + AUDIO_COMPONENT_PANEL_HORIZONTAL_PERCENT = 76.5; + AUDIO_COMPONENT_PANEL_VERTICAL_PERCENT = 76; + AUDIO_COMPONENT_ARROW_PERCENT = 81.5; AUDIO_COMPONENT_AREA_TOP_PERCENT = 74; AUDIO_COMPONENT_AREA_LEFT_PERCENT = 66; - AUDIO_COMPONENT_AREA_WIDTH_PERCENT = 9.3; + AUDIO_COMPONENT_AREA_WIDTH_PERCENT = 9.5; AUDIO_COMPONENT_AREA_HEIGHT_PERCENT = 18.5; LED_COMPONENT_VISIBLE = true; diff --git a/connectcore-demo-example/static/js/ccmp133-dvk.js b/connectcore-demo-example/static/js/ccmp133-dvk.js new file mode 100644 index 0000000..7b60622 --- /dev/null +++ b/connectcore-demo-example/static/js/ccmp133-dvk.js @@ -0,0 +1,147 @@ +/* + * Copyright 2022, Digi International Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ +class CCMP133 extends ConnectCoreDevice { + + // Public constants. + static DEVICE_TYPE = "ccmp133-dvk"; + static PLATFORM_NAME = "ConnectCore MP133"; + + // Variables. + BOARD_IMAGE = "ccmp133_board.png"; + BOARD_IMAGE_SCALE = 82; + + CPU_COMPONENT_VISIBLE = true; + CPU_COMPONENT_HAS_PANEL = true; + CPU_COMPONENT_HAS_ARROW = true; + CPU_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + CPU_COMPONENT_PANEL_ORIENTATION = VALUE_RIGHT; + CPU_COMPONENT_PANEL_HORIZONTAL_PERCENT = 68.5; + CPU_COMPONENT_PANEL_VERTICAL_PERCENT = 45; + CPU_COMPONENT_ARROW_PERCENT = 54.5; + CPU_COMPONENT_AREA_TOP_PERCENT = 50; + CPU_COMPONENT_AREA_LEFT_PERCENT = 32.9; + CPU_COMPONENT_AREA_WIDTH_PERCENT = 10; + CPU_COMPONENT_AREA_HEIGHT_PERCENT = 13; + + MEMORY_COMPONENT_VISIBLE = true; + MEMORY_COMPONENT_HAS_PANEL = true; + MEMORY_COMPONENT_HAS_ARROW = true; + MEMORY_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + MEMORY_COMPONENT_PANEL_ORIENTATION = VALUE_BOTTOM; + MEMORY_COMPONENT_PANEL_HORIZONTAL_PERCENT = 25; + MEMORY_COMPONENT_PANEL_VERTICAL_PERCENT = 67.5; + MEMORY_COMPONENT_ARROW_PERCENT = 37; + MEMORY_COMPONENT_AREA_TOP_PERCENT = 34.5; + MEMORY_COMPONENT_AREA_LEFT_PERCENT = 31.1; + MEMORY_COMPONENT_AREA_WIDTH_PERCENT = 15.4; + MEMORY_COMPONENT_AREA_HEIGHT_PERCENT = 15.5; + + WIFI_BT_COMPONENT_VISIBLE = true; + WIFI_BT_COMPONENT_HAS_PANEL = true; + WIFI_BT_COMPONENT_HAS_ARROW = true; + WIFI_BT_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + WIFI_BT_COMPONENT_PANEL_ORIENTATION = VALUE_LEFT; + WIFI_BT_COMPONENT_PANEL_HORIZONTAL_PERCENT = 55; + WIFI_BT_COMPONENT_PANEL_VERTICAL_PERCENT = 28; + WIFI_BT_COMPONENT_ARROW_PERCENT = 43.5; + WIFI_BT_COMPONENT_AREA_TOP_PERCENT = 41; + WIFI_BT_COMPONENT_AREA_LEFT_PERCENT = 46.7; + WIFI_BT_COMPONENT_AREA_WIDTH_PERCENT = 6.8; + WIFI_BT_COMPONENT_AREA_HEIGHT_PERCENT = 9; + + ETHERNET0_COMPONENT_VISIBLE = true; + ETHERNET0_COMPONENT_HAS_PANEL = true; + ETHERNET0_COMPONENT_HAS_ARROW = true; + ETHERNET0_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + ETHERNET0_COMPONENT_PANEL_ORIENTATION = VALUE_BOTTOM; + ETHERNET0_COMPONENT_PANEL_HORIZONTAL_PERCENT = -3; + ETHERNET0_COMPONENT_PANEL_VERTICAL_PERCENT = 73; + ETHERNET0_COMPONENT_ARROW_PERCENT = 8; + ETHERNET0_COMPONENT_AREA_TOP_PERCENT = 29; + ETHERNET0_COMPONENT_AREA_LEFT_PERCENT = 3.8; + ETHERNET0_COMPONENT_AREA_WIDTH_PERCENT = 12.2; + ETHERNET0_COMPONENT_AREA_HEIGHT_PERCENT = 12.5; + + CONSOLE_COMPONENT_VISIBLE = true; + CONSOLE_COMPONENT_HAS_PANEL = false; + CONSOLE_COMPONENT_HAS_ARROW = false; + CONSOLE_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + CONSOLE_COMPONENT_AREA_TOP_PERCENT = 79.9; + CONSOLE_COMPONENT_AREA_LEFT_PERCENT = 90.5; + CONSOLE_COMPONENT_AREA_WIDTH_PERCENT = 3.3; + CONSOLE_COMPONENT_AREA_HEIGHT_PERCENT = 6.2; + + VIDEO_COMPONENT_VISIBLE = true; + VIDEO_COMPONENT_HAS_PANEL = true; + VIDEO_COMPONENT_HAS_ARROW = true; + VIDEO_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + VIDEO_COMPONENT_PANEL_ORIENTATION = VALUE_TOP; + VIDEO_COMPONENT_PANEL_HORIZONTAL_PERCENT = 55; + VIDEO_COMPONENT_PANEL_VERTICAL_PERCENT = 16; + VIDEO_COMPONENT_ARROW_PERCENT = 72.8; + VIDEO_COMPONENT_AREA_TOP_PERCENT = 4.8; + VIDEO_COMPONENT_AREA_LEFT_PERCENT = 70.2; + VIDEO_COMPONENT_AREA_WIDTH_PERCENT = 8.6; + VIDEO_COMPONENT_AREA_HEIGHT_PERCENT = 9.1; + + AUDIO_COMPONENT_VISIBLE = true; + AUDIO_COMPONENT_HAS_PANEL = true; + AUDIO_COMPONENT_HAS_ARROW = true; + AUDIO_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + AUDIO_COMPONENT_PANEL_ORIENTATION = VALUE_TOP; + AUDIO_COMPONENT_PANEL_HORIZONTAL_PERCENT = 81; + AUDIO_COMPONENT_PANEL_VERTICAL_PERCENT = 37; + AUDIO_COMPONENT_ARROW_PERCENT = 87; + AUDIO_COMPONENT_AREA_TOP_PERCENT = 28; + AUDIO_COMPONENT_AREA_LEFT_PERCENT = 83.5; + AUDIO_COMPONENT_AREA_WIDTH_PERCENT = 10.2; + AUDIO_COMPONENT_AREA_HEIGHT_PERCENT = 7; + + LED_COMPONENT_VISIBLE = true; + LED_COMPONENT_HAS_PANEL = true; + LED_COMPONENT_HAS_ARROW = false; + LED_COMPONENT_PANEL_ALWAYS_VISIBLE = true; + LED_COMPONENT_PANEL_ORIENTATION = VALUE_BOTTOM; + LED_COMPONENT_PANEL_HORIZONTAL_PERCENT = 69; + LED_COMPONENT_PANEL_VERTICAL_PERCENT = 10; + LED_COMPONENT_AREA_TOP_PERCENT = 91; + LED_COMPONENT_AREA_LEFT_PERCENT = 70.6; + LED_COMPONENT_AREA_WIDTH_PERCENT = 1.8; + LED_COMPONENT_AREA_HEIGHT_PERCENT = 4; + + FLASH_MEMORY_COMPONENT_VISIBLE = true; + FLASH_MEMORY_COMPONENT_HAS_PANEL = true; + FLASH_MEMORY_COMPONENT_HAS_ARROW = true; + FLASH_MEMORY_COMPONENT_PANEL_ALWAYS_VISIBLE = false; + FLASH_MEMORY_COMPONENT_PANEL_ORIENTATION = VALUE_TOP; + FLASH_MEMORY_COMPONENT_PANEL_HORIZONTAL_PERCENT = 27; + FLASH_MEMORY_COMPONENT_PANEL_VERTICAL_PERCENT = 65; + FLASH_MEMORY_COMPONENT_ARROW_PERCENT = 46.7; + FLASH_MEMORY_COMPONENT_AREA_TOP_PERCENT = 50; + FLASH_MEMORY_COMPONENT_AREA_LEFT_PERCENT = 43.5; + FLASH_MEMORY_COMPONENT_AREA_WIDTH_PERCENT = 10; + FLASH_MEMORY_COMPONENT_AREA_HEIGHT_PERCENT = 13; + + + // Capabilities + SUPPORTS_VIDEO_BRIGHTNESS = false; + SUPPORTS_DUAL_ETHERNET = false; + + // Constructor. + constructor(deviceData) { + super(CCMP133.DEVICE_TYPE, CCMP133.PLATFORM_NAME, deviceData); + } +} \ No newline at end of file diff --git a/connectcore-demo-example/static/js/ccmp157-dvk.js b/connectcore-demo-example/static/js/ccmp157-dvk.js index 4eaa7ac..a8496b6 100644 --- a/connectcore-demo-example/static/js/ccmp157-dvk.js +++ b/connectcore-demo-example/static/js/ccmp157-dvk.js @@ -97,18 +97,18 @@ class CCMP157 extends ConnectCoreDevice { VIDEO_COMPONENT_AREA_WIDTH_PERCENT = 8.6; VIDEO_COMPONENT_AREA_HEIGHT_PERCENT = 9.1; - AUDIO_COMPONENT_VISIBLE = false; + AUDIO_COMPONENT_VISIBLE = true; AUDIO_COMPONENT_HAS_PANEL = true; AUDIO_COMPONENT_HAS_ARROW = true; AUDIO_COMPONENT_PANEL_ALWAYS_VISIBLE = false; AUDIO_COMPONENT_PANEL_ORIENTATION = VALUE_TOP; - AUDIO_COMPONENT_PANEL_HORIZONTAL_PERCENT = 1; - AUDIO_COMPONENT_PANEL_VERTICAL_PERCENT = 19; - AUDIO_COMPONENT_ARROW_PERCENT = 16; - AUDIO_COMPONENT_AREA_TOP_PERCENT = 7; - AUDIO_COMPONENT_AREA_LEFT_PERCENT = 14.5; - AUDIO_COMPONENT_AREA_WIDTH_PERCENT = 5.4; - AUDIO_COMPONENT_AREA_HEIGHT_PERCENT = 11; + AUDIO_COMPONENT_PANEL_HORIZONTAL_PERCENT = 81; + AUDIO_COMPONENT_PANEL_VERTICAL_PERCENT = 37; + AUDIO_COMPONENT_ARROW_PERCENT = 87; + AUDIO_COMPONENT_AREA_TOP_PERCENT = 28; + AUDIO_COMPONENT_AREA_LEFT_PERCENT = 83.5; + AUDIO_COMPONENT_AREA_WIDTH_PERCENT = 10.2; + AUDIO_COMPONENT_AREA_HEIGHT_PERCENT = 7; LED_COMPONENT_VISIBLE = true; LED_COMPONENT_HAS_PANEL = true; diff --git a/connectcore-demo-example/static/js/common.js b/connectcore-demo-example/static/js/common.js index d097f12..140cf5b 100644 --- a/connectcore-demo-example/static/js/common.js +++ b/connectcore-demo-example/static/js/common.js @@ -42,6 +42,7 @@ const ID_CPU_TEMPERATURE = "cpu_temperature"; const ID_CPU_UPTIME = "cpu_uptime"; const ID_CURRENT_DIR = "current_dir"; const ID_DATA = "data"; +const ID_DESC = "desc"; const ID_DEVICE_NAME = "device-name"; const ID_DEVICE_TYPE = "device_type"; const ID_DEVICES = "devices"; @@ -133,18 +134,31 @@ const VALUE_SUCCESSFUL = "successful"; const VALUE_TOP = "top"; const VALUE_UNKNOWN = "unknown"; +const CLASS_ARROW_DOWN = "fa-caret-down"; +const CLASS_ARROW_UP = "fa-caret-up"; const CLASS_D_NONE = "d-none"; const CLASS_DISABLED_DIV = "disabled-div"; const CLASS_ELEMENT_GRAYED = "element-grayed"; +const CLASS_INPUT_DISABLED = "input-control-disabled"; +const CLASS_INPUT_ERROR = "input-control-error"; +const CLASS_CONFIG_BUTTON_DISABLED = "config-button-disabled"; const CLASS_SELECTED = "selected"; const CLASS_VALUE_ANIMATION = "value-animation"; const CLASS_VALUE_UPDATED = "value-updated"; const ERROR_ABORTED = "Operation aborted"; +const ERROR_BAD_REQUEST = "Bad request"; +const ERROR_FIELD_EMPTY = "Field cannot be empty"; +const ERROR_FORBIDDEN = "Could not perform the selected action. Make sure you have the correct access rights."; const ERROR_URL_NOT_FOUND = "Requested URL not found"; const ERROR_SERVER_ERROR = "Internal server error"; const ERROR_TITLE = "Error"; -const ERROR_UNKNOWN_ERROR = "Unknown error"; +const ERROR_UNKNOWN_ERROR = "Unknown error. Make sure that the server is running."; + +const IFACE_BT = "hci0"; +const IFACE_ETH0 = "eth0"; +const IFACE_ETH1 = "eth1"; +const IFACE_WIFI = "wlan0"; const PREFIX_STREAM = "system_monitor/"; const STREAM_CPU_LOAD = PREFIX_STREAM + "cpu_load"; @@ -237,8 +251,10 @@ function processAjaxErrorResponse(response) { errorMessage = response.responseJSON[ID_ERROR]; // Show the error message (if any). if (errorMessage == null) - errorMessage = ERROR_UNKNOWN_ERROR; - } else if (response.status == 404) + errorMessage = ERROR_BAD_REQUEST; + } else if (response.status == 403) + errorMessage = ERROR_FORBIDDEN; + else if (response.status == 404) errorMessage = ERROR_URL_NOT_FOUND; else if (response.status == 500) errorMessage = ERROR_SERVER_ERROR; @@ -475,6 +491,11 @@ function isDashboardShowing() { return window.location.pathname.indexOf("index") > -1; } +// Returns whether the network page is showing or not. +function isNetworkShowing() { + return window.location.pathname.indexOf("network") > -1; +} + // Returns whether the management page is showing or not. function isManagementShowing() { return window.location.pathname.indexOf("management") > -1; @@ -522,4 +543,4 @@ function removeSection(sectionID) { var sectionElement = document.getElementById(sectionID); if (sectionElement != null && sectionElement != "undefined") sectionElement.parentNode.removeChild(sectionElement); -} \ No newline at end of file +} diff --git a/connectcore-demo-example/static/js/dashboard.js b/connectcore-demo-example/static/js/dashboard.js index 0b5614d..2261599 100644 --- a/connectcore-demo-example/static/js/dashboard.js +++ b/connectcore-demo-example/static/js/dashboard.js @@ -12,6 +12,8 @@ * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + * + * Music by https://www.bensound.com */ // Constants. @@ -55,6 +57,7 @@ const ID_MEMORY_PANEL_AREA = "memory_panel_area"; const ID_MEMORY_PANEL_ARROW = "memory_panel_arrow"; const ID_MEMORY_PANEL_ICON = "memory_panel_icon"; const ID_PLATFORM_NAME = "platform_name"; +const ID_PLAY = "play"; const ID_VIDEO_BRIGHTNESS_CONTAINER = "video_brightness_container"; const ID_VIDEO_PANEL = "video_panel"; const ID_VIDEO_PANEL_AREA = "video_panel_area"; @@ -66,36 +69,29 @@ const ID_WIFI_BT_PANEL_AREA = "wifi_bt_panel_area"; const ID_WIFI_BT_PANEL_ARROW = "wifi_bt_panel_arrow"; const ID_WIFI_BT_PANEL_ICON = "wifi_bt_panel_icon"; -const IFACE_BT = "hci0/"; -const IFACE_ETHERNET0 = "eth0/"; -const IFACE_ETHERNET1 = "eth1/"; -const IFACE_WIFI = "wlan0/"; - const USER_LED = "user_led"; const STREAM_CPU_FREQUENCY = PREFIX_STREAM + "frequency"; const STREAM_CPU_TEMPERATURE = PREFIX_STREAM + "cpu_temperature"; const STREAM_CPU_UPTIME = PREFIX_STREAM + "uptime"; -const STREAM_ETHERNET0_READ_BYTES = PREFIX_STREAM + IFACE_ETHERNET0 + "rx_bytes"; -const STREAM_ETHERNET0_SENT_BYTES = PREFIX_STREAM + IFACE_ETHERNET0 + "tx_bytes"; -const STREAM_ETHERNET0_STATE = PREFIX_STREAM + IFACE_ETHERNET0 + "state"; -const STREAM_ETHERNET1_READ_BYTES = PREFIX_STREAM + IFACE_ETHERNET1 + "rx_bytes"; -const STREAM_ETHERNET1_SENT_BYTES = PREFIX_STREAM + IFACE_ETHERNET1 + "tx_bytes"; -const STREAM_ETHERNET1_STATE = PREFIX_STREAM + IFACE_ETHERNET1 + "state"; +const STREAM_ETHERNET0_READ_BYTES = PREFIX_STREAM + IFACE_ETH0 + "/rx_bytes"; +const STREAM_ETHERNET0_SENT_BYTES = PREFIX_STREAM + IFACE_ETH0 + "/tx_bytes"; +const STREAM_ETHERNET0_STATE = PREFIX_STREAM + IFACE_ETH0 + "/state"; +const STREAM_ETHERNET1_READ_BYTES = PREFIX_STREAM + IFACE_ETH1 + "/rx_bytes"; +const STREAM_ETHERNET1_SENT_BYTES = PREFIX_STREAM + IFACE_ETH1 + "/tx_bytes"; +const STREAM_ETHERNET1_STATE = PREFIX_STREAM + IFACE_ETH1 + "/state"; const STREAM_LED_STATUS = PREFIX_STREAM + "led_status"; const STREAM_MEMORY_USED = PREFIX_STREAM + "used_memory"; -const STREAM_WIFI_READ_BYTES = PREFIX_STREAM + IFACE_WIFI + "rx_bytes"; -const STREAM_WIFI_SENT_BYTES = PREFIX_STREAM + IFACE_WIFI + "tx_bytes"; -const STREAM_WIFI_STATE = PREFIX_STREAM + IFACE_WIFI + "state"; -const STREAM_BT_READ_BYTES = PREFIX_STREAM + IFACE_BT + "rx_bytes"; -const STREAM_BT_SENT_BYTES = PREFIX_STREAM + IFACE_BT + "tx_bytes"; -const STREAM_BT_STATE = PREFIX_STREAM + IFACE_BT + "state"; +const STREAM_WIFI_READ_BYTES = PREFIX_STREAM + IFACE_WIFI + "/rx_bytes"; +const STREAM_WIFI_SENT_BYTES = PREFIX_STREAM + IFACE_WIFI + "/tx_bytes"; +const STREAM_WIFI_STATE = PREFIX_STREAM + IFACE_WIFI + "/state"; +const STREAM_BT_READ_BYTES = PREFIX_STREAM + IFACE_BT + "/rx_bytes"; +const STREAM_BT_SENT_BYTES = PREFIX_STREAM + IFACE_BT + "/tx_bytes"; +const STREAM_BT_STATE = PREFIX_STREAM + IFACE_BT + "/state"; const PANEL_ARROW_WIDTH_100 = 20; const PANEL_BOARD_WIDTH_100 = 1200; -const CLASS_ARROW_DOWN = "fa-caret-down"; -const CLASS_ARROW_UP = "fa-caret-up"; const CLASS_LED_PANEL_AREA_ON = "led-panel-area-on"; const CLASS_PANEL_AREA_SELECTED = "panel-area-selected"; const CLASS_PANEL_AREA_ICON_SELECTED = "panel-area-icon-selected"; @@ -103,6 +99,9 @@ const CLASS_PANEL_TOOLTIP = "panel-tooltip"; const MESSAGE_CHANGING_VIDEO_BRIGHTNESS = "Changing video brightness..."; const MESSAGE_CHANGING_AUDIO_VOLUME = "Changing audio volume..."; +const MESSAGE_MUSIC_PLAYING = "Music playing..." +const MESSAGE_MUSIC_STOPPED = "Music stopped." +const MESSAGE_PLAY_MUSIC = "Setting play music value..." const MESSAGE_READING_DEVICE_INFO = "Reading device info..."; const MESSAGE_READING_DEVICE_STATUS = "Reading device status..."; const MESSAGE_TOGGLING_LED_VALUE = "Toggling LED value..."; @@ -111,6 +110,8 @@ const ERROR_LED_UNKNOWN = "LED status has not been read yet, please wait."; const ERROR_NOT_SUPPORTED_DEVICE_MESSAGE = "The selected device type is not supported: {0}"; const ERROR_NOT_SUPPORTED_DEVICE_TITLE = "Unsupported device"; +const MUSIC_FILE = "/srv/www/static/sounds/inspire.mp3" + // Variables. var deviceInitialized = false; var device = null; @@ -239,6 +240,7 @@ function processDeviceInfoResponse(response) { // Position components after some time to give time to the image to load. window.setTimeout(function () { positionComponents(); + adjustImageSize(); setInfoPanelsVisible(false); }, 500); // Read device status. @@ -368,6 +370,9 @@ function createDevice(deviceData) { case CCMP157.DEVICE_TYPE: device = new CCMP157(deviceData); break; + case CCMP133.DEVICE_TYPE: + device = new CCMP133(deviceData); + break; } if (device != null) { // Draw the device. @@ -934,6 +939,50 @@ function processSetVideoBrightnessResponse(response) { videoSlider.enable(); } +// Handles what happens when the Play music button is pressed. +function playMusic(play) { + // Show the loading panel of the device. + showLoadingPopup(true, MESSAGE_PLAY_MUSIC); + // Send request to play music. + $.post( + "http://" + getServerAddress() + "/ajax/play_music", + JSON.stringify({ + "play": play, + "music_file": MUSIC_FILE + }), + function(data) { + // Process only in the dashboard page. + if (!isDashboardShowing()) + return; + // Process answer. + processPlayMusicResponse(data); + } + ).fail(function(response) { + // Process only in the dashboard page. + if (!isDashboardShowing()) + return; + // Process error. + processAjaxErrorResponse(response); + // Hide the loading panel of the device. + showLoadingPopup(false); + }); +} + +// Processes the "play music" request answer. +function processPlayMusicResponse(response) { + // Check if there was any error in the request. + if (!checkErrorResponse(response, false)) { + play = response[ID_PLAY]; + // Show confirmation. + if (play) + toastr.info(MESSAGE_MUSIC_PLAYING); + else + toastr.info(MESSAGE_MUSIC_STOPPED); + } + // Hide the loading panel of the device. + showLoadingPopup(false); +} + // Processes an audio volume changed event. function volumeChanged(newValue) { // Show the loading panel of the device. @@ -976,10 +1025,9 @@ function processSetAudioVolumeResponse(response) { audioSlider.setValue(volume); } else { // Save new volume value. - volume = response["value"]; - if (volume == null) - volume = audioSlider.getValue(); - audioSlider.setValue(volume); + volume = audioSlider.getValue(); + // Show confirmation. + toastr.info("Volume changed to " + volume + "%") } // Hide the loading panel of the device. showLoadingPopup(false); @@ -1049,6 +1097,10 @@ function processSetLEDResponse(response) { if (!checkErrorResponse(response, false)) { // Update the LED status. updateLEDStatus(); + if (ledStatus) + toastr.info("User LED set to ON"); + else + toastr.info("User LED set to OFF"); } // Hide the loading panel of the device. showLoadingPopup(false); diff --git a/connectcore-demo-example/static/js/devices.js b/connectcore-demo-example/static/js/devices.js index c52cc40..146bed5 100644 --- a/connectcore-demo-example/static/js/devices.js +++ b/connectcore-demo-example/static/js/devices.js @@ -325,6 +325,11 @@ class ConnectCoreDevice { return this.#ethernetIP[index]; } + // Returns whether the device supports Wifi or not. + hasWifi() { + return this.#wifiMAC != null && this.#wifiMAC != "undefined" + } + // Returns the device WiFi MAC address. getWifiMAC() { return this.#wifiMAC; diff --git a/connectcore-demo-example/static/js/management.js b/connectcore-demo-example/static/js/management.js index e692a3c..40c23d1 100644 --- a/connectcore-demo-example/static/js/management.js +++ b/connectcore-demo-example/static/js/management.js @@ -31,7 +31,6 @@ const ID_UPDATE_RUNNING = "update_running"; const CLASS_FIRMWARE_TAB = "firmware-tab"; const CLASS_FIRMWARE_TAB_HEADER = "firmware-tab-header"; const CLASS_FIRMWARE_TAB_HEADER_ACTIVE = "firmware-tab-header-active"; -const CLASS_MANAGEMENT_BUTTON_DISABLED = "management-button-disabled"; const CLASS_PROGRESS_BAR_ERROR = "update-firmware-progress-bar-error"; const CLASS_PROGRESS_BAR_INFO = "update-firmware-progress-bar-info"; const CLASS_PROGRESS_BAR_SUCCESS = "update-firmware-progress-bar-success"; @@ -625,10 +624,12 @@ function enableManagementButton(buttonID, enable) { var buttonElement = document.getElementById(buttonID); if (buttonElement != null) { if (enable) { - if (buttonElement.classList.contains(CLASS_MANAGEMENT_BUTTON_DISABLED)) - buttonElement.classList.remove(CLASS_MANAGEMENT_BUTTON_DISABLED); - } else - buttonElement.classList.add(CLASS_MANAGEMENT_BUTTON_DISABLED); + if (buttonElement.classList.contains(CLASS_CONFIG_BUTTON_DISABLED)) + buttonElement.classList.remove(CLASS_CONFIG_BUTTON_DISABLED); + } else { + if (!buttonElement.classList.contains(CLASS_CONFIG_BUTTON_DISABLED)) + buttonElement.classList.add(CLASS_CONFIG_BUTTON_DISABLED); + } } } diff --git a/connectcore-demo-example/static/js/network.js b/connectcore-demo-example/static/js/network.js new file mode 100644 index 0000000..6fd2869 --- /dev/null +++ b/connectcore-demo-example/static/js/network.js @@ -0,0 +1,459 @@ +/* + * Copyright 2022, Digi International Inc. + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +// Constants. +const ELEMENT_ETHERNET = "ethernet"; +const ELEMENT_WIFI = "wifi"; + +const FIELD_DEFAULT_GATEWAY = "_default_gateway"; +const FIELD_DNS1_ADDRESS = "_dns1_addr"; +const FIELD_DNS2_ADDRESS = "_dns2_addr"; +const FIELD_ENABLE = "_enable"; +const FIELD_ENCRYPTION_TYPE = "_enc_type"; +const FIELD_ERROR = "_error"; +const FIELD_MAC_ADDRESS = "_mac"; +const FIELD_IP_ADDRESS = "_ip_addr"; +const FIELD_IP_MODE = "_ip_mode"; +const FIELD_PARAM = "_param"; +const FIELD_PASSWORD = "_password"; +const FIELD_SAVE_BUTTON = "_save_button"; +const FIELD_SSID = "_ssid"; +const FIELD_SUBNET_MASK = "_subnet_mask"; + +const ID_DNS1 = "dns1"; +const ID_DNS2 = "dns2"; +const ID_ENABLE = "enable"; +const ID_ETH0_TITLE = "eth0_title"; +const ID_GATEWAY = "gateway"; +const ID_IP = "ip"; +const ID_IP_MODE = "type"; +const ID_INTERFACE = "interface"; +const ID_MAC = "mac"; +const ID_NETMASK = "netmask"; +const ID_PANEL_CONTAINER = "_panel_container"; +const ID_PASSWORD = "psk"; +const ID_SECURITY_MODE = "sec_mode"; +const ID_SSID = "ssid"; +const ID_TOGGLE_BUTTON = "_toggle_button"; + +const IP_MODE_DHCP = "dhcp"; +const IP_MODE_STATIC = "static"; + +const ERROR_IP_VALUE_FORMAT = "Value must follow this format: XXX.XXX.XXX.XXX"; +const ERROR_PASSWORD_INVALID = "Password must be between 8 and 63 characters long"; +const ERROR_SSID_INVALID = "SSID value is not valid"; +const ERROR_UNKNOWN = "Unknown error saving configuration."; + +const MESSAGE_LOADING_CONFIGURATION = "Loading configuration..."; +const MESSAGE_SAVING_CONFIGURATION = "Saving configuration..."; +const MESSAGE_CONFIGURATION_SAVED = "Configuration saved successfully!"; + +const PREFIX_ETHERNET = "eth"; +const PREFIX_WIFI = "wlan"; + +const REGEX_IP = '^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$'; +const REGEX_SSID = '^[^!#;+\\]/"\t][^+\\]/"\t]{0,31}$'; +const REGEX_PASSWORD = '^[\u0020-\u007e]{8,63}$'; + +const ALL_ELEMENTS = "all"; + +// Variables. +var readingNetworkInfo = false; +var passwordChanged = false; + +// Initializes the network page. +function initializeNetworkPage() { + // Sanity checks. + if (!isNetworkShowing()) + return; + // Read network configuration. + readElements = [ELEMENT_ETHERNET, ELEMENT_WIFI]; + readConfiguration(readElements, ALL_ELEMENTS); +} + +// Gets the device configuration the given elements. +function readConfiguration(readElements, parseElement) { + // Execute only in the network page. + if (!isNetworkShowing() || readingNetworkInfo) + return; + // Flag reading variable. + readingNetworkInfo = true; + // Hide the info popup. + showInfoPopup(false); + // Show the loading popup. + showLoadingPopup(true, MESSAGE_LOADING_CONFIGURATION); + // Send request to retrieve configuration. + $.post( + "http://" + getServerAddress() + "/ajax/get_config", + JSON.stringify({ + "elements": readElements + }), + function(data) { + // Process only in the network page. + if (!isNetworkShowing()) + return; + // Process answer. + processReadConfigurationResponse(data, parseElement); + } + ).fail(function(response) { + // Flag reading variable. + readingNetworkInfo = false; + // Clear the element being read. + elementReading = ""; + // Process only in the network page. + if (!isNetworkShowing()) + return; + // Hide the loading panel. + showLoadingPopup(false); + // Process error. + processAjaxErrorResponse(response); + // Update controls. + updateAllControls(); + }); +} + +// Processes the response of the read configuration request. +function processReadConfigurationResponse(response, parseElement) { + // Check if there was any error in the request. + if (!checkErrorResponse(response, false)) { + // Fill network info. + fillNetworkInfo(JSON.parse(response[ID_DATA]), parseElement); + } else { + // Flag reading variable. + readingNetworkInfo = false; + // Hide the loading panel. + showLoadingPopup(false); + } +} + +// Fills network information. +function fillNetworkInfo(response, parseElement) { + var numEthernetIfaces = 0; + var numWifiIfaces = 0; + for (const [element, elementData] of Object.entries(response)) { + if (element != ELEMENT_ETHERNET && element != ELEMENT_WIFI) + continue; + for (const [iface, ifaceData] of Object.entries(elementData)) { + if (!iface.startsWith(PREFIX_WIFI) && !iface.startsWith(PREFIX_ETHERNET)) + continue; + if (parseElement != ALL_ELEMENTS && iface != parseElement) + continue; + var enabled = ifaceData[ID_ENABLE]; + var ipMode = ifaceData[ID_IP_MODE]; + if (iface.startsWith(PREFIX_ETHERNET)) + numEthernetIfaces += 1; + else if (iface.startsWith(PREFIX_WIFI)) + numWifiIfaces += 1; + // Fill MAC. + document.getElementById(iface + FIELD_MAC_ADDRESS).innerText = ifaceData[ID_MAC]; + // Set enable state. + document.getElementById(iface + FIELD_ENABLE).checked = enabled; + // Set IP mode. + if (ipMode == 0) + document.getElementById(iface + FIELD_IP_MODE).value = IP_MODE_STATIC; + else + document.getElementById(iface + FIELD_IP_MODE).value = IP_MODE_DHCP; + // Fill the rest of the fields. + document.getElementById(iface + FIELD_IP_ADDRESS).value = ifaceData[ID_IP]; + document.getElementById(iface + FIELD_SUBNET_MASK).value = ifaceData[ID_NETMASK]; + document.getElementById(iface + FIELD_DEFAULT_GATEWAY).value = ifaceData[ID_GATEWAY]; + document.getElementById(iface + FIELD_DNS1_ADDRESS).value = ifaceData[ID_DNS1]; + document.getElementById(iface + FIELD_DNS2_ADDRESS).value = ifaceData[ID_DNS2]; + // Specific Wifi fields. + if (iface.startsWith(PREFIX_WIFI)) { + document.getElementById(iface + FIELD_SSID).value = ifaceData[ID_SSID]; + document.getElementById(iface + FIELD_ENCRYPTION_TYPE).selectedIndex = ifaceData[ID_SECURITY_MODE]; + if (ifaceData[ID_SECURITY_MODE] != 0) + document.getElementById(iface + FIELD_PASSWORD).value = "dummy_password"; + passwordChanged = false; + } + // Update controls. + updateInterfaceControls(iface); + } + } + // Update interfaces visibility. + if (parseElement == ALL_ELEMENTS) { + if (numEthernetIfaces == 1) { + document.getElementById(IFACE_ETH1).style.display = "none"; + document.getElementById(ID_ETH0_TITLE).innerHTML = "Ethernet"; + } + if (numWifiIfaces == 0) + document.getElementById(IFACE_WIFI).style.display = "none"; + } + // Flag reading variable. + readingNetworkInfo = false; + // Hide the loading panel. + showLoadingPopup(false); +} + +// Updates all controls of the page. +function updateAllControls() { + updateInterfaceControls(IFACE_ETH0); + updateInterfaceControls(IFACE_ETH1); + updateInterfaceControls(IFACE_WIFI); +} + +// Updates the given interface controls based on its configuration. +function updateInterfaceControls(iface) { + // Initialize variables. + var ipGroup = document.getElementById(iface + FIELD_IP_ADDRESS + FIELD_PARAM); + var ipField = document.getElementById(iface + FIELD_IP_ADDRESS); + var subnetGroup = document.getElementById(iface + FIELD_SUBNET_MASK + FIELD_PARAM); + var subnetField = document.getElementById(iface + FIELD_SUBNET_MASK); + var gatewayGroup = document.getElementById(iface + FIELD_DEFAULT_GATEWAY + FIELD_PARAM); + var gatewayField = document.getElementById(iface + FIELD_DEFAULT_GATEWAY); + var dns1Group = document.getElementById(iface + FIELD_DNS1_ADDRESS + FIELD_PARAM); + var dns1Field = document.getElementById(iface + FIELD_DNS1_ADDRESS); + var dns2Group = document.getElementById(iface + FIELD_DNS2_ADDRESS + FIELD_PARAM); + var dns2Field = document.getElementById(iface + FIELD_DNS2_ADDRESS); + var ipModeGroup = document.getElementById(iface + FIELD_IP_MODE + FIELD_PARAM); + var ipModeField = document.getElementById(iface + FIELD_IP_MODE); + var enableField = document.getElementById(iface + FIELD_ENABLE); + var enabled = enableField.checked; + var ipMode = ipModeField.value; + var ssidGroup, encryptionTypeGroup, encryptionTypeField, passwordGroup, usePassword; + if (iface.startsWith(PREFIX_WIFI)) { + ssidGroup = document.getElementById(iface + FIELD_SSID + FIELD_PARAM); + encryptionTypeGroup = document.getElementById(iface + FIELD_ENCRYPTION_TYPE + FIELD_PARAM); + encryptionTypeField = document.getElementById(iface + FIELD_ENCRYPTION_TYPE); + passwordGroup = document.getElementById(iface + FIELD_PASSWORD + FIELD_PARAM); + usePassword = encryptionTypeField.value != 0; + if (usePassword) + passwordGroup.style.display = "block"; + else + passwordGroup.style.display = "none"; + } + // Determine if IP fields can be edited. + if (ipMode == IP_MODE_DHCP) { + if (!ipField.classList.contains(CLASS_INPUT_DISABLED)) + ipField.classList.add(CLASS_INPUT_DISABLED); + if (!subnetField.classList.contains(CLASS_INPUT_DISABLED)) + subnetField.classList.add(CLASS_INPUT_DISABLED); + if (!gatewayField.classList.contains(CLASS_INPUT_DISABLED)) + gatewayField.classList.add(CLASS_INPUT_DISABLED); + if (!dns1Field.classList.contains(CLASS_INPUT_DISABLED)) + dns1Field.classList.add(CLASS_INPUT_DISABLED); + if (!dns2Field.classList.contains(CLASS_INPUT_DISABLED)) + dns2Field.classList.add(CLASS_INPUT_DISABLED); + } else if (ipMode == IP_MODE_STATIC) { + if (ipField.classList.contains(CLASS_INPUT_DISABLED)) + ipField.classList.remove(CLASS_INPUT_DISABLED); + if (subnetField.classList.contains(CLASS_INPUT_DISABLED)) + subnetField.classList.remove(CLASS_INPUT_DISABLED); + if (gatewayField.classList.contains(CLASS_INPUT_DISABLED)) + gatewayField.classList.remove(CLASS_INPUT_DISABLED); + if (dns1Field.classList.contains(CLASS_INPUT_DISABLED)) + dns1Field.classList.remove(CLASS_INPUT_DISABLED); + if (dns2Field.classList.contains(CLASS_INPUT_DISABLED)) + dns2Field.classList.remove(CLASS_INPUT_DISABLED); + } + // Validate the network interface. + validateInterface(iface); +} + +// Validates the given network interface. +function validateInterface(iface) { + // Initialize vars. + var valid = true; + var saveButton = document.getElementById(iface + FIELD_SAVE_BUTTON); + var enableField = document.getElementById(iface + FIELD_ENABLE); + var ipModeField = document.getElementById(iface + FIELD_IP_MODE); + var ipField = document.getElementById(iface + FIELD_IP_ADDRESS); + var subnetField = document.getElementById(iface + FIELD_SUBNET_MASK); + var gatewayField = document.getElementById(iface + FIELD_DEFAULT_GATEWAY); + var dns1Field = document.getElementById(iface + FIELD_DNS1_ADDRESS); + var dns2Field = document.getElementById(iface + FIELD_DNS2_ADDRESS); + var ipMode = ipModeField.value; + var forceValid = false; + // Validate fields. + if (ipMode == IP_MODE_DHCP) + forceValid = true; + valid &= validateIPField(iface + FIELD_IP_ADDRESS, iface + FIELD_IP_ADDRESS + FIELD_ERROR, forceValid); + valid &= validateIPField(iface + FIELD_SUBNET_MASK, iface + FIELD_SUBNET_MASK + FIELD_ERROR, forceValid); + valid &= validateIPField(iface + FIELD_DEFAULT_GATEWAY, iface + FIELD_DEFAULT_GATEWAY + FIELD_ERROR, forceValid); + valid &= validateIPField(iface + FIELD_DNS1_ADDRESS, iface + FIELD_DNS1_ADDRESS + FIELD_ERROR, forceValid); + valid &= validateIPField(iface + FIELD_DNS2_ADDRESS, iface + FIELD_DNS2_ADDRESS + FIELD_ERROR, forceValid); + if (iface.startsWith(PREFIX_WIFI)) { + valid &= validateField(iface + FIELD_SSID, iface + FIELD_SSID + FIELD_ERROR, REGEX_SSID, ERROR_SSID_INVALID, false); + if (parseInt(document.getElementById(iface + FIELD_ENCRYPTION_TYPE).value) != 0) + valid &= validateField(iface + FIELD_PASSWORD, iface + FIELD_PASSWORD + FIELD_ERROR, REGEX_PASSWORD, ERROR_PASSWORD_INVALID, false); + } + // Check errors. + if (!valid) { + if (!saveButton.classList.contains(CLASS_CONFIG_BUTTON_DISABLED)) + saveButton.classList.add(CLASS_CONFIG_BUTTON_DISABLED); + } else { + if (saveButton.classList.contains(CLASS_CONFIG_BUTTON_DISABLED)) + saveButton.classList.remove(CLASS_CONFIG_BUTTON_DISABLED); + } +} + +// Validates the given IP field. +function validateIPField(fieldID, errorID, forceValid) { + return validateField(fieldID, errorID, REGEX_IP, ERROR_IP_VALUE_FORMAT, forceValid); +} + +// Validates the given field. +function validateField(fieldID, errorID, regexPattern, errorMessage, forceValid) { + // Initialize vars. + var field = document.getElementById(fieldID); + var fieldError = document.getElementById(errorID); + var isValid = true; + var error = ""; + // Sanity checks. + if (field == null || fieldError == null) + return false; + // Check if value is valid. + if (!forceValid) { + var value = field.value; + if (value.length == 0) { + isValid = false; + error = ERROR_FIELD_EMPTY; + } else if (!value.match(regexPattern)) { + isValid = false; + error = errorMessage; + } + } + // Update controls. + if (isValid) { + if (field.classList.contains(CLASS_INPUT_ERROR)) + field.classList.remove(CLASS_INPUT_ERROR); + fieldError.innerHTML = " "; + fieldError.style.display = "none"; + } else { + if (!field.classList.contains(CLASS_INPUT_ERROR)) + field.classList.add(CLASS_INPUT_ERROR); + fieldError.innerHTML = error; + fieldError.style.display = "block"; + } + return isValid; +} + +// Retrieves the given network interface form data. +function getInterfaceData(element, iface) { + // Initialize variables. + var data = {}; + var ifaceData = {}; + var networkData = {}; + var enable = document.getElementById(iface + FIELD_ENABLE).checked; + var ipMode = document.getElementById(iface + FIELD_IP_MODE).value; + // Fill network data. + networkData[ID_ENABLE] = enable; + if (ipMode == IP_MODE_STATIC) { + networkData[ID_IP_MODE] = 0; + networkData[ID_IP] = document.getElementById(iface + FIELD_IP_ADDRESS).value; + networkData[ID_NETMASK] = document.getElementById(iface + FIELD_SUBNET_MASK).value; + networkData[ID_GATEWAY] = document.getElementById(iface + FIELD_DEFAULT_GATEWAY).value; + networkData[ID_DNS1] = document.getElementById(iface + FIELD_DNS1_ADDRESS).value; + networkData[ID_DNS2] = document.getElementById(iface + FIELD_DNS2_ADDRESS).value; + } else { + networkData[ID_IP_MODE] = 1; + } + if (iface.startsWith(PREFIX_WIFI)) { + networkData[ID_SSID] = document.getElementById(iface + FIELD_SSID).value; + networkData[ID_SECURITY_MODE] = parseInt(document.getElementById(iface + FIELD_ENCRYPTION_TYPE).value); + if (networkData[ID_SECURITY_MODE] != 0 && passwordChanged) + networkData[ID_PASSWORD] = document.getElementById(iface + FIELD_PASSWORD).value; + } + ifaceData[iface] = networkData; + data[element] = ifaceData; + return data; +} + +// Saves the network settings for the given interface. +function saveInterface(element, iface) { + // Execute only in the network page. + if (!isNetworkShowing()) + return; + // Hide the info popup. + showInfoPopup(false); + // Show the loading popup. + showLoadingPopup(true, MESSAGE_SAVING_CONFIGURATION); + // Send request to set new configuration. + $.post( + "http://" + getServerAddress() + "/ajax/set_config", + JSON.stringify({ + "configuration": getInterfaceData(element, iface) + }), + function(data) { + // Process only in the network page. + if (!isNetworkShowing()) + return; + // Process answer. + processSaveInterfaceResponse(data, element, iface); + } + ).fail(function(response) { + // Process only in the network page. + if (!isNetworkShowing()) + return; + // Hide the loading panel. + showLoadingPopup(false); + // Process error. + processAjaxErrorResponse(response); + }); +} + +// Processes the save network request response. +function processSaveInterfaceResponse(response, element, iface) { + // Check for error in the response. + if (!checkErrorResponse(response, false)) { + var statusFound = false; + // Check status in the response. + if (response[ID_DATA] != null) { + data = JSON.parse(response[ID_DATA]); + for (const [elementEntry, elementData] of Object.entries(data)) { + if (elementEntry != element) + continue; + for (const [ifaceEntry, ifaceData] of Object.entries(elementData)) { + if (ifaceEntry != iface) + continue; + if (ifaceData[ID_STATUS] != null) { + statusFound = true; + if (ifaceData[ID_STATUS] != 0) + toastr.error(ifaceData[ID_DESC]); + else + toastr.success(MESSAGE_CONFIGURATION_SAVED); + } + } + } + } + if (!statusFound) + toastr.error(ERROR_UNKNOWN); + } + // Hide the loading panel. + showLoadingPopup(false); +} + +// Toggles the visibility of the given interface panel. +function togglePanelVisibility(interface) { + // Initialize variables. + var panelButton = document.getElementById(interface + ID_TOGGLE_BUTTON); + // Sanity checks. + if (panelButton == null) + return; + // Check visibility. + if (panelButton.classList.contains(CLASS_ARROW_UP)) { + $("#" + interface + ID_PANEL_CONTAINER).slideUp("fast", function() { + panelButton.classList.remove(CLASS_ARROW_UP); + panelButton.classList.add(CLASS_ARROW_DOWN); + }); + } else { + $("#" + interface + ID_PANEL_CONTAINER).slideDown("fast", function() { + panelButton.classList.remove(CLASS_ARROW_DOWN); + panelButton.classList.add(CLASS_ARROW_UP); + }); + } +} \ No newline at end of file diff --git a/connectcore-demo-example/static/js/sidebar.js b/connectcore-demo-example/static/js/sidebar.js index dcb7192..0c760a9 100644 --- a/connectcore-demo-example/static/js/sidebar.js +++ b/connectcore-demo-example/static/js/sidebar.js @@ -93,5 +93,10 @@ function setSelectedSection(element=null) { } }); } + if (isDashboardShowing()) { + window.setTimeout(function () { + adjustImageSize(); + }, 300); + } } diff --git a/connectcore-demo-example/static/sounds/inspire.mp3 b/connectcore-demo-example/static/sounds/inspire.mp3 new file mode 100755 index 0000000..93a1c56 Binary files /dev/null and b/connectcore-demo-example/static/sounds/inspire.mp3 differ