diff --git a/share/config_domain.toml b/share/config_domain.toml index 82ef90c32..88d02349f 100644 --- a/share/config_domain.toml +++ b/share/config_domain.toml @@ -5,10 +5,10 @@ i18n = "domain_config" name = "Features" [feature.app] + [feature.app.default_app] type = "app" filter = "is_webapp" - default = "_none" [feature.mail] @@ -44,28 +44,30 @@ name = "Certificate" [cert.cert.cert_validity] type = "number" readonly = true - visible = "false" + visible = false # Automatically filled by DomainConfigPanel [cert.cert.cert_issuer] type = "string" + readonly = true visible = false # Automatically filled by DomainConfigPanel [cert.cert.acme_eligible] type = "boolean" + readonly = true visible = false # Automatically filled by DomainConfigPanel [cert.cert.acme_eligible_explain] type = "alert" style = "warning" - visible = "acme_eligible == false || acme_eligible == null" + visible = "!acme_eligible" [cert.cert.cert_no_checks] type = "boolean" default = false - visible = "acme_eligible == false || acme_eligible == null" + visible = "!acme_eligible" [cert.cert.cert_install] type = "button" diff --git a/src/app.py b/src/app.py index c5b865819..fd210266b 100644 --- a/src/app.py +++ b/src/app.py @@ -1411,7 +1411,7 @@ def app_remove(operation_logger, app, purge=False, force_workdir=None): for domain in domain_list()["domains"]: if domain_config_get(domain, "feature.app.default_app") == app: - domain_config_set(domain, "feature.app.default_app", "_none") + domain_config_set(domain, "feature.app.default_app", None) if ret == 0: logger.success(m18n.n("app_removed", app=app)) @@ -1448,7 +1448,7 @@ def app_makedefault(operation_logger, app, domain=None, undo=False): operation_logger.start() if undo: - domain_config_set(domain, "feature.app.default_app", "_none") + domain_config_set(domain, "feature.app.default_app", None) else: domain_config_set(domain, "feature.app.default_app", app) @@ -1701,7 +1701,7 @@ def app_ssowatconf(): for domain in domains: default_app = domain_config_get(domain, "feature.app.default_app") - if default_app != "_none" and _is_installed(default_app): + if default_app and _is_installed(default_app): app_settings = _get_app_settings(default_app) app_domain = app_settings["domain"] app_path = app_settings["path"] diff --git a/src/dns.py b/src/dns.py index 3a5e654ec..7d4f7bec8 100644 --- a/src/dns.py +++ b/src/dns.py @@ -529,7 +529,7 @@ def _get_registrar_config_section(domain): parent_domain=parent_domain, parent_domain_link=parent_domain_link, ), - "value": "parent_domain", + "default": "parent_domain", } ) return OrderedDict(registrar_infos) @@ -542,7 +542,7 @@ def _get_registrar_config_section(domain): "type": "alert", "style": "success", "ask": m18n.n("domain_dns_registrar_yunohost"), - "value": "yunohost", + "default": "yunohost", } ) return OrderedDict(registrar_infos) @@ -552,7 +552,7 @@ def _get_registrar_config_section(domain): "type": "alert", "style": "info", "ask": m18n.n("domain_dns_conf_special_use_tld"), - "value": None, + "default": None, } ) @@ -564,7 +564,7 @@ def _get_registrar_config_section(domain): "type": "alert", "style": "warning", "ask": m18n.n("domain_dns_registrar_not_supported"), - "value": None, + "default": None, } ) else: @@ -573,7 +573,7 @@ def _get_registrar_config_section(domain): "type": "alert", "style": "info", "ask": m18n.n("domain_dns_registrar_supported", registrar=registrar), - "value": registrar, + "default": registrar, } ) diff --git a/src/domain.py b/src/domain.py index 7839b988d..0bd110c06 100644 --- a/src/domain.py +++ b/src/domain.py @@ -18,7 +18,7 @@ # import os import time -from typing import List, Optional +from typing import List, Optional, TYPE_CHECKING, Union, Any from collections import OrderedDict from moulinette import m18n, Moulinette @@ -33,10 +33,14 @@ from yunohost.app import ( _get_conflicting_apps, ) from yunohost.regenconf import regen_conf, _force_clear_hashes, _process_regen_conf -from yunohost.utils.config import ConfigPanel, Question +from yunohost.utils.configpanel import Config +from yunohost.utils.config import Question from yunohost.utils.error import YunohostError, YunohostValidationError from yunohost.log import is_unit_operation +if TYPE_CHECKING: + from yunohost.utils.configpanel import YunoForm + logger = getActionLogger("yunohost.domain") DOMAIN_SETTINGS_DIR = "/etc/yunohost/domains" @@ -532,135 +536,103 @@ def domain_config_set( return config.set(key, value, args, args_file, operation_logger=operation_logger) -class DomainConfigPanel(ConfigPanel): +class DomainConfigPanel(Config): entity_type = "domain" save_path_tpl = f"{DOMAIN_SETTINGS_DIR}/{{entity}}.yml" save_mode = "diff" - def _apply(self): - if ( - "default_app" in self.future_values - and self.future_values["default_app"] != self.values["default_app"] - ): - from yunohost.app import app_ssowatconf, app_map + # TODO add mechanism to share some settings with other domains on the same zone - if "/" in app_map(raw=True).get(self.entity, {}): - raise YunohostValidationError( - "app_make_default_location_already_used", - app=self.future_values["default_app"], - domain=self.entity, - other_app=app_map(raw=True)[self.entity]["/"]["id"], - ) + def _get_config_data( + self, + panel_id: Union[str, None] = None, + section_id: Union[str, None] = None, + option_id: Union[str, None] = None, + ) -> dict[str, Any]: + any_filter = all([panel_id, section_id, option_id]) + config_data = super()._get_config_data(panel_id, section_id, option_id) - super()._apply() - - # Reload ssowat if default app changed - if ( - "default_app" in self.future_values - and self.future_values["default_app"] != self.values["default_app"] - ): - app_ssowatconf() - - stuff_to_regen_conf = [] - if ( - "xmpp" in self.future_values - and self.future_values["xmpp"] != self.values["xmpp"] - ): - stuff_to_regen_conf.append("nginx") - stuff_to_regen_conf.append("metronome") - - if ( - "mail_in" in self.future_values - and self.future_values["mail_in"] != self.values["mail_in"] - ) or ( - "mail_out" in self.future_values - and self.future_values["mail_out"] != self.values["mail_out"] - ): - if "nginx" not in stuff_to_regen_conf: - stuff_to_regen_conf.append("nginx") - stuff_to_regen_conf.append("postfix") - stuff_to_regen_conf.append("dovecot") - stuff_to_regen_conf.append("rspamd") - - if stuff_to_regen_conf: - regen_conf(names=stuff_to_regen_conf) - - def _get_toml(self): - toml = super()._get_toml() - - toml["feature"]["xmpp"]["xmpp"]["default"] = ( + config_data["feature"]["xmpp"]["xmpp"]["default"] = ( 1 if self.entity == _get_maindomain() else 0 ) # Optimize wether or not to load the DNS section, # e.g. we don't want to trigger the whole _get_registary_config_section # when just getting the current value from the feature section - filter_key = self.filter_key.split(".") if self.filter_key != "" else [] - if not filter_key or filter_key[0] == "dns": + if not any_filter or panel_id == "dns": from yunohost.dns import _get_registrar_config_section - toml["dns"]["registrar"] = _get_registrar_config_section(self.entity) - - # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... - self.registar_id = toml["dns"]["registrar"]["registrar"]["value"] - del toml["dns"]["registrar"]["registrar"]["value"] + config_data["dns"]["registrar"] = _get_registrar_config_section(self.entity) # Cert stuff - if not filter_key or filter_key[0] == "cert": + if not any_filter or panel_id == "cert": from yunohost.certificate import certificate_status status = certificate_status([self.entity], full=True)["certificates"][ self.entity ] - toml["cert"]["cert"]["cert_summary"]["style"] = status["style"] + config_data["cert"]["cert"]["cert_summary"]["style"] = status["style"] # i18n: domain_config_cert_summary_expired # i18n: domain_config_cert_summary_selfsigned # i18n: domain_config_cert_summary_abouttoexpire # i18n: domain_config_cert_summary_ok # i18n: domain_config_cert_summary_letsencrypt - toml["cert"]["cert"]["cert_summary"]["ask"] = m18n.n( + config_data["cert"]["cert"]["cert_summary"]["ask"] = m18n.n( f"domain_config_cert_summary_{status['summary']}" ) - # FIXME: Ugly hack to save the cert status and reinject it in _load_current_values ... - self.cert_status = status + for option_id, status_key in [ + ("cert_validity", "validity"), + ("cert_issuer", "CA_type"), + ("acme_eligible", "ACME_eligible"), + # FIXME not sure why "summary" was injected in settings values + # ("summary", "summary") + ]: + config_data["cert"]["cert"][option_id]["default"] = status[status_key] - return toml + # Other specific strings used in config panels + # i18n: domain_config_cert_renew_help - def get(self, key="", mode="classic"): - result = super().get(key=key, mode=mode) + return config_data - if mode == "full": - for panel, section, option in self._iterate(): - # This injects: - # i18n: domain_config_cert_renew_help - # i18n: domain_config_default_app_help - # i18n: domain_config_xmpp_help - if m18n.key_exists(self.config["i18n"] + "_" + option["id"] + "_help"): - option["help"] = m18n.n( - self.config["i18n"] + "_" + option["id"] + "_help" - ) - return self.config + def _apply(self, settings: "YunoForm", exclude=None): + # Do not rely on super `_apply` to get new values since it excludes + # values that are default values. + new_values = settings.dict(exclude_unset=True) - return result + # Check if another app is already the default one + # FIXME could be a custom pydantic.validator() instead + if "default_app" in new_values: + from yunohost.app import app_ssowatconf, app_map - def _load_current_values(self): - # TODO add mechanism to share some settings with other domains on the same zone - super()._load_current_values() + app_map_ = app_map(raw=True).get(self.entity, {}) + if "/" in app_map_: + raise YunohostValidationError( + "app_make_default_location_already_used", + app=new_values["default_app"], + domain=self.entity, + other_app=app_map_["/"]["id"], + ) - # FIXME: Ugly hack to save the registar id/value and reinject it in _load_current_values ... - filter_key = self.filter_key.split(".") if self.filter_key != "" else [] - if not filter_key or filter_key[0] == "dns": - self.values["registrar"] = self.registar_id + # Save settings (without default values) + super()._apply(settings) - # FIXME: Ugly hack to save the cert status and reinject it in _load_current_values ... - if not filter_key or filter_key[0] == "cert": - self.values["cert_validity"] = self.cert_status["validity"] - self.values["cert_issuer"] = self.cert_status["CA_type"] - self.values["acme_eligible"] = self.cert_status["ACME_eligible"] - self.values["summary"] = self.cert_status["summary"] + # Reload ssowat if default app changed + if "default_app" in new_values: + app_ssowatconf() + + stuff_to_regen_conf = set() + if "xmpp" in new_values: + stuff_to_regen_conf.update({"nginx", "metronome"}) + + if "mail_in" in new_values or "mail_out" in new_values: + stuff_to_regen_conf.update({"nginx", "postfix", "dovecot", "rspamd"}) + + if stuff_to_regen_conf: + # TODO `regen_conf` could (only?) accept a set() as "names" + regen_conf(names=list(stuff_to_regen_conf)) def domain_action_run(domain, action, args=None):