|
91 | 91 | predbat_log_file_prev, |
92 | 92 | is_secret_key, |
93 | 93 | ) |
94 | | -from utils import is_data_numerical, ROOT_YAML_KEY, SECRET_MASK, YAML_DUMP_WIDTH, parse_yaml_path, update_nested_yaml_value # noqa: F401 - re-exported: moved to utils.py, agent_tools.py/chat_tools.py must not import from web.py |
| 94 | +from utils import is_data_numerical, ROOT_YAML_KEY, SECRET_MASK, YAML_DUMP_WIDTH, parse_yaml_path, resolve_nested_yaml_value, update_nested_yaml_value # noqa: F401 - re-exported: moved to utils.py, agent_tools.py/chat_tools.py must not import from web.py |
95 | 95 | from const import TIME_FORMAT, TIME_FORMAT_DAILY, TIME_FORMAT_HA, MANUAL_RATE_MAX_MINUTES, MANUAL_TIME_MAX_MINUTES |
96 | 96 | from predbat import THIS_VERSION_DISPLAY |
97 | 97 | from component_base import ComponentBase |
@@ -514,6 +514,7 @@ async def start(self): |
514 | 514 | app.router.add_get("/log", self.html_log) |
515 | 515 | app.router.add_get("/apps", self.html_apps) |
516 | 516 | app.router.add_post("/apps", self.html_apps_post) |
| 517 | + app.router.add_get("/apps_value", self.html_apps_value) |
517 | 518 | app.router.add_get("/charts", self.html_charts) |
518 | 519 | app.router.add_get("/config", self.html_config) |
519 | 520 | app.router.add_get("/entity", self.html_entity) |
@@ -2950,9 +2951,10 @@ def render_type(self, arg, value, parent_path="", row_counter=None): |
2950 | 2951 | actions_cell += self.render_delete_button(nested_row_id) |
2951 | 2952 |
|
2952 | 2953 | raw_value = self.resolve_value_raw(key, nested_value) |
| 2954 | + secret_attr = self.secret_row_attr(key, nested_value, "'") |
2953 | 2955 |
|
2954 | 2956 | if nested_row_id is not None: |
2955 | | - text += f"<tr id='nested_row_{nested_row_id}' data-nested-path='{nested_path_attr}' data-nested-original='{html_module.escape(str(raw_value))}'><td><b>{key}: </b></td><td id='nested_value_{nested_row_id}'>{self.render_type(key, nested_value, nested_path, row_counter)}</td><td>{actions_cell}</td></tr>\n" |
| 2957 | + text += f"<tr id='nested_row_{nested_row_id}' data-nested-path='{nested_path_attr}' data-nested-original='{html_module.escape(str(raw_value))}'{secret_attr}><td><b>{key}: </b></td><td id='nested_value_{nested_row_id}'>{self.render_type(key, nested_value, nested_path, row_counter)}</td><td>{actions_cell}</td></tr>\n" |
2956 | 2958 | else: |
2957 | 2959 | text += "<tr><td><b>{}: </b></td><td colspan='2'>{}</td></tr>\n".format(key, self.render_type(key, nested_value, nested_path, row_counter)) |
2958 | 2960 | text += self.render_add_row("addDictKey", [dict_path], "Add setting", row_counter) |
@@ -3752,6 +3754,41 @@ def is_editable_value(self, value): |
3752 | 3754 | return len(value) > 0 and any(self.is_editable_value(item) for item in value) |
3753 | 3755 | return False |
3754 | 3756 |
|
| 3757 | + def secret_row_attr(self, key, value, quote): |
| 3758 | + """ |
| 3759 | + Return the data-secret attribute for a row whose value html_apps() served masked, else "". |
| 3760 | +
|
| 3761 | + The page holds SECRET_MASK for a credential rather than the credential itself, so the |
| 3762 | + browser's Edit has to fetch the real value (/apps_value) before it can offer it - this flag |
| 3763 | + is how it knows to. Only a scalar leaf is flagged: mask_secret_args() replaces a secret |
| 3764 | + key's whole value, list or not, so this is the row that holds the mask. |
| 3765 | + """ |
| 3766 | + if value == SECRET_MASK and is_secret_key(key): |
| 3767 | + return " data-secret={}1{}".format(quote, quote) |
| 3768 | + return "" |
| 3769 | + |
| 3770 | + async def html_apps_value(self, request): |
| 3771 | + """ |
| 3772 | + Return the real value of one apps.yaml setting, for the /apps editor's Edit on a credential. |
| 3773 | +
|
| 3774 | + html_apps() serves credentials masked, so without this Edit could only offer "xxx" - the |
| 3775 | + user could neither see nor amend the key, and saving it back unchanged is refused. Only |
| 3776 | + the one path asked for is returned, and only when it is a single value, so the page itself |
| 3777 | + still never carries credentials and a whole container cannot be pulled in the clear. The |
| 3778 | + unmasked /debug_apps download already exposes the same values behind the same access. |
| 3779 | + """ |
| 3780 | + path = request.query.get("path", "") |
| 3781 | + if not path: |
| 3782 | + return web.json_response({"success": False, "message": "No path given"}) |
| 3783 | + try: |
| 3784 | + value = resolve_nested_yaml_value(self.args, path) |
| 3785 | + except (KeyError, TypeError, ValueError) as e: |
| 3786 | + return web.json_response({"success": False, "message": f"Path {path} not found: {str(e)}"}) |
| 3787 | + if not self.is_editable_value(value) or isinstance(value, list): |
| 3788 | + return web.json_response({"success": False, "message": f"{path} is not a single value that can be edited here"}) |
| 3789 | + leaf = [segment for segment in parse_yaml_path(path) if not segment.startswith("[")][-1] |
| 3790 | + return web.json_response({"success": True, "value": str(self.resolve_value_raw(leaf, value))}) |
| 3791 | + |
3755 | 3792 | def resolve_value_raw(self, arg, value): |
3756 | 3793 | if isinstance(value, str) and "{" in value: |
3757 | 3794 | text = self.base.resolve_arg(arg, value, indirect=False, quiet=True) |
@@ -3870,8 +3907,8 @@ async def html_apps(self, request): |
3870 | 3907 | # For numerical values, show edit button |
3871 | 3908 | actions_cell = f'<button class="edit-button" onclick="editValue({row_id})">Edit</button>' |
3872 | 3909 |
|
3873 | | - text += '<tr id="row_{}" data-arg-name="{}" data-original-value="{}"><td>{}</td><td id="value_{}">{}</td><td>{}</td></tr>\n'.format( |
3874 | | - row_id, arg_attr, html_module.escape(str(raw_value)), arg, row_id, self.render_type(arg, value, "", row_counter), actions_cell |
| 3910 | + text += '<tr id="row_{}" data-arg-name="{}" data-original-value="{}"{}><td>{}</td><td id="value_{}">{}</td><td>{}</td></tr>\n'.format( |
| 3911 | + row_id, arg_attr, html_module.escape(str(raw_value)), self.secret_row_attr(arg, value, '"'), arg, row_id, self.render_type(arg, value, "", row_counter), actions_cell |
3875 | 3912 | ) |
3876 | 3913 | row_id += 1 |
3877 | 3914 |
|
|
0 commit comments