diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..dd15b5c --- /dev/null +++ b/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import controllers diff --git a/__manifest__.py b/__manifest__.py new file mode 100644 index 0000000..fd4a03a --- /dev/null +++ b/__manifest__.py @@ -0,0 +1,24 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +{ + 'name': 'IoT Box Homepage', + 'category': 'Sales/Point of Sale', + 'sequence': 6, + 'website': 'https://www.odoo.com/app/point-of-sale-hardware', + 'summary': 'A homepage for the IoT Box', + 'description': """ +IoT Box Homepage +================ + +This module overrides Odoo web interface to display a simple +Homepage that explains what's the iotbox and shows the status, +and where to find documentation. + +If you activate this module, you won't be able to access the +regular Odoo interface anymore. + +""", + 'installable': False, + 'license': 'LGPL-3', +} diff --git a/controllers/__init__.py b/controllers/__init__.py new file mode 100644 index 0000000..5d4b25d --- /dev/null +++ b/controllers/__init__.py @@ -0,0 +1,4 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +from . import main diff --git a/controllers/main.py b/controllers/main.py new file mode 100644 index 0000000..7c47633 --- /dev/null +++ b/controllers/main.py @@ -0,0 +1,526 @@ +# -*- coding: utf-8 -*- +# Part of Odoo. See LICENSE file for full copyright and licensing details. + +import json +import jinja2 +import platform +import logging +import os +from pathlib import Path +import socket +import subprocess +import sys +import threading + +from odoo import http, service, tools +from odoo.http import Response, request +from odoo.addons.hw_drivers.connection_manager import connection_manager +from odoo.addons.hw_drivers.main import iot_devices +from odoo.addons.hw_drivers.tools import helpers +from odoo.addons.web.controllers.home import Home +from odoo.tools.misc import file_path + +_logger = logging.getLogger(__name__) + + +#---------------------------------------------------------- +# Controllers +#---------------------------------------------------------- + +if hasattr(sys, 'frozen'): + # When running on compiled windows binary, we don't have access to package loader. + path = os.path.realpath(os.path.join(os.path.dirname(__file__), '..', 'views')) + loader = jinja2.FileSystemLoader(path) +else: + loader = jinja2.PackageLoader('odoo.addons.hw_posbox_homepage', "views") + +jinja_env = jinja2.Environment(loader=loader, autoescape=True) +jinja_env.filters["json"] = json.dumps + +homepage_template = jinja_env.get_template('homepage.html') +server_config_template = jinja_env.get_template('server_config.html') +wifi_config_template = jinja_env.get_template('wifi_config.html') +handler_list_template = jinja_env.get_template('handler_list.html') +remote_connect_template = jinja_env.get_template('remote_connect.html') +configure_wizard_template = jinja_env.get_template('configure_wizard.html') +six_payment_terminal_template = jinja_env.get_template('six_payment_terminal.html') +list_credential_template = jinja_env.get_template('list_credential.html') +upgrade_page_template = jinja_env.get_template('upgrade_page.html') + +class IoTboxHomepage(Home): + def __init__(self): + super(IoTboxHomepage,self).__init__() + self.updating = threading.Lock() + + def clean_partition(self): + subprocess.check_call(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; cleanup']) + + def get_six_terminal(self): + terminal_id = helpers.read_file_first_line('odoo-six-payment-terminal.conf') + return terminal_id or 'Not Configured' + + def get_homepage_data(self): + hostname = str(socket.gethostname()) + if platform.system() == 'Linux': + ssid = helpers.get_ssid() + wired = helpers.read_file_first_line('/sys/class/net/eth0/operstate') + else: + wired = 'up' + if wired == 'up': + network = 'Ethernet' + elif ssid: + if helpers.access_point(): + network = 'Wifi access point' + else: + network = 'Wifi : ' + ssid + else: + network = 'Not Connected' + + is_certificate_ok, certificate_details = helpers.get_certificate_status() + + iot_device = [] + for device in iot_devices: + iot_device.append({ + 'name': iot_devices[device].device_name + ' : ' + str(iot_devices[device].data['value']), + 'type': iot_devices[device].device_type.replace('_', ' '), + 'identifier': iot_devices[device].device_identifier, + }) + + return { + 'hostname': hostname, + 'ip': helpers.get_ip(), + 'mac': helpers.get_mac_address(), + 'iot_device_status': iot_device, + 'server_status': helpers.get_odoo_server_url() or 'Not Configured', + 'pairing_code': connection_manager.pairing_code, + 'six_terminal': self.get_six_terminal(), + 'network_status': network, + 'version': helpers.get_version(), + 'system': platform.system(), + 'is_certificate_ok': is_certificate_ok, + 'certificate_details': certificate_details, + } + + @http.route() + def index(self): + wifi = Path.home() / 'wifi_network.txt' + remote_server = Path.home() / 'odoo-remote-server.conf' + if (wifi.exists() == False or remote_server.exists() == False) and helpers.access_point(): + return "" + else: + return homepage_template.render(self.get_homepage_data()) + + @http.route('/list_handlers', type='http', auth='none', website=True, csrf=False, save_session=False) + def list_handlers(self, **post): + AVAILABLE_LOG_LEVELS = ('debug', 'info', 'warning', 'error') + if request.httprequest.method == 'POST': + IOT_LOGGING_PREFIX = 'iot-logging-' + INTERFACE_PREFIX = 'interface-' + DRIVER_PREFIX = 'driver-' + AVAILABLE_LOG_LEVELS_WITH_PARENT = AVAILABLE_LOG_LEVELS + ('parent',) + need_config_save = False + for post_request_key, log_level_or_parent in post.items(): + if not post_request_key.startswith(IOT_LOGGING_PREFIX): + # probably a new post request payload argument not related to logging + continue + post_request_key = post_request_key[len(IOT_LOGGING_PREFIX):] + + if post_request_key == 'root': + need_config_save |= self._update_logger_level('', log_level_or_parent, AVAILABLE_LOG_LEVELS) + elif post_request_key == 'odoo': + need_config_save |= self._update_logger_level('odoo', log_level_or_parent, AVAILABLE_LOG_LEVELS) + need_config_save |= self._update_logger_level('werkzeug', log_level_or_parent if log_level_or_parent != 'debug' else 'info', AVAILABLE_LOG_LEVELS) + elif post_request_key.startswith(INTERFACE_PREFIX): + logger_name = post_request_key[len(INTERFACE_PREFIX):] + need_config_save |= self._update_logger_level(logger_name, log_level_or_parent, AVAILABLE_LOG_LEVELS_WITH_PARENT, 'interfaces') + elif post_request_key.startswith(DRIVER_PREFIX): + logger_name = post_request_key[len(DRIVER_PREFIX):] + need_config_save |= self._update_logger_level(logger_name, log_level_or_parent, AVAILABLE_LOG_LEVELS_WITH_PARENT, 'drivers') + else: + _logger.warning('Unhandled iot logger: %s', post_request_key) + + # Update and save the config file (in case of IoT box reset) + if need_config_save: + with helpers.writable(): + tools.config.save() + drivers_list = helpers.list_file_by_os(file_path('hw_drivers/iot_handlers/drivers')) + interfaces_list = helpers.list_file_by_os(file_path('hw_drivers/iot_handlers/interfaces')) + return handler_list_template.render({ + 'title': "Odoo's IoT Box - Handlers list", + 'breadcrumb': 'Handlers list', + 'drivers_list': drivers_list, + 'interfaces_list': interfaces_list, + 'server': helpers.get_odoo_server_url(), + 'root_logger_log_level': self._get_logger_effective_level_str(logging.getLogger()), + 'odoo_current_log_level': self._get_logger_effective_level_str(logging.getLogger('odoo')), + 'recommended_log_level': 'warning', + 'available_log_levels': AVAILABLE_LOG_LEVELS, + 'drivers_logger_info': self._get_iot_handlers_logger(drivers_list, 'drivers'), + 'interfaces_logger_info': self._get_iot_handlers_logger(interfaces_list, 'interfaces'), + }) + + @http.route('/load_iot_handlers', type='http', auth='none', website=True) + def load_iot_handlers(self): + helpers.download_iot_handlers(False) + helpers.odoo_restart(0) + return "" + + @http.route('/list_credential', type='http', auth='none', website=True) + def list_credential(self): + return list_credential_template.render({ + 'title': "Odoo's IoT Box - List credential", + 'breadcrumb': 'List credential', + 'db_uuid': helpers.read_file_first_line('odoo-db-uuid.conf'), + 'enterprise_code': helpers.read_file_first_line('odoo-enterprise-code.conf'), + }) + + @http.route('/save_credential', type='http', auth='none', cors='*', csrf=False) + def save_credential(self, db_uuid, enterprise_code): + helpers.write_file('odoo-db-uuid.conf', db_uuid) + helpers.write_file('odoo-enterprise-code.conf', enterprise_code) + helpers.odoo_restart(0) + return "" + + @http.route('/clear_credential', type='http', auth='none', cors='*', csrf=False) + def clear_credential(self): + helpers.unlink_file('odoo-db-uuid.conf') + helpers.unlink_file('odoo-enterprise-code.conf') + helpers.odoo_restart(0) + return "" + + @http.route('/wifi', type='http', auth='none', website=True) + def wifi(self): + return wifi_config_template.render({ + 'title': 'Wifi configuration', + 'breadcrumb': 'Configure Wifi', + 'loading_message': 'Connecting to Wifi', + 'ssid': helpers.get_wifi_essid(), + }) + + @http.route('/wifi_connect', type='http', auth='none', cors='*', csrf=False) + def connect_to_wifi(self, essid, password, persistent=False): + if persistent: + persistent = "1" + else: + persistent = "" + + subprocess.check_call([file_path('point_of_sale/tools/posbox/configuration/connect_to_wifi.sh'), essid, password, persistent]) + server = helpers.get_odoo_server_url() + res_payload = { + 'message': 'Connecting to ' + essid, + } + if server: + res_payload['server'] = { + 'url': server, + 'message': 'Redirect to Odoo Server' + } + else: + res_payload['server'] = { + 'url': 'http://' + helpers.get_ip() + ':8069', + 'message': 'Redirect to IoT Box' + } + + return json.dumps(res_payload) + + @http.route('/wifi_clear', type='http', auth='none', cors='*', csrf=False) + def clear_wifi_configuration(self): + helpers.unlink_file('wifi_network.txt') + return "" + + @http.route('/server_clear', type='http', auth='none', cors='*', csrf=False) + def clear_server_configuration(self): + helpers.unlink_file('odoo-remote-server.conf') + return "" + + @http.route('/handlers_clear', type='http', auth='none', cors='*', csrf=False) + def clear_handlers_list(self): + for directory in ['drivers', 'interfaces']: + for file in list(Path(file_path(f'hw_drivers/iot_handlers/{directory}')).glob('*')): + if file.name != '__pycache__': + helpers.unlink_file(str(file.relative_to(*file.parts[:3]))) + return "" + + @http.route('/server_connect', type='http', auth='none', cors='*', csrf=False) + def connect_to_server(self, token, iotname): + if token: + credential = token.split('|') + url = credential[0] + token = credential[1] + db_uuid = credential[2] + enterprise_code = credential[3] + helpers.save_conf_server(url, token, db_uuid, enterprise_code) + else: + url = helpers.get_odoo_server_url() + token = helpers.get_token() + if iotname and platform.system() == 'Linux': + subprocess.check_call([file_path('point_of_sale/tools/posbox/configuration/rename_iot.sh'), iotname]) + helpers.odoo_restart(5) + return 'http://' + helpers.get_ip() + ':8069' + + @http.route('/steps', type='http', auth='none', cors='*', csrf=False) + def step_by_step_configure_page(self): + return configure_wizard_template.render({ + 'title': 'Configure IoT Box', + 'breadcrumb': 'Configure IoT Box', + 'loading_message': 'Configuring your IoT Box', + 'ssid': helpers.get_wifi_essid(), + 'server': helpers.get_odoo_server_url() or '', + 'hostname': subprocess.check_output('hostname').decode('utf-8').strip('\n'), + }) + + @http.route('/step_configure', type='http', auth='none', cors='*', csrf=False) + def step_by_step_configure(self, token, iotname, essid, password, persistent=False): + if token: + url = token.split('|')[0] + token = token.split('|')[1] + else: + url = '' + subprocess.check_call([file_path('point_of_sale/tools/posbox/configuration/connect_to_server_wifi.sh'), url, iotname, token, essid, password, persistent]) + return url + + # Set server address + @http.route('/server', type='http', auth='none', website=True) + def server(self): + return server_config_template.render({ + 'title': 'IoT -> Odoo server configuration', + 'breadcrumb': 'Configure Odoo Server', + 'hostname': subprocess.check_output('hostname').decode('utf-8').strip('\n'), + 'server_status': helpers.get_odoo_server_url() or 'Not configured yet', + 'loading_message': 'Configure Domain Server' + }) + + # Get password + @http.route('/hw_posbox_homepage/password', type='json', auth='none', methods=['POST']) + def view_password(self): + return helpers.generate_password() + + @http.route('/remote_connect', type='http', auth='none', cors='*') + def remote_connect(self): + """ + Establish a link with a customer box trough internet with a ssh tunnel + 1 - take a new auth_token on https://dashboard.ngrok.com/ + 2 - copy past this auth_token on the IoT Box : http://IoT_Box:8069/remote_connect + 3 - check on ngrok the port and url to get access to the box + 4 - you can connect to the box with this command : ssh -p port -v pi@url + """ + return remote_connect_template.render({ + 'title': 'Remote debugging', + 'breadcrumb': 'Remote Debugging', + }) + + @http.route('/enable_ngrok', type='http', auth='none', cors='*', csrf=False) + def enable_ngrok(self, auth_token): + if subprocess.call(['pgrep', 'ngrok']) == 1: + subprocess.Popen(['ngrok', 'tcp', '--authtoken', auth_token, '--log', '/tmp/ngrok.log', '22']) + return 'starting with ' + auth_token + else: + return 'already running' + + @http.route('/six_payment_terminal', type='http', auth='none', cors='*', csrf=False) + def six_payment_terminal(self): + return six_payment_terminal_template.render({ + 'title': 'Six Payment Terminal', + 'breadcrumb': 'Six Payment Terminal', + 'terminalId': self.get_six_terminal(), + }) + + @http.route('/six_payment_terminal_add', type='http', auth='none', cors='*', csrf=False) + def add_six_payment_terminal(self, terminal_id): + if terminal_id.isdigit(): + helpers.write_file('odoo-six-payment-terminal.conf', terminal_id) + service.server.restart() + else: + _logger.warning('Ignoring invalid Six TID: "%s". Only digits are allowed', terminal_id) + self.clear_six_payment_terminal() + return 'http://' + helpers.get_ip() + ':8069' + + @http.route('/six_payment_terminal_clear', type='http', auth='none', cors='*', csrf=False) + def clear_six_payment_terminal(self): + helpers.unlink_file('odoo-six-payment-terminal.conf') + service.server.restart() + return "" + + @http.route('/hw_proxy/upgrade', type='http', auth='none', ) + def upgrade(self): + commit = subprocess.check_output(["git", "--work-tree=/home/pi/odoo/", "--git-dir=/home/pi/odoo/.git", "log", "-1"]).decode('utf-8').replace("\n", "
") + flashToVersion = helpers.check_image() + actualVersion = helpers.get_version() + if flashToVersion: + flashToVersion = '%s.%s' % (flashToVersion.get('major', ''), flashToVersion.get('minor', '')) + return upgrade_page_template.render({ + 'title': "Odoo's IoTBox - Software Upgrade", + 'breadcrumb': 'IoT Box Software Upgrade', + 'loading_message': 'Updating IoT box', + 'commit': commit, + 'flashToVersion': flashToVersion, + 'actualVersion': actualVersion, + }) + + @http.route('/hw_proxy/perform_upgrade', type='http', auth='none') + def perform_upgrade(self): + self.updating.acquire() + os.system('/home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/posbox_update.sh') + self.updating.release() + return 'SUCCESS' + + @http.route('/hw_proxy/get_version', type='http', auth='none') + def check_version(self): + return helpers.get_version() + + @http.route('/hw_proxy/perform_flashing_create_partition', type='http', auth='none') + def perform_flashing_create_partition(self): + try: + response = subprocess.check_output(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; create_partition']).decode().split('\n')[-2] + if response in ['Error_Card_Size', 'Error_Upgrade_Already_Started']: + raise Exception(response) + return Response('success', status=200) + except subprocess.CalledProcessError as e: + raise Exception(e.output) + except Exception as e: + _logger.error('A error encountered : %s ' % e) + return Response(str(e), status=500) + + @http.route('/hw_proxy/perform_flashing_download_raspios', type='http', auth='none') + def perform_flashing_download_raspios(self): + try: + response = subprocess.check_output(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; download_raspios']).decode().split('\n')[-2] + if response == 'Error_Raspios_Download': + raise Exception(response) + return Response('success', status=200) + except subprocess.CalledProcessError as e: + raise Exception(e.output) + except Exception as e: + self.clean_partition() + _logger.error('A error encountered : %s ' % e) + return Response(str(e), status=500) + + @http.route('/hw_proxy/perform_flashing_copy_raspios', type='http', auth='none') + def perform_flashing_copy_raspios(self): + try: + response = subprocess.check_output(['sudo', 'bash', '-c', '. /home/pi/odoo/addons/point_of_sale/tools/posbox/configuration/upgrade.sh; copy_raspios']).decode().split('\n')[-2] + if response == 'Error_Iotbox_Download': + raise Exception(response) + return Response('success', status=200) + except subprocess.CalledProcessError as e: + raise Exception(e.output) + except Exception as e: + self.clean_partition() + _logger.error('A error encountered : %s ' % e) + return Response(str(e), status=500) + + @http.route('/iot_restart_odoo_or_reboot', type='json', auth='none', cors='*', csrf=False) + def iot_restart_odoo_or_reboot(self, action): + """ Reboots the IoT Box / restarts Odoo on it depending on chosen 'action' argument""" + try: + if action == 'restart_odoo': + helpers.odoo_restart(3) + else: + subprocess.call(['sudo', 'reboot']) + return 'success' + except Exception as e: + _logger.error('An error encountered : %s ', e) + return str(e) + + def _get_logger_effective_level_str(self, logger): + return logging.getLevelName(logger.getEffectiveLevel()).lower() + + def _get_iot_handler_logger(self, handler_name, handler_folder_name): + """ + Get Odoo Iot logger given an IoT handler name + :param handler_name: name of the IoT handler + :param handler_folder_name: IoT handler folder name (interfaces or drivers) + :return: logger if any, False otherwise + """ + odoo_addon_handler_path = helpers.compute_iot_handlers_addon_name(handler_folder_name, handler_name) + return odoo_addon_handler_path in logging.Logger.manager.loggerDict.keys() and \ + logging.getLogger(odoo_addon_handler_path) + + def _update_logger_level(self, logger_name, new_level, available_log_levels, handler_folder=False): + """ + Update (if necessary) Odoo's configuration and logger to the given logger_name to the given level. + The responsibility of saving the config file is not managed here. + :param logger_name: name of the logging logger to change level + :param new_level: new log level to set for this logger + :param available_log_levels: iterable of logs levels allowed (for initial check) + :param handler_folder: optional string of the IoT handler folder name ('interfaces' or 'drivers') + :return: wherever some changes were performed or not on the config + """ + if new_level not in available_log_levels: + _logger.warning('Unknown level to set on logger %s: %s', logger_name, new_level) + return False + + if handler_folder: + logger = self._get_iot_handler_logger(logger_name, handler_folder) + if not logger: + _logger.warning('Unable to change log level for logger %s as logger missing', logger_name) + return False + logger_name = logger.name + + ODOO_TOOL_CONFIG_HANDLER_NAME = 'log_handler' + LOG_HANDLERS = tools.config[ODOO_TOOL_CONFIG_HANDLER_NAME] + LOGGER_PREFIX = logger_name + ':' + IS_NEW_LEVEL_PARENT = new_level == 'parent' + + if not IS_NEW_LEVEL_PARENT: + intended_to_find = LOGGER_PREFIX + new_level.upper() + if intended_to_find in LOG_HANDLERS: + # There is nothing to do, the entry is already inside + return False + + # We remove every occurrence for the given logger + log_handlers_without_logger = [ + log_handler for log_handler in LOG_HANDLERS if not log_handler.startswith(LOGGER_PREFIX) + ] + + if IS_NEW_LEVEL_PARENT: + # We must check that there is no existing entries using this logger (whatever the level) + if len(log_handlers_without_logger) == len(LOG_HANDLERS): + return False + + # We add if necessary new logger entry + # If it is "parent" it means we want it to inherit from the parent logger. + # In order to do this we have to make sure that no entries for the logger exists in the + # `log_handler` (which is the case at this point as long as we don't re-add an entry) + tools.config[ODOO_TOOL_CONFIG_HANDLER_NAME] = log_handlers_without_logger + new_level_upper_case = new_level.upper() + if not IS_NEW_LEVEL_PARENT: + new_entry = [LOGGER_PREFIX + new_level_upper_case] + tools.config[ODOO_TOOL_CONFIG_HANDLER_NAME] += new_entry + _logger.debug('Adding to odoo config log_handler: %s', new_entry) + + # Update the logger dynamically + real_new_level = logging.NOTSET if IS_NEW_LEVEL_PARENT else new_level_upper_case + _logger.debug('Change logger %s level to %s', logger_name, real_new_level) + logging.getLogger(logger_name).setLevel(real_new_level) + return True + + def _get_iot_handlers_logger(self, handlers_name, iot_handler_folder_name): + """ + :param handlers_name: List of IoT handler string to search the loggers of + :param iot_handler_folder_name: name of the handler folder ('interfaces' or 'drivers') + :return: + { + : { + 'level': , + 'is_using_parent_level': , + 'parent_name': , + }, + ... + } + """ + handlers_loggers_level = dict() + for handler_name in handlers_name: + handler_logger = self._get_iot_handler_logger(handler_name, iot_handler_folder_name) + if not handler_logger: + # Might happen if the file didn't define a logger (or not init yet) + handlers_loggers_level[handler_name] = False + _logger.debug('Unable to find logger for handler %s', handler_name) + continue + logger_parent = handler_logger.parent + handlers_loggers_level[handler_name] = { + 'level': self._get_logger_effective_level_str(handler_logger), + 'is_using_parent_level': handler_logger.level == logging.NOTSET, + 'parent_name': logger_parent.name, + 'parent_level': self._get_logger_effective_level_str(logger_parent), + } + return handlers_loggers_level diff --git a/views/configure_wizard.html b/views/configure_wizard.html new file mode 100644 index 0000000..d492a50 --- /dev/null +++ b/views/configure_wizard.html @@ -0,0 +1,180 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + + +{% endblock %} +{% block content %} +

Configure IoT Box

+
    +
  • Connect to Odoo
  • +
  • Connect to Internet
  • +
  • Done
  • +
+
+
+
+ + + + + + + + + +
IoT Box Name
Server token
+
+ Server token is not mandatory for the community version. +
+ +
+
+ + + + + + + + + + +
Wifi Network + +
Password
+ +
+
+

✔ Nice! Your configuration is done.

+

+

+
+ {{ loading_block_ui(loading_message) }} +
+{% endblock %} diff --git a/views/handler_list.html b/views/handler_list.html new file mode 100644 index 0000000..7ebdaae --- /dev/null +++ b/views/handler_list.html @@ -0,0 +1,126 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + +{% endblock %} +{% block content %} +

Logging

+
+
+ + + + + +
+ +

Interfaces list

+ + + + + + {% for interface in interfaces_list -%} + + + + + {%- endfor %} +
NameLog Level
{{ interface }} + {% set interface_logger_info = interfaces_logger_info[interface] %} + {% if interface_logger_info != False %} + + {% else %} + Logger uninitialised + {% endif %} +
+ +

Drivers list

+ + + + + + {% for driver in drivers_list -%} + + + + + {%- endfor %} +
NameLog Level
{{ driver }} + {% set driver_logger_info = drivers_logger_info[driver] %} + {% if driver_logger_info != False %} + + {% else %} + Logger uninitialised + {% endif %} +
+ +
+ {% if server %} + Load handlers + {% endif %} + +
+
+ {% if server %} +
+ You can clear the handlers configuration +
+ +
+
+ {% endif %} + {{ loading_block_ui('Loading Handlers') }} +{% endblock %} diff --git a/views/homepage.html b/views/homepage.html new file mode 100644 index 0000000..504109e --- /dev/null +++ b/views/homepage.html @@ -0,0 +1,263 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + + +{% endblock %} +{% block content %} +
+
+
Restart
+
+
+ {% if system == "Linux" %} + + {% endif %} + +
+
+
+
+

Your IoT Box is up and running

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + {% if server_status != "Not Configured" %} + + + + + {% endif %} + {% if pairing_code %} + + + + + {% endif %} + + + + +
Name {{ hostname }} {% if system == "Linux" %}configure{% endif %}
Version {{ version }} {% if system == "Linux" %}update{% endif %}
IP Address{{ ip }}
Mac Address {{ mac }}
Network{{ network_status }} {% if system == "Linux" %}configure wifi{% endif %}
Server{{ server_status }}configure
HTTPS certificate + {% if is_certificate_ok %} +
+ OK + {{ certificate_details }} +
+ {% else %} + Error code: + {% set error_code = certificate_details.split(' ') | first | replace("_", "-") | lower %} + {% set doc_url = 'https://www.odoo.com/documentation/17.0/applications/productivity/iot/config/https_certificate_iot.html#' ~ error_code %} + help +
+ {{ certificate_details }} + {% endif %} +
Six payment terminal{{ six_terminal }} configure
Pairing code{{ pairing_code }}
IOT Device +
+ {% if iot_device_status|length == 0 %} + No Device Found + {% endif %} + {% for iot_devices in iot_device_status|groupby('type') %} +
+
{{ iot_devices.grouper|capitalize }}s
+
+ {% for device in iot_devices.list %} +
+ {{ device['name'] }} +
{{ device['identifier'] }}
+
+ {% endfor %} +
+
+ {% endfor %} +
+
handlers list
+
+
+ POS Display + {% if system == "Linux" %} + Remote Debug + Printers server + {% endif %} + {% if server_status != "Not Configured" %} + Credential + {% endif %} +
+ {{ loading_block_ui(loading_message) }} +{% endblock %} diff --git a/views/layout.html b/views/layout.html new file mode 100644 index 0000000..b64a812 --- /dev/null +++ b/views/layout.html @@ -0,0 +1,143 @@ + + + + + + {{ title or "Odoo's IoT Box" }} + + + {% block head %}{% endblock %} + + + {%if breadcrumb %} + + {% endif %} +
+ {% block content %}{% endblock %} +

+

+ + + diff --git a/views/list_credential.html b/views/list_credential.html new file mode 100644 index 0000000..87a8f09 --- /dev/null +++ b/views/list_credential.html @@ -0,0 +1,68 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + +{% endblock %} +{% block content %} +

List Credential

+

+ Set the DB UUID and your Contract Number you want to use. +

+
+ + + + + + + + + + + + +
DB uuid
Contract Number
+
+ {{ loading_block_ui(loading_message) }} +
+ {% if db_uuid or enterprise_code %} +

+ Current DB uuid: {{ db_uuid }} +

+

+ Current Contract Number: {{ enterprise_code }} +

+
+ You can clear the credential configuration +
+ +
+
+ {% endif %} +{% endblock %} diff --git a/views/loading.html b/views/loading.html new file mode 100644 index 0000000..7d65146 --- /dev/null +++ b/views/loading.html @@ -0,0 +1,14 @@ +{% macro loading_block_ui(message) %} +
+
+
+ Loading... +
+
+
+ Please wait..
+ {{ message }} +
+
+
+{% endmacro %} diff --git a/views/remote_connect.html b/views/remote_connect.html new file mode 100644 index 0000000..1967918 --- /dev/null +++ b/views/remote_connect.html @@ -0,0 +1,56 @@ +{% extends "layout.html" %} +{% block head %} + +{% endblock %} +{% block content %} +

Remote Debugging

+

+ This allows someone who give a ngrok authtoken to gain remote access to your IoT Box, and + thus your entire local network. Only enable this for someone + you trust. +

+ +{% endblock %} diff --git a/views/server_config.html b/views/server_config.html new file mode 100644 index 0000000..6824ee6 --- /dev/null +++ b/views/server_config.html @@ -0,0 +1,60 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + +{% endblock %} +{% block content %} +

Configure Odoo Server

+

+ Paste the token from the Connect wizard in your Odoo instance in the Server Token field. If you change the IoT Box Name, + your IoT Box will need a reboot. +

+
+ + + + + + + + + + + + +
IoT Box Name
Server Token
+
+

+ Your current server {{ server_status }} +

+ {{ loading_block_ui(loading_message) }} +
+
+ You can clear the server configuration +
+ +
+
+{% endblock %} diff --git a/views/six_payment_terminal.html b/views/six_payment_terminal.html new file mode 100644 index 0000000..89a2e69 --- /dev/null +++ b/views/six_payment_terminal.html @@ -0,0 +1,61 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + +{% endblock %} +{% block content %} +

Six Payment Terminal

+

+ Set the Terminal ID (TID) of the terminal you want to use. +

+
+ + + + + + + + +
Terminal ID (digits only)
+
+ {{ loading_block_ui(loading_message) }} +
+ {% if terminalId %} +

+ Current Terminal Id: {{ terminalId }} +

+
+ You can clear the terminal configuration +
+ +
+
+ {% endif %} +{% endblock %} diff --git a/views/upgrade_page.html b/views/upgrade_page.html new file mode 100644 index 0000000..0c5d7a9 --- /dev/null +++ b/views/upgrade_page.html @@ -0,0 +1,95 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + + + +{% endblock %} +{% block content %} +

IoT Box Software Upgrade

+

+ This tool will help you perform an upgrade of the IoTBox's software over the internet. + However the preferred method to upgrade the IoTBox is to flash the sd-card with + the latest image. The upgrade + procedure is explained into to the + IoTBox manual +

+

+ To upgrade the IoTBox, click on the upgrade button. The upgrade will take a few minutes. Do not reboot the IoTBox during the upgrade. +

+
+
+ Latest patch: +
+
{{ commit|safe }}
+
+
+ {% if flashToVersion %} + Upgrade to {{ flashToVersion }} + {% else %} + Upgrade + {% endif %} +
+ {{ loading_block_ui(loading_message) }} +{% endblock %} diff --git a/views/wifi_config.html b/views/wifi_config.html new file mode 100644 index 0000000..94e0579 --- /dev/null +++ b/views/wifi_config.html @@ -0,0 +1,71 @@ +{% extends "layout.html" %} +{% from "loading.html" import loading_block_ui %} +{% block head %} + +{% endblock %} +{% block content %} +

Configure Wifi

+

+ Here you can configure how the iotbox should connect to wireless networks. + Currently only Open and WPA networks are supported. When enabling the persistent checkbox, + the chosen network will be saved and the iotbox will attempt to connect to it every time it boots. +

+
+ + + + + + + + + + + + + + + + +
ESSID + +
Password
Persistent
+
+
+
+ You can clear the persistent configuration +
+ +
+
+ {{ loading_block_ui(loading_message) }} +{% endblock %}