def get_form(self): system_pw_form = fapi.ForisForm("system_password", self.data) system_pw_main = system_pw_form.add_section( name="set_password", title=_(self.userfriendly_title), description=_("In order to access the advanced configuration possibilities which are " "not present here, you must set the root user's password. The advanced " "configuration options can be managed either through the " "<a href=\"//%(host)s/%(path)s\">LuCI web interface</a> " "or over SSH.") % {'host': bottle.request.get_header('host'), 'path': 'cgi-bin/luci'} ) system_pw_main.add_field(Password, name="password", label=_("Password"), required=True, validators=validators.LenRange(6, 128)) system_pw_main.add_field(Password, name="password_validation", label=_("Password (repeat)"), required=True, validators=validators.EqualTo("password", "password_validation", _("Passwords are not equal."))) def system_pw_form_cb(data): client.set_password("root", data["password"]) return "none", None system_pw_form.add_callback(system_pw_form_cb) return system_pw_form
def save(self, *args, **kwargs): result = False try: result = super(MaintenanceConfigPage, self).save(no_messages=True, *args, **kwargs) new_ip = self.form.callback_results.get('new_ip') if new_ip: # rebuild current URL with new IP old_urlparts = bottle.request.urlparts new_url = urlunsplit((old_urlparts.scheme, new_ip, old_urlparts.path, "", "")) messages.success(_("Configuration was successfully restored. After installing " "updates and rebooting you can return to this page at " "<a href=\"%(new_url)s\">%(new_url)s</a> in local " "network. Please wait a while until router automatically " "restarts.") % dict(new_url=new_url)) elif result: messages.success(_("Configuration was successfully restored. Please wait a while " "for installation of updates and automatic restart of the " "device.")) messages.warning(_("IP address of the router could not be determined from the backup.")) else: messages.warning(_("There were some errors in your input.")) except ConfigRestoreError: messages.error(_("Configuration could not be loaded, backup file is probably corrupted.")) logger.exception("Error when restoring backup.") return result
def get_form(self): system_pw_form = fapi.ForisForm("system_password", self.data) system_pw_main = system_pw_form.add_section( name="set_password", title=_(self.userfriendly_title), description= _("In order to access the advanced configuration possibilities which are " "not present here, you must set the root user's password. The advanced " "configuration options can be managed either through the " "<a href=\"//%(host)s/%(path)s\">LuCI web interface</a> " "or over SSH.") % { 'host': bottle.request.get_header('host'), 'path': 'cgi-bin/luci' }) system_pw_main.add_field(Password, name="password", label=_("Password"), required=True, validators=validators.LenRange(6, 128)) system_pw_main.add_field(Password, name="password_validation", label=_("Password (repeat)"), required=True, validators=validators.EqualTo( "password", "password_validation", _("Passwords are not equal."))) def system_pw_form_cb(data): client.set_password("root", data["password"]) return "none", None system_pw_form.add_callback(system_pw_form_cb) return system_pw_form
def get_form(self): form = ForisForm( "tor_plugin_check", self.data, filter=None #create_config_filter("tor_plugin") ) section = form.add_section(name="parameters", title=_("Tor configuration")) section.add_field( Checkbox, name="default_tor_route", label=_("All traffic through tor"), hint=_("After enabling this option all traffic from your client " "will be routed through the tor network."), ) def update_uci_callback(data): enable_route = data["default_tor_route"] print form.nuci_config().find_child( "uci.tor.omnia_specific.enable_routing").value if enable_route: print "Selected" else: print "Unselected" syslog.syslog("update_uci_callback") return "none", None def update_program_callback(data): syslog.syslog("foris tor plugin update_program_callback") return "none", None form.add_callback(update_uci_callback) #form.add_callback(update_program_callback) return form
def get_form(self): # form definitions pw_form = fapi.ForisForm("password", self.data) pw_main = pw_form.add_section(name="set_password", title=_(self.userfriendly_title), description=_("Set your password for this administration " "interface. The password must be at least 6 " "characters long.")) if self.change: pw_main.add_field(Password, name="old_password", label=_("Current password")) label_pass1 = _("New password") label_pass2 = _("New password (repeat)") else: label_pass1 = _("Password") label_pass2 = _("Password (repeat)") pw_main.add_field(Password, name="password", label=label_pass1, required=True, validators=validators.LenRange(6, 128)) pw_main.add_field(Password, name="password_validation", label=label_pass2, required=True, validators=validators.EqualTo("password", "password_validation", _("Passwords are not equal."))) pw_main.add_field(Checkbox, name="set_system_pw", label=_("Use the same password for advanced configuration"), hint=_("Same password would be used for accessing this administration " "interface, for root user in LuCI web interface and for SSH " "login. Use a strong password! (If you choose not to set the " "password for advanced configuration here, you will have the " "option to do so later. Until then, the root account will be " "blocked.)")) def pw_form_cb(data): from beaker.crypto import pbkdf2 if self.change: # if changing password, check the old pw is right first uci_data = client.get(filter=filters.foris_config) password_hash = uci_data.find_child("uci.foris.auth.password") # allow changing the password if password_hash is empty if password_hash: password_hash = password_hash.value # crypt automatically extracts salt and iterations from formatted pw hash if password_hash != pbkdf2.crypt(data['old_password'], salt=password_hash): return "save_result", {'wrong_old_password': True} uci = Uci() foris = Config("foris") uci.add(foris) auth = Section("auth", "config") foris.add(auth) # use 48bit pseudo-random salt internally generated by pbkdf2 new_password_hash = pbkdf2.crypt(data['password'], iterations=1000) auth.add(Option("password", new_password_hash)) if data['set_system_pw'] is True: client.set_password("root", data['password']) return "edit_config", uci pw_form.add_callback(pw_form_cb) return pw_form
def save(self, *args, **kwargs): result = super(UpdaterConfigPage, self).save(no_messages=True, *args, **kwargs) if result: messages.success(_("Configuration was successfully saved. Selected " "packages should be installed or removed shortly.")) else: messages.warning(_("There were some errors in your input.")) return result
def save(self, *args, **kwargs): no_messages = kwargs.pop("no_messages", False) result = super(ConfigPageMixin, self).save(*args, **kwargs) if no_messages: return result if result: messages.success(_("Configuration was successfully saved.")) else: messages.warning(_("There were some errors in your input.")) return result
def save(self, *args, **kwargs): result = super(PasswordConfigPage, self).save(no_messages=True, *args, **kwargs) wrong_old_password = self.form.callback_results.get('wrong_old_password', False) if wrong_old_password: messages.warning(_("Old password you entered was not valid.")) elif result: messages.success(_("Password was successfully saved.")) else: messages.warning(_("There were some errors in your input.")) return result
def _action_save_notifications(self): if bottle.request.method != 'POST': messages.error("Wrong HTTP method.") bottle.redirect(reverse("config_page", page_name="maintenance")) handler = NotificationsHandler(request.POST) if handler.save(): messages.success(_("Configuration was successfully saved.")) bottle.redirect(reverse("config_page", page_name="maintenance")) messages.warning(_("There were some errors in your input.")) return super(MaintenanceConfigPage, self).render(notifications_form=handler.form, config_pages=config_page_map)
def get_form(self): maintenance_form = fapi.ForisForm("maintenance", self.data) maintenance_main = maintenance_form.add_section(name="restore_backup", title=_(self.userfriendly_title)) maintenance_main.add_field(File, name="backup_file", label=_("Backup file"), required=True) def maintenance_form_cb(data): result = client.load_config_backup(data['backup_file'].file) return "save_result", {'new_ip': result} maintenance_form.add_callback(maintenance_form_cb) return maintenance_form
def _action_test_notifications(self): if bottle.request.method != 'POST': messages.error("Wrong HTTP method.") bottle.redirect(reverse("config_page", page_name="maintenance")) result, error_message = client.test_notifications() if result: messages.success(_("Testing message was sent, please check your inbox.")) else: if error_message: messages.error(_("Sending of the testing message failed, your configuration is possibly " "wrong.<br>Error returned:<br><pre>%(error)s</pre>") % dict(error=error_message)) else: messages.error(_("Sending of the testing message failed because of an internal error.")) bottle.redirect(reverse("config_page", page_name="maintenance"))
def get_form(self): maintenance_form = fapi.ForisForm("maintenance", self.data) maintenance_main = maintenance_form.add_section( name="restore_backup", title=_(self.userfriendly_title)) maintenance_main.add_field(File, name="backup_file", label=_("Backup file"), required=True) def maintenance_form_cb(data): result = client.load_config_backup(data['backup_file'].file) return "save_result", {'new_ip': result} maintenance_form.add_callback(maintenance_form_cb) return maintenance_form
def __init__(self, low, high): self._low = low self._high = high super(InRange, self).__init__( _("Not in a valid range %(low)s - %(high)s.") % dict(low=low, high=high)) self.js_validator_params = "[%s,%s]" % (low, high)
def __init__(self, low, high): self._low = low self._high = high super(ByteLenRange, self).__init__( _("Length must be from %(low)s to %(high)s characters.") % dict(low=low, high=high)) self.js_validator_params = "[%s,%s]" % (low, high)
def __init__(self): pattern = r"^([01][0-9]|2[0-3]):([0-5][0-9])$" super(Time, self).__init__(_("This is not valid time in HH:MM format."), pattern) self.js_validator = ("pattern", pattern) self.extra_data['parsley-error-message'] = self.msg
def step_post(number=1): Wizard = get_wizard(number) wiz = Wizard(request.POST) if request.is_xhr: # only update is allowed via XHR return wiz.render(is_xhr=True) try: if wiz.save(): bottle.redirect(reverse("wizard_step", number=int(number) + 1)) except TypeError: # raised by Validator - could happen when the form is posted with wrong fields messages.error(_("Configuration could not be saved due to an internal error.")) logger.exception("Error when saving form.") messages.warning(_("There were some errors in your input.")) logger.warning("Form not saved.") return wiz.render(stepnumber=number)
def translate_sending_status(status): verbose = _(AboutConfigPage.SENDING_STATUS_TRANSLATION.get( status, AboutConfigPage.SENDING_STATUS_TRANSLATION['unknown'] )) if status not in AboutConfigPage.SENDING_STATUS_TRANSLATION: verbose += " (%s)" % status return verbose
def get_form(self): form = fapi.ForisForm("registration_check", self.data, filter=create_config_filter("foris")) main_section = form.add_section(name="check_email", title=_(self.userfriendly_title)) main_section.add_field(Email, name="email", label=_("Email")) def form_cb(data): result = client.get_registration_status(data.get("email"), bottle.request.app.lang) return "save_result", { 'success': result[0], 'response': result[1], } form.add_callback(form_cb) return form
def get_form(self): dns_form = fapi.ForisForm("dns", self.data, filter=create_config_filter("unbound")) dns_main = dns_form.add_section(name="set_dns", title=_(self.userfriendly_title)) dns_main.add_field(Checkbox, name="forward_upstream", label=_("Use forwarding"), nuci_path="uci.unbound.server.forward_upstream", nuci_preproc=lambda val: bool(int(val.value)), default=True) def dns_form_cb(data): uci = Uci() unbound = Config("unbound") uci.add(unbound) server = Section("server", "unbound") unbound.add(server) server.add(Option("forward_upstream", data['forward_upstream'])) return "edit_config", uci dns_form.add_callback(dns_form_cb) return dns_form
def get_form(self): time_form = fapi.ForisForm("time", self.data, filter=filters.time) time_main = time_form.add_section( name="set_time", title=_(self.userfriendly_title), description=_("We could not synchronize the time with a timeserver, probably due to a " "loss of connection. It is necessary for the router to have correct time " "in order to function properly. Please, synchronize it with your " "computer's time, or set it manually.") ) time_main.add_field(Textbox, name="time", label=_("Time"), nuci_path="time", nuci_preproc=lambda v: v.local) def time_form_cb(data): client.set_time(data['time']) return "none", None time_form.add_callback(time_form_cb) return time_form
def _get_channels(wifi_card): channels_2g4 = [("auto", _("auto"))] channels_5g = [] for channel in wifi_card['channels']: if channel['disabled']: continue pretty_channel = "%s (%s MHz%s)" % (channel['number'], channel['frequency'], ", DFS" if channel['radar'] else "") if channel['frequency'] < 2500: channels_2g4.append((str(channel['number']), pretty_channel)) else: channels_5g.append((str(channel['number']), pretty_channel)) return channels_2g4, channels_5g
def get_form(self): time_form = fapi.ForisForm("time", self.data, filter=filters.time) time_main = time_form.add_section( name="set_time", title=_(self.userfriendly_title), description= _("We could not synchronize the time with a timeserver, probably due to a " "loss of connection. It is necessary for the router to have correct time " "in order to function properly. Please, synchronize it with your " "computer's time, or set it manually.")) time_main.add_field(Textbox, name="time", label=_("Time"), nuci_path="time", nuci_preproc=lambda v: v.local) def time_form_cb(data): client.set_time(data['time']) return "none", None time_form.add_callback(time_form_cb) return time_form
def get_messages(): try: return get(filter=filters.messages).find_child( "messages") or user_notify.Messages() except (RPCError, TimeoutExpiredError): from foris.core import ugettext as _ logger.exception("Unable to fetch messages") error_message = user_notify.Message( message_id=None, body=LocalizableTextValue( _("Unable to retrieve notifications, " "please try refreshing the page.")), severity="error", timestamp=int(time())) return user_notify.Messages(messages=[error_message])
def config_page_post(page_name): ConfigPage = get_config_page(page_name) config_page = ConfigPage(request.POST) if request.is_xhr: # only update is allowed return config_page.render(is_xhr=True) try: if config_page.save(): bottle.redirect(request.fullpath) except TypeError: # raised by Validator - could happen when the form is posted with wrong fields messages.error(_("Configuration could not be saved due to an internal error.")) logger.exception("Error when saving form.") logger.warning("Form not saved.") return config_page.render(config_pages=config_page_map, active_config_page_key=page_name)
def redirect_unauthenticated(redirect_url=None): redirect_url = redirect_url or reverse("index") no_auth = bottle.default_app().config.get("no_auth", False) if not no_auth and not is_user_authenticated(): from foris.core import ugettext as _ import messages messages.info(_("You have been logged out due to longer inactivity.")) if bottle.request.is_xhr: # "raise" JSON response if requested by XHR res = bottle.response.copy(cls=bottle.HTTPResponse) res.content_type = 'application/json' res.body = json.dumps(dict(success=False, loggedOut=True, loginUrl=redirect_url)) raise res # "raise" standard bottle redirect login_url = "%s?next=%s" % (redirect_url, bottle.request.fullpath) bottle.redirect(login_url)
def redirect_unauthenticated(redirect_url=None): redirect_url = redirect_url or reverse("index") no_auth = bottle.default_app().config.get("no_auth", False) if not no_auth and not is_user_authenticated(): from foris.core import ugettext as _ import messages messages.info(_("You have been logged out due to longer inactivity.")) if bottle.request.is_xhr: # "raise" JSON response if requested by XHR res = bottle.response.copy(cls=bottle.HTTPResponse) res.content_type = "application/json" res.body = json.dumps(dict(success=False, loggedOut=True, loginUrl=redirect_url)) raise res # "raise" standard bottle redirect login_url = "%s?next=%s" % (redirect_url, bottle.request.fullpath) bottle.redirect(login_url)
def config_action_post(page_name, action): ConfigPage = get_config_page(page_name) config_page = ConfigPage(request.POST) if request.is_xhr: # only update is allowed request.POST.pop("update", None) return config_page.render(is_xhr=True) # check if the button click wasn't any sub-action subaction = request.POST.pop("action", None) if subaction: return config_action_post(page_name, subaction) try: result = config_page.call_action(action) try: if not result: bottle.redirect(reverse("config_page", page_name=page_name)) except TypeError: # raised by Validator - could happen when the form is posted with wrong fields messages.error(_("Configuration could not be saved due to an internal error.")) logger.exception("Error when saving form.") logger.warning("Form not saved.") return result except ValueError: raise bottle.HTTPError(404, "Unknown action.")
def __init__(self): super(IPv4Prefix, self).__init__( _("This is not an IPv4 address with prefix length."))
def get_form(self): region_form = fapi.ForisForm( "region", self.data, filter=filters.create_config_filter("system")) timezone = region_form.add_section( name="timezone", title=_(self.userfriendly_title), description=_( "Please select the timezone the router is being operated in. " "Correct setting is required to display the right time and for related functions." )) lang = bottle.request.app.lang def construct_args(items, translation_function=_, key_getter=lambda x: x): """ Helper function that builds args for country/timezone dropdowns. If there's only one item, dropdown should contain only that item. Otherwise the list of items should be prepended by an empty value. :param items: list of filtered TZ data :param translation_function: function that returns displayed choice from TZ data :param key_getter: :return: list of args """ args = localized_sorted( ((key_getter(x), translation_function(x)) for x in items), lang=lang, key=lambda x: x[1]) if len(args) > 1: return [(None, "-" * 16)] + args return args timezone.add_field(Hidden, name="system_name", nuci_path="uci.system.@system[0]", nuci_preproc=lambda val: val.name) regions = localized_sorted(((x, _(x)) for x in tzinfo.regions), lang=lang, key=lambda x: x[1]) timezone.add_field(Dropdown, name="region", label=_("Continent or ocean"), required=True, args=regions, nuci_path="uci.system.@system[0].zonename", nuci_preproc=lambda x: x.value.split("/")[0]) # Get region and offer available countries region = region_form.current_data.get('region') countries = construct_args(tzinfo.countries_in_region(region), lambda x: _(tzinfo.countries[x])) timezone.add_field(Dropdown, name="country", label=_("Country"), required=True, default=None, args=countries, nuci_path="uci.system.@system[0].zonename", nuci_preproc=lambda x: tzinfo.get_country_for_tz(x.value))\ .requires("region") # Get country and offer available timezones country = region_form.current_data.get("country", countries[0][0]) # It's possible that data contain country from the previous request, # in that case fall back to the first item in list of available countries if country not in (x[0] for x in countries): country = countries[0][0] timezones = construct_args(tzinfo.timezones_in_region_and_country( region, country), translation_function=lambda x: _(x[2]), key_getter=lambda x: x[0]) # Offer timezones - but only if a country is selected and is not None (ensured by the # requires() method) timezone.add_field(Dropdown, name="zonename", label=_("Timezone"), required=True, default=None, args=timezones, nuci_path="uci.system.@system[0].zonename")\ .requires("country", lambda x: country and x is not None) def region_form_cb(data): uci = Uci() system = Config("system") uci.add(system) system_section = Section(data['system_name'], "system") system.add(system_section) zonename = data['zonename'] system_section.add(Option("zonename", zonename)) system_section.add( Option("timezone", tzinfo.get_zoneinfo_for_tz(zonename))) return "edit_config", uci region_form.add_callback(region_form_cb) return region_form
def get_form(self): notifications_form = fapi.ForisForm( "notifications", self.data, filter=create_config_filter("user_notify")) notifications = notifications_form.add_section( name="notifications", title=_("Notifications settings")) # notifications settings notifications.add_field(Checkbox, name="enable_smtp", label=_("Enable notifications"), nuci_path="uci.user_notify.smtp.enable", nuci_preproc=lambda val: bool(int(val.value)), default=False) notifications.add_field( Radio, name="use_turris_smtp", label=_("SMTP provider"), default="0", args=(("1", _("Turris")), ("0", _("Custom"))), nuci_path="uci.user_notify.smtp.use_turris_smtp", hint=_("If you set SMTP provider to \"Turris\", the servers provided to members of the " "Turris project would be used. These servers do not require any additional " "settings. If you want to set your own SMTP server, please select \"Custom\" " "and enter required settings."))\ .requires("enable_smtp", True) notifications.add_field( Textbox, name="to", label=_("Recipient's email"), nuci_path="uci.user_notify.smtp.to", nuci_preproc=lambda x: " ".join( map(lambda value: value.content, x.children)), hint= _("Email address of recipient. Separate multiple addresses by spaces." ), required=True).requires("enable_smtp", True) # sender's name for CZ.NIC SMTP only notifications.add_field( Textbox, name="sender_name", label=_("Sender's name"), hint=_("Name of the sender - will be used as a part of the " "sender's email address before the \"at\" sign."), nuci_path="uci.user_notify.smtp.sender_name", validators=[validators.RegExp(_("Sender's name can contain only " "alphanumeric characters, dots " "and underscores."), r"^[0-9a-zA-Z_\.-]+$")], required=True )\ .requires("enable_smtp", True)\ .requires("use_turris_smtp", "1") SEVERITY_OPTIONS = ( (1, _("Reboot is required")), (2, _("Reboot or attention is required")), (3, _("Reboot or attention is required or update was installed")), ) notifications.add_field(Dropdown, name="severity", label=_("Importance"), nuci_path="uci.user_notify.notifications.severity", nuci_preproc=lambda val: int(val.value), args=SEVERITY_OPTIONS, default=1)\ .requires("enable_smtp", True) notifications.add_field(Checkbox, name="news", label=_("Send news"), hint=_("Send emails about new features."), nuci_path="uci.user_notify.notifications.news", nuci_preproc=lambda val: bool(int(val.value)), default=True)\ .requires("enable_smtp", True) # SMTP settings (custom server) smtp = notifications_form.add_section(name="smtp", title=_("SMTP settings")) smtp.add_field(Email, name="from", label=_("Sender address (From)"), hint=_("This is the address notifications are sent from."), nuci_path="uci.user_notify.smtp.from", required=True)\ .requires("enable_smtp", True)\ .requires("use_turris_smtp", "0") smtp.add_field(Textbox, name="server", label=_("Server address"), nuci_path="uci.user_notify.smtp.server", required=True)\ .requires("enable_smtp", True)\ .requires("use_turris_smtp", "0") smtp.add_field(Number, name="port", label=_("Server port"), nuci_path="uci.user_notify.smtp.port", validators=[validators.PositiveInteger()], required=True) \ .requires("enable_smtp", True)\ .requires("use_turris_smtp", "0") SECURITY_OPTIONS = ( ("none", _("None")), ("ssl", _("SSL/TLS")), ("starttls", _("STARTTLS")), ) smtp.add_field(Dropdown, name="security", label=_("Security"), nuci_path="uci.user_notify.smtp.security", args=SECURITY_OPTIONS, default="none") \ .requires("enable_smtp", True).requires("use_turris_smtp", "0") smtp.add_field(Textbox, name="username", label=_("Username"), nuci_path="uci.user_notify.smtp.username")\ .requires("enable_smtp", True)\ .requires("use_turris_smtp", "0") smtp.add_field(Password, name="password", label=_("Password"), nuci_path="uci.user_notify.smtp.password")\ .requires("enable_smtp", True)\ .requires("use_turris_smtp", "0") # reboot time reboot = notifications_form.add_section( name="reboot", title=_("Automatic restarts after software update")) reboot.add_field( Number, name="delay", label=_("Delay (days)"), hint=_( "Number of days that must pass between receiving the request " "for restart and the automatic restart itself."), nuci_path="uci.user_notify.reboot.delay", validators=[ validators.PositiveInteger(), validators.InRange(0, 10) ], required=True) reboot.add_field( Time, name="reboot_time", label=_("Reboot time"), hint=_("Time of day of automatic reboot in HH:MM format."), nuci_path="uci.user_notify.reboot.time", validators=[validators.Time()], required=True) def notifications_form_cb(data): uci = Uci() user_notify = Config("user_notify") uci.add(user_notify) smtp = Section("smtp", "smtp") user_notify.add(smtp) smtp.add(Option("enable", data['enable_smtp'])) reboot = Section("reboot", "reboot") user_notify.add(reboot) reboot.add(Option("time", data['reboot_time'])) reboot.add(Option("delay", data['delay'])) if data['enable_smtp']: smtp.add(Option("use_turris_smtp", data['use_turris_smtp'])) if data['use_turris_smtp'] == "0": smtp.add(Option("server", data['server'])) smtp.add(Option("port", data['port'])) smtp.add(Option("username", data['username'])) smtp.add(Option("password", data['password'])) smtp.add(Option("security", data['security'])) smtp.add(Option("from", data['from'])) else: smtp.add(Option("sender_name", data['sender_name'])) to = List("to") for i, to_item in enumerate(data['to'].split(" ")): if to_item: to.add(Value(i, to_item)) smtp.add_replace(to) # notifications section notifications = Section("notifications", "notifications") user_notify.add(notifications) notifications.add(Option("severity", data['severity'])) notifications.add(Option("news", data['news'])) return "edit_config", uci notifications_form.add_callback(notifications_form_cb) return notifications_form
def __init__(self): super(IPv6Prefix, self).__init__(_("This is not an IPv6 address with prefix length."))
def index(): notifications = client.get_messages() return template("config/index", config_pages=config_page_map, title=_("Home page"), make_notification_title=make_notification_title, active_config_page_key='', notifications=notifications.new)
def default_template(self, **kwargs): if kwargs.get("stepnumber"): kwargs['title'] = _("Configuration wizard - step %s") % kwargs['stepnumber'] next_step_url = reverse("wizard_step", number=self.next_step_allowed) return template(self.template, can_skip_wizard=self.can_skip_wizard, stepname=self.name, next_step_url=next_step_url, **kwargs)
def get_form(self): form = fapi.ForisForm("enable_collection", self.data, filter=filters.create_config_filter( "foris", "updater")) section = form.add_section( name="collection_toggle", title=_(self.userfriendly_title), ) section.add_field(Checkbox, name="enable", label=_("Enable data collection"), nuci_path="uci.foris.eula.agreed_collect", nuci_preproc=lambda val: bool(int(val.value))) def form_cb(data): uci = build_option_uci_tree("foris.eula.agreed_collect", "config", data.get("enable")) return "edit_config", uci def adjust_lists_cb(data): uci = Uci() # All enabled lists enabled_lists = map( lambda x: x.content, form.nuci_config.find_child( "uci.updater.pkglists.lists").children) # Lists that do not need agreement enabled_no_agree = filter(lambda x: not x.startswith("i_agree_"), enabled_lists) # Lists that need agreement enabled_i_agree = filter(lambda x: x.startswith("i_agree_"), enabled_lists) # Always install lists that do not need agreement - create a copy of the list installed_lists = enabled_no_agree[:] logger.warning("no agree: %s", enabled_no_agree) logger.warning("installed: %s", installed_lists) if data.get("enable", False): # Include i_agree lists if user agreed with EULA installed_lists.extend(enabled_i_agree) # Add main data collection list if it's not present logger.warning(installed_lists) logger.warning("i_agree_datacollect" not in installed_lists) logger.warning("i_agree_datacollect" in installed_lists) if "i_agree_datacollect" not in installed_lists: logger.warning("appending") installed_lists.append("i_agree_datacollect") logger.warning("saving %s", installed_lists) # Reconstruct list of package lists updater = uci.add(Config("updater")) pkglists = updater.add(Section("pkglists", "pkglists")) lists = List("lists") for i, name in enumerate(installed_lists): lists.add(Value(i, name)) # If there's anything to add, replace the list, otherwise remove it completely if len(installed_lists) > 0: pkglists.add_replace(lists) else: pkglists.add_removal(lists) return "edit_config", uci def run_updater_cb(data): logger.info("Checking for updates.") client.check_updates() return "none", None form.add_callback(form_cb) form.add_callback(adjust_lists_cb) form.add_callback(run_updater_cb) return form
def __init__(self): super(MacAddress, self).__init__(_("MAC address is not valid.")) self.extra_data['parsley-validation-minlength'] = '17' self.reg_exp = re.compile(r"^([a-fA-F0-9]{2}:){5}[a-fA-F0-9]{2}$")
def __init__(self): super(Domain, self).__init__(_("This is not a valid domain name.")) self.extra_data['parsley-validation-maxlength'] = '63' self.reg_exp = re.compile(r"^(?![0-9]+$)(?!-)[a-zA-Z0-9-]{,63}(?<!-)$")
def __init__(self): super(PositiveInteger, self).__init__(_("Is not a number."))
def get_form(self): lan_form = fapi.ForisForm( "lan", self.data, filter=create_config_filter("dhcp", "network", "firewall", "sqm")) lan_main = lan_form.add_section( name="set_lan", title=_(self.userfriendly_title), description=_("This section contains settings for the local network (LAN). The provided" " defaults are suitable for most networks. <br><strong>Note:</strong> If " "you change the router IP address, all computers in LAN, probably " "including the one you are using now, will need to obtain a <strong>new " "IP address</strong> which does <strong>not</strong> happen <strong>" "immediately</strong>. It is recommended to disconnect and reconnect all " "LAN cables after submitting your changes to force the update. The next " "page will not load until you obtain a new IP from DHCP (if DHCP enabled)" " and you might need to <strong>refresh the page</strong> in your " "browser.") ) lan_main.add_field(Textbox, name="lan_ipaddr", label=_("Router IP address"), nuci_path="uci.network.lan.ipaddr", validators=validators.IPv4(), hint=_("Router's IP address in inner network. Also defines the range of " "assigned IP addresses.")) lan_main.add_field(Checkbox, name="dhcp_enabled", label=_("Enable DHCP"), nuci_path="uci.dhcp.lan.ignore", nuci_preproc=lambda val: not bool(int(val.value)), default=True, hint=_("Enable this option to automatically assign IP addresses to " "the devices connected to the router.")) lan_main.add_field(Textbox, name="dhcp_min", label=_("DHCP start"), nuci_path="uci.dhcp.lan.start")\ .requires("dhcp_enabled", True) lan_main.add_field(Textbox, name="dhcp_max", label=_("DHCP max leases"), nuci_path="uci.dhcp.lan.limit")\ .requires("dhcp_enabled", True) guest_network_section = lan_form.add_section( name="guest_network", title=_("Guest network"), ) guest_network_section.add_field( Checkbox, name="guest_network_enabled", label=_("Enable guest network"), default=False, hint=_( "Guest network is used for <a href='%(url)s'>guest Wi-Fi</a>. It is separated " "from your ordinary LAN network. Devices connected to this network are allowed " "to access the internet, but are not allowed to access other devices and " "the configuration interface of the router." ) % dict(url=reverse("config_page", page_name="wifi")), nuci_preproc=guest_network_enabled, ) guest_network_section.add_field( Textbox, name="guest_network_subnet", label=_("Guest network"), nuci_preproc=generate_network_preprocessor( "uci.network.guest_turris.ipaddr", "uci.network.guest_turris.netmask", DEFAULT_GUEST_NETWORK, DEFAULT_GUEST_MASK, ), validators=[validators.IPv4Prefix()], hint=_( "You need to set the IP range for your guest network. It is necessary that " "the range is different than ranges on your other networks (LAN, WAN, VPN, etc.)." ), ).requires("guest_network_enabled", True) guest_network_section.add_field( Checkbox, name="guest_network_shapping", label=_("Guest Lan QoS"), nuci_preproc=parse_uci_bool, nuci_path="uci.sqm.guest_limit_turris.enabled", hint=_( "This option enables you to set a bandwidth limit for the guest network, " "so that your main network doesn't get slowed-down by it." ), ).requires("guest_network_enabled", True) guest_network_section.add_field( Number, name="guest_network_download", label=_("Download (kb/s)"), validators=[validators.PositiveInteger()], hint=_( "Download speed in guest network (in kilobits per second)." ), default=1024, nuci_path="uci.sqm.guest_limit_turris.upload", ).requires("guest_network_shapping", True) guest_network_section.add_field( Number, name="guest_network_upload", label=_("Upload (kb/s)"), validators=[validators.PositiveInteger()], hint=_( "Upload speed in guest network (in kilobits per second)." ), default=1024, nuci_path="uci.sqm.guest_limit_turris.download", ).requires("guest_network_shapping", True) def lan_form_cb(data): uci = Uci() config = Config("dhcp") uci.add(config) dhcp = Section("lan", "dhcp") config.add(dhcp) # FIXME: this would overwrite any unrelated DHCP options the user might have set. # Maybe we should get the current values, scan them and remove selectively the ones # with 6 in front of them? Or have some support for higher level of stuff in nuci. options = List("dhcp_option") options.add(Value(0, "6," + data['lan_ipaddr'])) dhcp.add_replace(options) network = Config("network") uci.add(network) interface = Section("lan", "interface") network.add(interface) interface.add(Option("ipaddr", data['lan_ipaddr'])) if data['dhcp_enabled']: dhcp.add(Option("ignore", "0")) dhcp.add(Option("start", data['dhcp_min'])) dhcp.add(Option("limit", data['dhcp_max'])) else: dhcp.add(Option("ignore", "1")) # qos data qos = {'enabled': False} if 'guest_network_shapping' in data and data['guest_network_shapping']: qos['enabled'] = True qos['download'] = data['guest_network_download'] qos['upload'] = data['guest_network_upload'] # update guest network configs guest_enabled = data.get("guest_network_enabled") guest_network_subnet = data.get("guest_network_subnet") if guest_network_subnet: network, prefix = data.get("guest_network_subnet").split("/") else: network, prefix = DEFAULT_GUEST_NETWORK, DEFAULT_GUEST_PREFIX # disable guest wifi when guest network is not enabled data = client.get(filter=wifi_filter()) card_count = 0 while data.find_child("uci.wireless.@wifi-device[%d]" % card_count): card_count += 1 if not guest_enabled and card_count > 0: wireless = uci.add(Config("wireless")) for i in range(card_count): guest_iface = wireless.add(Section("guest_iface_%d" % i, "wifi-iface")) guest_iface.add(Option("disabled", "1")) guest_interfaces = ["guest_turris_%d" % e for e in range(card_count)] LanHandler.prepare_guest_configs( uci, guest_enabled, network, prefix, guest_interfaces, qos) return "edit_config", uci lan_form.add_callback(lan_form_cb) return lan_form
def __init__(self): super(IPv4, self).__init__(_("Not a valid IPv4 address."))
def __init__(self): super(IPv4Netmask, self).__init__(_("Not a valid IPv4 netmask address."))
def __init__(self, low, high): self._low = low self._high = high super(ByteLenRange, self).__init__(_("Length must be from %(low)s to %(high)s characters.") % dict(low=low, high=high)) self.js_validator_params = "[%s,%s]" % (low, high)
def default_template(self, **kwargs): return template(self.template, title=_(kwargs.pop('title', self.userfriendly_title)), **kwargs)
def get_form(self): ucollect_form = fapi.ForisForm( "ucollect", self.data, filter=filters.create_config_filter("ucollect")) fakes = ucollect_form.add_section( name="fakes", title=_("Emulated services"), description= _("One of uCollect's features is emulation of some commonly abused " "services. If this function is enabled, uCollect is listening for " "incoming connection attempts to these services. Enabling of the " "emulated services has no effect if another service is already " "listening on its default port (port numbers are listed below)." )) SERVICES_OPTIONS = ( ("23tcp", _("Telnet (23/TCP)")), ("2323tcp", _("Telnet - alternative port (2323/TCP)")), ("80tcp", _("HTTP (80/TCP)")), ("3128tcp", _("Squid HTTP proxy (3128/TCP)")), ("8123tcp", _("Polipo HTTP proxy (8123/TCP)")), ("8080tcp", _("HTTP proxy (8080/TCP)")), ) def get_enabled_services(disabled_list): disabled_services = map(lambda value: value.content, disabled_list.children) res = [ x[0] for x in SERVICES_OPTIONS if x[0] not in disabled_services ] return res fakes.add_field(MultiCheckbox, name="services", label=_("Emulated services"), args=SERVICES_OPTIONS, multifield=True, nuci_path="uci.ucollect.fakes.disable", nuci_preproc=get_enabled_services, default=[x[0] for x in SERVICES_OPTIONS]) fakes.add_field( Checkbox, name="log_credentials", label=_("Collect credentials"), hint= _("If this option is enabled, user names and passwords are collected " "and sent to server in addition to the IP address of the client." ), nuci_path="uci.ucollect.fakes.log_credentials", nuci_preproc=parse_uci_bool) def ucollect_form_cb(data): uci = Uci() ucollect = Config("ucollect") uci.add(ucollect) fakes = Section("fakes", "fakes") ucollect.add(fakes) disable = List("disable") disabled_services = [ x[0] for x in SERVICES_OPTIONS if x[0] not in data['services'] ] for i, service in enumerate(disabled_services): disable.add(Value(i, service)) if len(disabled_services): fakes.add_replace(disable) else: fakes.add_removal(disable) fakes.add(Option("log_credentials", data['log_credentials'])) return "edit_config", uci ucollect_form.add_callback(ucollect_form_cb) return ucollect_form
def __init__(self): super(AnyIP, self).__init__(_("This is not a valid IPv4 or IPv6 address."))
def get_form(self): # form definitions pw_form = fapi.ForisForm("password", self.data) pw_main = pw_form.add_section( name="set_password", title=_(self.userfriendly_title), description=_("Set your password for this administration " "interface. The password must be at least 6 " "characters long.")) if self.change: pw_main.add_field(Password, name="old_password", label=_("Current password")) label_pass1 = _("New password") label_pass2 = _("New password (repeat)") else: label_pass1 = _("Password") label_pass2 = _("Password (repeat)") pw_main.add_field(Password, name="password", label=label_pass1, required=True, validators=validators.LenRange(6, 128)) pw_main.add_field(Password, name="password_validation", label=label_pass2, required=True, validators=validators.EqualTo( "password", "password_validation", _("Passwords are not equal."))) pw_main.add_field( Checkbox, name="set_system_pw", label=_("Use the same password for advanced configuration"), hint=_( "Same password would be used for accessing this administration " "interface, for root user in LuCI web interface and for SSH " "login. Use a strong password! (If you choose not to set the " "password for advanced configuration here, you will have the " "option to do so later. Until then, the root account will be " "blocked.)")) def pw_form_cb(data): from beaker.crypto import pbkdf2 if self.change: # if changing password, check the old pw is right first uci_data = client.get(filter=filters.foris_config) password_hash = uci_data.find_child("uci.foris.auth.password") # allow changing the password if password_hash is empty if password_hash: password_hash = password_hash.value # crypt automatically extracts salt and iterations from formatted pw hash if password_hash != pbkdf2.crypt(data['old_password'], salt=password_hash): return "save_result", {'wrong_old_password': True} uci = Uci() foris = Config("foris") uci.add(foris) auth = Section("auth", "config") foris.add(auth) # use 48bit pseudo-random salt internally generated by pbkdf2 new_password_hash = pbkdf2.crypt(data['password'], iterations=1000) auth.add(Option("password", new_password_hash)) if data['set_system_pw'] is True: client.set_password("root", data['password']) return "edit_config", uci pw_form.add_callback(pw_form_cb) return pw_form
def get_form(self): dns_form = fapi.ForisForm("dns", self.data, filter=create_config_filter( "resolver", "dhcp")) dns_main = dns_form.add_section(name="set_dns", title=_(self.userfriendly_title)) dns_main.add_field(Checkbox, name="forward_upstream", label=_("Use forwarding"), nuci_path="uci.resolver.common.forward_upstream", nuci_preproc=lambda val: bool(int(val.value)), default=True) if not contract_valid(): dns_main.add_field( Checkbox, name="ignore_root_key", label=_("Disable DNSSEC"), nuci_path="uci.resolver.common.ignore_root_key", nuci_preproc=lambda val: bool(int(val.value)), default=False, ) resolver = dns_form.nuci_config.find_child( "uci.resolver.common.prefered_resolver") if resolver and resolver.value in ["kresd", "unbound"]: dns_main.add_field( Checkbox, name="dhcp_from_dns", label=_("Enable DHCP clients in DNS"), hint=_( "This will enable your DNS resolver to place DHCP client's " "names among the local DNS records."), nuci_path="uci.resolver.common.dynamic_domains", nuci_preproc=lambda val: bool(int(val.value)), default=False, ) dns_main.add_field( Textbox, name="dhcp_dns_domain", label=_("Domain of DHCP clients in DNS"), hint=_( "This domain will be used as TLD. E.g. The result for client \"android-123\" " "and domain \"lan\" will be \"android-123.lan\"."), nuci_path="uci.dhcp.@dnsmasq[0].local", nuci_preproc=lambda val: val.value.strip("/") if val else "lan", default="lan", validators=[validators.Domain()], ).requires("dhcp_from_dns", True) resolver = dns_form.nuci_config.find_child("uci.dhcp.@dnsmasq[0]") def dns_form_cb(data): uci = Uci() resolver = Config("resolver") uci.add(resolver) server = Section("common", "resolver") resolver.add(server) server.add(Option("forward_upstream", data['forward_upstream'])) if not contract_valid(): server.add(Option("ignore_root_key", data['ignore_root_key'])) if 'dhcp_from_dns' in data: server.add(Option("dynamic_domains", data['dhcp_from_dns'])) if 'dhcp_dns_domain' in data: dhcp = uci.add(Config("dhcp")) dnsmasq_section = dns_form.nuci_config.find_child( "uci.dhcp.@dnsmasq[0]") dnsmasq = dhcp.add( Section(dnsmasq_section.name, "dnsmasq", anonymous=True)) dnsmasq.add( Option("local", "/%s/" % data["dhcp_dns_domain"].strip("/"))) return "edit_config", uci dns_form.add_callback(dns_form_cb) return dns_form
def render(self, **kwargs): stats = client.get(filter=filters.stats).find_child("stats") if_eth2 = stats.data['interfaces'].get('eth2') if not (if_eth2 and if_eth2.get('is_up')): messages.warning(_("WAN port has no link, your internet connection probably won't work.")) return super(WanConfigPage, self).render(**kwargs)
def __init__(self, low, high): self._low = low self._high = high super(InRange, self).__init__(_("Not in a valid range %(low)s - %(high)s.") % dict(low=low, high=high)) self.js_validator_params = "[%s,%s]" % (low, high)
def get_form(self): # WAN wan_form = fapi.ForisForm("wan", self.data, filter=create_config_filter( "network", "smrtd", "ucollect")) wan_main = wan_form.add_section( name="set_wan", title=_(self.userfriendly_title), description= _("Here you specify your WAN port settings. Usually, you can leave this " "options untouched unless instructed otherwise by your internet service " "provider. Also, in case there is a cable or DSL modem connecting your " "router to the network, it is usually not necessary to change this " "setting.")) WAN_DHCP = "dhcp" WAN_STATIC = "static" WAN_PPPOE = "pppoe" WAN_OPTIONS = ( (WAN_DHCP, _("DHCP (automatic configuration)")), (WAN_STATIC, _("Static IP address (manual configuration)")), (WAN_PPPOE, _("PPPoE (for DSL bridges, Modem Turris, etc.)")), ) WAN6_NONE = "none" WAN6_DHCP = "dhcpv6" WAN6_STATIC = "static" WAN6_OPTIONS = ( (WAN6_DHCP, _("DHCPv6 (automatic configuration)")), (WAN6_STATIC, _("Static IP address (manual configuration)")), ) if not self.hide_no_wan: WAN6_OPTIONS = ((WAN6_NONE, _("Disable IPv6")), ) + WAN6_OPTIONS # protocol wan_main.add_field(Dropdown, name="proto", label=_("IPv4 protocol"), nuci_path="uci.network.wan.proto", args=WAN_OPTIONS, default=WAN_DHCP) # static ipv4 wan_main.add_field(Textbox, name="ipaddr", label=_("IP address"), nuci_path="uci.network.wan.ipaddr", required=True, validators=validators.IPv4())\ .requires("proto", WAN_STATIC) wan_main.add_field(Textbox, name="netmask", label=_("Network mask"), nuci_path="uci.network.wan.netmask", required=True, validators=validators.IPv4Netmask())\ .requires("proto", WAN_STATIC) wan_main.add_field(Textbox, name="gateway", label=_("Gateway"), nuci_path="uci.network.wan.gateway", validators=validators.IPv4(), required=True)\ .requires("proto", WAN_STATIC) def extract_dns_item(dns_option, index, default=None): if isinstance(dns_option, List): dns_list = [e.content for e in dns_option.children] elif isinstance(dns_option, Option): dns_list = dns_option.value.split(" ") else: return default # Server with higher priority should be last dns_list.reverse() try: return dns_list[index] except IndexError: return default # DNS servers wan_main.add_field(Textbox, name="dns1", label=_("DNS server 1"), nuci_path="uci.network.wan.dns", nuci_preproc=lambda val: extract_dns_item(val, 0), validators=validators.AnyIP(), hint=_("DNS server address is not required as the built-in " "DNS resolver is capable of working without it."))\ .requires("proto", WAN_STATIC) wan_main.add_field(Textbox, name="dns2", label=_("DNS server 2"), nuci_path="uci.network.wan.dns", nuci_preproc=lambda val: extract_dns_item(val, 1), validators=validators.AnyIP(), hint=_("DNS server address is not required as the built-in " "DNS resolver is capable of working without it."))\ .requires("proto", WAN_STATIC) # xDSL settings wan_main.add_field(Textbox, name="username", label=_("PAP/CHAP username"), nuci_path="uci.network.wan.username")\ .requires("proto", WAN_PPPOE) wan_main.add_field(Textbox, name="password", label=_("PAP/CHAP password"), nuci_path="uci.network.wan.password")\ .requires("proto", WAN_PPPOE) # IPv6 configuration wan_main.add_field(Dropdown, name="wan6_proto", label=_("IPv6 protocol"), args=WAN6_OPTIONS, default=WAN6_NONE, nuci_path="uci.network.wan6.proto") wan_main.add_field(Textbox, name="ip6addr", label=_("IPv6 address"), nuci_path="uci.network.wan6.ip6addr", validators=validators.IPv6Prefix(), hint=_("IPv6 address and prefix length for WAN interface, " "e.g. 2001:db8:be13:37da::1/64"), required=True)\ .requires("wan6_proto", WAN6_STATIC) wan_main.add_field(Textbox, name="ip6gw", label=_("IPv6 gateway"), validators=validators.IPv6(), nuci_path="uci.network.wan6.ip6gw")\ .requires("wan6_proto", WAN6_STATIC) wan_main.add_field(Textbox, name="ip6prefix", label=_("IPv6 prefix"), validators=validators.IPv6Prefix(), nuci_path="uci.network.wan6.ip6prefix", hint=_("Address range for local network, " "e.g. 2001:db8:be13:37da::/64"))\ .requires("wan6_proto", WAN6_STATIC) # enable SMRT settings only if smrtd config is present has_smrtd = wan_form.nuci_config.find_child("uci.smrtd") is not None if has_smrtd: wan_main.add_field(Hidden, name="has_smrtd", default="1") wan_main.add_field(Checkbox, name="use_smrt", label=_("Use Modem Turris"), nuci_path="uci.smrtd.global.enabled", nuci_preproc=lambda val: bool(int(val.value)), hint=_("Modem Turris (aka SMRT - Small Modem for Router Turris), " "a simple ADSL/VDSL modem designed specially for router " "Turris. Enable this option if you have Modem Turris " "connected to your router."))\ .requires("proto", WAN_PPPOE) def get_smrtd_param(param_name): """Helper function for getting SMRTd params for "connections" list.""" def wrapped(conn_list): # internet connection must be always first list element vlan_id, vpi, vci = ( conn_list.children[0].content.split(" ") if conn_list else (None, None, None)) if param_name == "VPI": return vpi elif param_name == "VCI": return vci elif param_name == "VLAN": return vlan_id raise ValueError("Unknown SMRTd connection parameter.") return wrapped def get_smrtd_vlan(data): """Helper function for getting VLAN number from Uci data.""" ifname = data.find_child("uci.network.wan.ifname") if ifname: ifname = ifname.value matches = re.match("%s.(\d+)" % self.wan_ifname, ifname) if matches: return matches.group(1) connections = data.find_child("uci.smrtd.%s.connections" % self.wan_ifname) result = get_smrtd_param("VLAN")(connections) return result # 802.1Q VLAN number is 12-bit, 0x0 and 0xFFF reserved wan_main.add_field(Textbox, name="smrt_vlan", label=_("xDSL VLAN number"), nuci_preproc=get_smrtd_vlan, validators=[validators.PositiveInteger(), validators.InRange(1, 4095)], hint=_("VLAN number for your internet connection. Your ISP might " "have provided you this number. If you have VPI and VCI " "numbers instead, leave this field empty, a default value " "will be used automatically."))\ .requires("use_smrt", True) vpi_vci_validator = validators.RequiredWithOtherFields( ("smrt_vpi", "smrt_vci"), _("Both VPI and VCI must be filled or both must be empty.")) wan_main.add_field( Textbox, name="smrt_vpi", label=_("VPI"), nuci_path="uci.smrtd.%s.connections" % self.wan_ifname, nuci_preproc=get_smrtd_param("VPI"), validators=[validators.PositiveInteger(), validators.InRange(0, 255), vpi_vci_validator], hint=_("Virtual Path Identifier (VPI) is a parameter that you might have received " "from your ISP. If you have a VLAN number instead, leave this field empty. " "You need to fill in both VPI and VCI together.") ) \ .requires("use_smrt", True) wan_main.add_field( Textbox, name="smrt_vci", label=_("VCI"), nuci_path="uci.smrtd.%s.connections" % self.wan_ifname, nuci_preproc=get_smrtd_param("VCI"), validators=[validators.PositiveInteger(), validators.InRange(32, 65535), vpi_vci_validator], hint=_("Virtual Circuit Identifier (VCI) is a parameter that you might have " "received from your ISP. If you have a VLAN number instead, leave this " "field empty. You need to fill in both VPI and VCI together.") )\ .requires("use_smrt", True) # custom MAC wan_main.add_field( Checkbox, name="custom_mac", label=_("Custom MAC address"), nuci_path="uci.network.wan.macaddr", nuci_preproc=lambda val: bool(val.value), hint=_( "Useful in cases, when a specific MAC address is required by " "your internet service provider.")) wan_main.add_field(Textbox, name="macaddr", label=_("MAC address"), nuci_path="uci.network.wan.macaddr", validators=validators.MacAddress(), hint=_("Separator is a colon, for example 00:11:22:33:44:55"), required=True)\ .requires("custom_mac", True) def wan_form_cb(data): uci = Uci() network = Config("network") uci.add(network) wan = Section("wan", "interface") network.add(wan) wan.add(Option("proto", data['proto'])) if data['custom_mac'] is True: wan.add(Option("macaddr", data['macaddr'])) else: wan.add_removal(Option("macaddr", None)) ucollect_ifname = self.wan_ifname if data['proto'] == WAN_PPPOE: wan.add(Option("username", data['username'])) wan.add(Option("password", data['password'])) wan.add(Option("ipv6", data.get("wan6_proto") is not WAN6_NONE)) ucollect_ifname = "pppoe-wan" elif data['proto'] == WAN_STATIC: wan.add(Option("ipaddr", data['ipaddr'])) wan.add(Option("netmask", data['netmask'])) wan.add(Option("gateway", data['gateway'])) dns_list = List("dns") dns2 = data.get("dns2", None) if dns2: dns_list.add(Value(0, dns2)) dns1 = data.get("dns1", None) if dns1: dns_list.add(Value( 1, dns1)) # dns with higher priority should be added last if not dns_list.children: wan.add_removal(dns_list) else: wan.add_replace(dns_list) # IPv6 configuration wan6 = Section("wan6", "interface") network.add(wan6) wan6.add(Option("ifname", "@wan")) wan6.add(Option("proto", data['wan6_proto'])) if data.get("wan6_proto") == WAN6_STATIC: wan6.add(Option("ip6addr", data['ip6addr'])) wan6.add(Option("ip6gw", data['ip6gw'])) wan6.add(Option("ip6prefix", data['ip6prefix'])) else: wan6.add_removal(Option("ip6addr", None)) wan6.add_removal(Option("ip6gw", None)) wan6.add_removal(Option("ip6prefix", None)) if has_smrtd: smrtd = Config("smrtd") uci.add(smrtd) smrt_vlan = data.get("smrt_vlan") use_smrt = data.get("use_smrt", False) wan_if = Section(self.wan_ifname, "interface") smrtd.add(wan_if) wan_if.add(Option("name", self.wan_ifname)) if use_smrt: if not smrt_vlan: # "proprietary" number - and also a common VLAN ID in CZ smrt_vlan = "848" self.wan_ifname += ".%s" % smrt_vlan vpi, vci = data.get("smrt_vpi"), data.get("smrt_vci") connections = List("connections") if vpi and vci: wan_if.add(connections) connections.add( Value(1, "%s %s %s" % (smrt_vlan, vpi, vci))) elif use_smrt: wan_if.add_removal(connections) smrtd_global = Section("global", "global") smrtd.add(smrtd_global) smrtd_global.add(Option("enabled", use_smrt)) # set correct ifname for WAN - must be changed when disabling SMRT wan.add(Option("ifname", self.wan_ifname)) # set interface for ucollect to listen on interface_if_name = None ucollect_interface0 = wan_form.nuci_config.find_child( "uci.ucollect.@interface[0]") if ucollect_interface0: interface_if_name = ucollect_interface0.name ucollect = Config("ucollect") uci.add(ucollect) interface = Section(interface_if_name, "interface", True) ucollect.add(interface) interface.add(Option("ifname", ucollect_ifname)) return "edit_config", uci wan_form.add_callback(wan_form_cb) return wan_form
def __init__(self): super(NotEmpty, self).__init__(_("This field is required."))
def get_form(self): stats = client.get(filter=filters.stats).find_child("stats") cards = self._get_wireless_cards(stats) if not cards: return None wifi_form = fapi.ForisForm("wifi", self.data, filter=filters.wifi_filter()) # Create mapping of radio_name -> iface_index radio_to_iface = self._get_radio_to_iface(wifi_form) # Add header section (used for page title) wifi_form.add_section( name="wifi", title=_(self.userfriendly_title), description= _("If you want to use your router as a Wi-Fi access point, enable Wi-Fi " "here and fill in an SSID (the name of the access point) and a " "corresponding password. You can then set up your mobile devices, " "using the QR code available next to the form.")) # Add wifi section wifi_section = wifi_form.add_section( name="wifi_settings", title=_("Wi-Fi settings"), ) # Get list of available radios radios = self._get_radios(cards, wifi_section, radio_to_iface, self.data or {}, wifi_form.nuci_config()) def wifi_form_cb(data): uci = Uci() wireless = Config("wireless") uci.add(wireless) guest_wifi_enabled = False for radio in radios: if self._prepare_radio_cb(data, wireless, radio): guest_wifi_enabled = True guest_interfaces = ["guest_turris_%s" % e for e in sorted(radios)] # test whether it is required to pass update guest network current_data = client.get(filter=filters.wifi_filter()) current_enabled = preprocessors.guest_network_enabled(current_data) if guest_wifi_enabled and not current_enabled: # Guest network handling guest_network_subnet = data.get("guest_network_subnet") if guest_network_subnet: network, prefix = data.get("guest_network_subnet").split( "/") else: network, prefix = DEFAULT_GUEST_NETWORK, DEFAULT_GUEST_PREFIX LanHandler.prepare_guest_configs(uci, True, network, prefix, guest_interfaces) elif guest_wifi_enabled: # try to update guest interfaces if the differs stored = current_data.find_child( "uci.network.guest_turris.ifname") if not stored or set( stored.value.split(" ")) != set(guest_interfaces): network_conf = uci.add(Config("network")) interface_section = network_conf.add( Section("guest_turris", "interface")) interface_section.add( Option("ifname", " ".join(guest_interfaces))) return "edit_config", uci wifi_form.add_callback(wifi_form_cb) return wifi_form
def _add_wifi_section(self, wifi_section, wifi_card, radio_to_iface, post_data, nuci_data, last=False): HINTS = { 'password': _("WPA2 pre-shared key, that is required to connect to the " "network. Minimum length is 8 characters.") } radio_index = int(wifi_card['name'][3:]) iface_index = radio_to_iface.get('radio%s' % radio_index) if iface_index is None: # Interface is not present in the wireless config - skip this radio return None def prefixed_name(name): return "radio%s-%s" % (radio_index, name) wifi_main = wifi_section.add_section( name=prefixed_name("set_wifi"), title=None, ) wifi_main.add_field(Hidden, name=prefixed_name("iface_section"), nuci_path="uci.wireless.@wifi-iface[%s]" % iface_index, nuci_preproc=lambda val: val.name) # Use numbering starting from one. In rare cases, it may happen that the first radio # is not radio0, or that there's a gap between radio numbers, but it should not happen # on most of the setups. wifi_main.add_field(Checkbox, name=prefixed_name("wifi_enabled"), label=_("Enable Wi-Fi %s") % (radio_index + 1), default=True, nuci_path="uci.wireless.@wifi-iface[%s].disabled" % iface_index, nuci_preproc=lambda val: not bool(int(val.value))) wifi_main.add_field( Textbox, name=prefixed_name("ssid"), label=_("SSID"), nuci_path="uci.wireless.@wifi-iface[%s].ssid" % iface_index, required=True, validators=validators.ByteLenRange(1, 32)).requires( prefixed_name("wifi_enabled"), True) wifi_main.add_field( Checkbox, name=prefixed_name("ssid_hidden"), label=_("Hide SSID"), default=False, nuci_path="uci.wireless.@wifi-iface[%s].hidden" % iface_index, hint= _("If set, network is not visible when scanning for available networks." )).requires(prefixed_name("wifi_enabled"), True) channels_2g4, channels_5g = self._get_channels(wifi_card) is_dual_band = False # hwmode choice for dual band devices if len(channels_2g4) > 1 and len(channels_5g) > 1: is_dual_band = True wifi_main.add_field( Radio, name=prefixed_name("hwmode"), label=_("Wi-Fi mode"), default="11g", args=(("11g", "2.4 GHz (g)"), ("11a", "5 GHz (a)")), nuci_path="uci.wireless.radio%s.hwmode" % radio_index, nuci_preproc=lambda x: x.value.replace("n", "" ), # old configs used # 11ng/11na hint= _("The 2.4 GHz band is more widely supported by clients, but " "tends to have more interference. The 5 GHz band is a newer" " standard and may not be supported by all your devices. It " "usually has less interference, but the signal does not " "carry so well indoors.")).requires( prefixed_name("wifi_enabled"), True) htmodes = (("NOHT", _("Disabled")), ("HT20", _("802.11n - 20 MHz wide channel")), ("HT40", _("802.11n - 40 MHz wide channel"))) # Allow VHT modes only if the card has the capabilities and 5 GHz band is selected hwmode = self._get_value(post_data, nuci_data, "radio%s-hwmode" % radio_index, "uci.wireless.radio%s.hwmode" % radio_index) allow_vht = wifi_card['vht-capabilities'] and hwmode == "11a" if allow_vht: htmodes += ( ("VHT20", _("802.11ac - 20 MHz wide channel")), ("VHT40", _("802.11ac - 40 MHz wide channel")), ("VHT80", _("802.11ac - 80 MHz wide channel")), ) wifi_main.add_field( Dropdown, name=prefixed_name("htmode"), label=_("802.11n/ac mode"), args=htmodes, nuci_path="uci.wireless.radio%s.htmode" % radio_index, hint= _("Change this to adjust 802.11n/ac mode of operation. 802.11n with 40 MHz wide " "channels can yield higher throughput but can cause more interference in the " "network. If you don't know what to choose, use the default option with 20 MHz " "wide channel.")).requires(prefixed_name("wifi_enabled"), True) # 2.4 GHz channels if len(channels_2g4) > 1: field_2g4 = wifi_main.add_field( Dropdown, name=prefixed_name("channel2g4"), label=_("Network channel"), default=channels_2g4[0][0], args=channels_2g4, nuci_path="uci.wireless.radio%s.channel" % radio_index).requires(prefixed_name("wifi_enabled"), True) if is_dual_band: field_2g4.requires(prefixed_name("hwmode"), "11g") # 5 GHz channels if len(channels_5g) > 1: field_5g = wifi_main.add_field( Dropdown, name=prefixed_name("channel5g"), label=_("Network channel"), default=channels_5g[0][0], args=channels_5g, nuci_path="uci.wireless.radio%s.channel" % radio_index).requires(prefixed_name("wifi_enabled"), True) if is_dual_band: field_5g.requires(prefixed_name("hwmode"), "11a") wifi_main.add_field( Password, name=prefixed_name("key"), label=_("Network password"), nuci_path="uci.wireless.@wifi-iface[%s].key" % iface_index, required=True, validators=validators.ByteLenRange(8, 63), hint=HINTS['password']).requires(prefixed_name("wifi_enabled"), True) # Guest wi-fi part guest_section = wifi_main.add_section( name=prefixed_name("set_guest_wifi"), title=_("Guest Wi-Fi"), description=_("Set guest Wi-Fi here.")) guest_section.add_field( Checkbox, name=prefixed_name("guest_enabled"), label=_("Enable guest Wi-Fi"), default=False, nuci_path="uci.wireless.guest_iface_%s.disabled" % iface_index, nuci_preproc=lambda value: not parse_uci_bool(value), hint= _("Enables Wi-Fi for guests, which is separated from LAN network. Devices " "connected to this network are allowed to access the internet, but aren't " "allowed to access other devices and the configuration interface of the router. " "Parameters of the guest network can be set in <a href='%(url)s'>the LAN tab</a>. " ) % dict(url=reverse("config_page", page_name="lan"))).requires( prefixed_name("wifi_enabled"), True) default_ssid = self._get_value( post_data, nuci_data, prefixed_name("ssid"), "uci.wireless.@wifi-iface[%s].ssid" % iface_index, "Turris", ) default_guest_ssid = self._get_value( post_data, nuci_data, prefixed_name("guest_ssid"), "uci.wireless.guest_iface_%s.ssid" % iface_index, "%s-guest" % default_ssid) guest_section.add_field(Textbox, name=prefixed_name("guest_ssid"), label=_("SSID for guests"), nuci_path="uci.wireless.guest_iface_%s.ssid" % iface_index, required=True, validators=validators.ByteLenRange(1, 32), default=default_guest_ssid).requires( prefixed_name("guest_enabled"), True) guest_section.add_field( Password, name=prefixed_name("guest_key"), label=_("Password for guests"), nuci_path="uci.wireless.guest_iface_%s.key" % iface_index, required=True, default="", validators=validators.ByteLenRange(8, 63), hint=HINTS['password'], ).requires(prefixed_name("guest_enabled"), True) # Horizontal line separating wi-fi cards if not last: wifi_main.add_field(HorizontalLine, name=prefixed_name("wifi-separator"), class_="wifi-separator").requires( prefixed_name("wifi_enabled"), True)
def get_form(self): def approval_preproc_approve_status(nuci_config): """Preprocess approval status """ # try to obtain status from the form data if self.data and "approval_status" in self.data: return self.data["approval_status"] need_item = nuci_config.find_child("uci.updater.approvals.need") if not need_item: return UpdaterAutoUpdatesHandler.APPROVAL_NO if not parse_uci_bool(need_item.value): return UpdaterAutoUpdatesHandler.APPROVAL_NO seconds_item = nuci_config.find_child( "uci.updater.approvals.auto_grant_seconds") if not seconds_item: return UpdaterAutoUpdatesHandler.APPROVAL_NEEDED try: hours = int(seconds_item.value) except ValueError: return UpdaterAutoUpdatesHandler.APPROVAL_NEEDED if hours < 0: return UpdaterAutoUpdatesHandler.APPROVAL_NEEDED return UpdaterAutoUpdatesHandler.APPROVAL_TIMEOUT form = fapi.ForisForm("updater_eula", self.data, filter=filters.create_config_filter( "updater", "foris")) main_section = form.add_section(name="agree_eula", title=_(self.userfriendly_title)) main_section.add_field( Radio, name="agreed", label=_("I agree"), default="1", args=(("1", _("Use automatic updates (recommended)")), ("0", _("Turn automatic updates off"))), nuci_preproc=lambda x: "1" if preproc_disabled_to_agreed(x) else "0") approval_section = form.add_section(name="approvals", title=_("Update approvals")) main_section.add_section(approval_section) approval_section.add_field( RadioSingle, name=UpdaterAutoUpdatesHandler.APPROVAL_NO, group="approval_status", label=_("Automatic installation"), hint=_("Updates will be installed without user's intervention."), nuci_preproc=lambda e: approval_preproc_approve_status(e), ).requires("agreed", "1") approval_section.add_field( RadioSingle, name=UpdaterAutoUpdatesHandler.APPROVAL_TIMEOUT, group="approval_status", label=_("Delayed updates"), hint= _("Updates will be installed with an adjustable delay. You can also approve them manually." ), nuci_preproc=lambda e: approval_preproc_approve_status(e), ).requires("agreed", "1") approval_section.add_field( Number, name="approval_timeout", nuci_path="uci.updater.approvals.auto_grant_seconds", nuci_preproc=lambda val: int(val.value) / 60 / 60, # seconds to hours validators=[validators.InRange(1, 24 * 7)], default=24, required=True, min=1, max=24 * 7, ).requires(UpdaterAutoUpdatesHandler.APPROVAL_TIMEOUT, UpdaterAutoUpdatesHandler.APPROVAL_TIMEOUT).requires( UpdaterAutoUpdatesHandler.APPROVAL_NO, UpdaterAutoUpdatesHandler.APPROVAL_TIMEOUT).requires( UpdaterAutoUpdatesHandler.APPROVAL_NEEDED, UpdaterAutoUpdatesHandler.APPROVAL_TIMEOUT) approval_section.add_field( RadioSingle, name=UpdaterAutoUpdatesHandler.APPROVAL_NEEDED, group="approval_status", label=_("Update approval needed"), hint= _("You have to approve the updates, otherwise they won't be installed." ), nuci_preproc=lambda e: approval_preproc_approve_status(e), ).requires("agreed", "1") def form_cb(data): agreed = bool(int(data.get("agreed", "0"))) approval_status = data.get(UpdaterAutoUpdatesHandler.APPROVAL_NO, UpdaterAutoUpdatesHandler.APPROVAL_NO) auto_grant_seconds = int(data.get("approval_timeout", 24)) * 60 * 60 uci = Uci() updater = uci.add(Config("updater")) override = updater.add(Section("override", "override")) override.add(Option("disable", not agreed)) approvals = updater.add_replace(Section("approvals", "approvals")) if approval_status == UpdaterAutoUpdatesHandler.APPROVAL_NO: approvals.add(Option("need", "0")) elif approval_status == UpdaterAutoUpdatesHandler.APPROVAL_NEEDED: approvals.add(Option("need", "1")) elif approval_status == UpdaterAutoUpdatesHandler.APPROVAL_TIMEOUT: approvals.add(Option("need", "1")) approvals.add(Option("auto_grant_seconds", auto_grant_seconds)) return "edit_config", uci def save_result_cb(data): return "save_result", { 'agreed': bool(int(data.get("agreed", "0"))) } form.add_callback(form_cb) form.add_callback(save_result_cb) return form