@qingflow-tech/qingflow-cli 1.1.61-beta.4 → 1.1.61-beta.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/package.json +1 -1
- package/pyproject.toml +1 -1
- package/skills/qingflow-cli/reference/builder/30-schema-fields.md +13 -0
- package/skills/qingflow-cli/reference/builder/70-portal.md +37 -16
- package/skills/qingflow-cli/reference/examples/portal/portal_apply_v1alpha1.example.json +100 -0
- package/skills/qingflow-cli/reference/examples/portal/portal_sections_all_types.example.json +6 -7
- package/src/qingflow_mcp/app_form_validator.py +20 -3
- package/src/qingflow_mcp/builder_facade/app_form.py +137 -17
- package/src/qingflow_mcp/builder_facade/models.py +164 -13
- package/src/qingflow_mcp/builder_facade/portals_schema.py +397 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/manifest.json +14 -1
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/complete.json +49 -9
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/address.json +27 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_content_classification.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_content_extraction.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_content_generation.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_document_parsing.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_image_generation.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_image_recognition.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_machine_translation.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ai_text_summary.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/amount.json +2 -2
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/attachment.json +27 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/date_duration.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/embed_field.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/image_select.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/location.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/number.json +2 -2
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/ocr.json +146 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/q_linker.json +2 -2
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/relation.json +27 -0
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/fields/subtable.json +29 -2
- package/src/qingflow_mcp/builder_facade/schemas/app_form/v1alpha1/summary.json +20 -7
- package/src/qingflow_mcp/builder_facade/schemas/portals/manifest.json +59 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/complete.json +1397 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/carousel.json +193 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/chart.json +643 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/favourite.json +203 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/filter.json +268 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/grid.json +265 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/link.json +131 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/task.json +199 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/text.json +123 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/components/view.json +196 -0
- package/src/qingflow_mcp/builder_facade/schemas/portals/v1alpha1/summary.json +800 -0
- package/src/qingflow_mcp/builder_facade/service.py +1104 -71
- package/src/qingflow_mcp/cli/commands/builder.py +114 -5
- package/src/qingflow_mcp/public_surface.py +2 -0
- package/src/qingflow_mcp/server_app_builder.py +32 -1
- package/src/qingflow_mcp/solution/compiler/icon_utils.py +73 -0
- package/src/qingflow_mcp/solution/executor.py +5 -2
- package/src/qingflow_mcp/tools/ai_builder_tools.py +110 -12
- package/src/qingflow_mcp/tools/portal_tools.py +4 -54
- package/src/qingflow_mcp/tools/portal_writer.py +59 -0
- package/src/qingflow_mcp/tools/solution_tools.py +2 -0
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import argparse
|
|
4
|
+
import sys
|
|
4
5
|
from copy import deepcopy
|
|
5
6
|
from pathlib import Path
|
|
6
7
|
|
|
@@ -18,6 +19,7 @@ from ...app_form_validator import (
|
|
|
18
19
|
validate_declaration,
|
|
19
20
|
validate_formula_expression,
|
|
20
21
|
)
|
|
22
|
+
from ...builder_facade.portals_schema import PORTALS_SUPPORTED_SCHEMA_KINDS
|
|
21
23
|
from ...builder_facade.views_schema import VIEWS_SUPPORTED_SCHEMA_KINDS
|
|
22
24
|
from ..context import CliContext
|
|
23
25
|
from ..json_io import load_json_value
|
|
@@ -260,17 +262,39 @@ def register(subparsers: argparse._SubParsersAction[argparse.ArgumentParser]) ->
|
|
|
260
262
|
portal_list = portal_subparsers.add_parser("list", help="列出可配置门户")
|
|
261
263
|
portal_list.set_defaults(handler=_handle_portal_list, format_hint="builder_summary")
|
|
262
264
|
|
|
263
|
-
|
|
265
|
+
portal_schema = portal_subparsers.add_parser("schema", help="读取门户声明 Schema")
|
|
266
|
+
portal_schema.add_argument("--schema-version")
|
|
267
|
+
portal_schema.add_argument(
|
|
268
|
+
"--schema-kind",
|
|
269
|
+
choices=PORTALS_SUPPORTED_SCHEMA_KINDS,
|
|
270
|
+
)
|
|
271
|
+
portal_schema.set_defaults(handler=_handle_portal_schema, format_hint="builder_summary", force_json_output=True)
|
|
272
|
+
|
|
273
|
+
portal_get = portal_subparsers.add_parser("get", help="读取门户声明或详情")
|
|
264
274
|
portal_get.add_argument("--dash-key", required=True)
|
|
265
275
|
portal_get.add_argument("--being-draft", action=argparse.BooleanOptionalAction, default=True)
|
|
276
|
+
portal_get.add_argument(
|
|
277
|
+
"--schema-kind",
|
|
278
|
+
choices=("summary", "complete", "raw"),
|
|
279
|
+
default="summary",
|
|
280
|
+
help="summary/complete=canonical 声明投影(重放提取锦点);raw=原始归一响应",
|
|
281
|
+
)
|
|
282
|
+
portal_get.add_argument("--schema-version")
|
|
266
283
|
portal_get.set_defaults(handler=_handle_portal_get, format_hint="builder_summary")
|
|
267
284
|
|
|
285
|
+
portal_validate = portal_subparsers.add_parser("validate", help="只校验门户声明,不访问后端")
|
|
286
|
+
portal_validate.add_argument("--file", required=True, help="门户声明 JSON 文件,- 表示 stdin")
|
|
287
|
+
portal_validate.add_argument("--schema-version", help="固定使用的门户 Schema 版本;缺省读文件内 schema_version,再缺省 latest")
|
|
288
|
+
portal_validate.set_defaults(handler=_handle_portal_validate, format_hint="builder_summary", force_json_output=True)
|
|
289
|
+
|
|
268
290
|
portal_apply = portal_subparsers.add_parser("apply", help="更新门户")
|
|
291
|
+
portal_apply.add_argument("--file", help="声明文件主形态(完整 portal 声明 JSON,支持 - stdin);与 patch 参数互斥")
|
|
292
|
+
portal_apply.add_argument("--schema-version", help="声明使用的 Schema 版本;缺省读文件内 schema_version,再缺省 latest")
|
|
269
293
|
portal_apply.add_argument("--dash-key", default="")
|
|
270
294
|
portal_apply.add_argument("--dash-name", default="")
|
|
271
295
|
portal_apply.add_argument("--package-id", type=int)
|
|
272
|
-
portal_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=
|
|
273
|
-
portal_apply.add_argument("--payload-file")
|
|
296
|
+
portal_apply.add_argument("--publish", action=argparse.BooleanOptionalAction, default=None, help="发布开关;--file 声明模式下缺省尊重声明内 publish 字段(再缺省 true),显式传参时覆盖声明值")
|
|
297
|
+
portal_apply.add_argument("--payload-file", help="兼容:完整门户 payload JSON(裸 DTO 透传);新代码改用 --file")
|
|
274
298
|
portal_apply.add_argument("--sections-file")
|
|
275
299
|
portal_apply.add_argument("--layout-preset", default="")
|
|
276
300
|
portal_apply.add_argument("--visibility-file")
|
|
@@ -981,8 +1005,36 @@ def _handle_portal_list(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
981
1005
|
return context.builder.portal_list(profile=args.profile)
|
|
982
1006
|
|
|
983
1007
|
|
|
1008
|
+
def _handle_portal_schema(args: argparse.Namespace, context: CliContext) -> dict:
|
|
1009
|
+
return context.builder.portal_schema(
|
|
1010
|
+
schema_version=getattr(args, "schema_version", None),
|
|
1011
|
+
schema_kind=getattr(args, "schema_kind", None),
|
|
1012
|
+
)
|
|
1013
|
+
|
|
1014
|
+
|
|
1015
|
+
def _handle_portal_validate(args: argparse.Namespace, context: CliContext) -> dict:
|
|
1016
|
+
payload = load_json_value(
|
|
1017
|
+
args.file,
|
|
1018
|
+
option_name="--file",
|
|
1019
|
+
max_bytes=2 * 1024 * 1024,
|
|
1020
|
+
max_depth=64,
|
|
1021
|
+
)
|
|
1022
|
+
if not isinstance(payload, dict):
|
|
1023
|
+
raise_config_error("builder portal validate --file must contain a JSON object")
|
|
1024
|
+
return context.builder.portal_validate(
|
|
1025
|
+
declaration=payload,
|
|
1026
|
+
schema_version=getattr(args, "schema_version", None),
|
|
1027
|
+
)
|
|
1028
|
+
|
|
1029
|
+
|
|
984
1030
|
def _handle_portal_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
985
|
-
return context.builder.portal_get(
|
|
1031
|
+
return context.builder.portal_get(
|
|
1032
|
+
profile=args.profile,
|
|
1033
|
+
dash_key=args.dash_key,
|
|
1034
|
+
being_draft=bool(args.being_draft),
|
|
1035
|
+
schema_kind=getattr(args, "schema_kind", "summary") or "summary",
|
|
1036
|
+
schema_version=getattr(args, "schema_version", None),
|
|
1037
|
+
)
|
|
986
1038
|
|
|
987
1039
|
|
|
988
1040
|
def _handle_view_get(args: argparse.Namespace, context: CliContext) -> dict:
|
|
@@ -1385,6 +1437,62 @@ def _handle_charts_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
1385
1437
|
|
|
1386
1438
|
|
|
1387
1439
|
def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
1440
|
+
file_declaration = (
|
|
1441
|
+
load_json_value(args.file, option_name="--file", max_bytes=4 * 1024 * 1024, max_depth=64)
|
|
1442
|
+
if getattr(args, "file", None)
|
|
1443
|
+
else None
|
|
1444
|
+
)
|
|
1445
|
+
file_schema_version: str | None = None
|
|
1446
|
+
legacy_args_used = False
|
|
1447
|
+
if file_declaration is not None:
|
|
1448
|
+
if not isinstance(file_declaration, dict):
|
|
1449
|
+
raise_config_error("builder portal apply --file must contain a JSON object")
|
|
1450
|
+
legacy_keys = (
|
|
1451
|
+
getattr(args, "sections_file", None),
|
|
1452
|
+
getattr(args, "patch_sections_file", None),
|
|
1453
|
+
getattr(args, "visibility_file", None),
|
|
1454
|
+
getattr(args, "auth_file", None),
|
|
1455
|
+
getattr(args, "dash_global_config_file", None),
|
|
1456
|
+
getattr(args, "config_file", None),
|
|
1457
|
+
getattr(args, "payload_file", None),
|
|
1458
|
+
getattr(args, "icon", None),
|
|
1459
|
+
getattr(args, "color", None),
|
|
1460
|
+
(getattr(args, "dash_name", "") or ""),
|
|
1461
|
+
(getattr(args, "layout_preset", "") or ""),
|
|
1462
|
+
)
|
|
1463
|
+
if any(bool(item) for item in legacy_keys):
|
|
1464
|
+
raise_config_error(
|
|
1465
|
+
"builder portal apply --file cannot be combined with patch parameters.",
|
|
1466
|
+
fix_hint="Put the full declaration in the --file JSON; keep only --schema-version/--publish/--dash-key flags.",
|
|
1467
|
+
error_code="ARGUMENT_ERROR",
|
|
1468
|
+
)
|
|
1469
|
+
if getattr(args, "package_id", None) is not None:
|
|
1470
|
+
file_declaration.setdefault("package_tag_id", args.package_id)
|
|
1471
|
+
if getattr(args, "dash_key", ""):
|
|
1472
|
+
file_declaration["dash_key"] = args.dash_key
|
|
1473
|
+
# publish 三态对齐 MCP(声明值优先、显式 flag 最高):声明内 publish 弹出后由 flag 解析——
|
|
1474
|
+
# flag 未传时尊重声明值(含幂等重放的 publish:false),声明也未写时才缺省 true。
|
|
1475
|
+
flag_publish = getattr(args, "publish", None)
|
|
1476
|
+
declaration_publish = file_declaration.pop("publish", None)
|
|
1477
|
+
effective_publish = (
|
|
1478
|
+
bool(flag_publish)
|
|
1479
|
+
if flag_publish is not None
|
|
1480
|
+
else (bool(declaration_publish) if declaration_publish is not None else True)
|
|
1481
|
+
)
|
|
1482
|
+
file_schema_version = str(file_declaration.pop("schema_version", None) or file_declaration.pop("schemaVersion", None) or "").strip() or None
|
|
1483
|
+
effective_schema_version = getattr(args, "schema_version", None) or file_schema_version
|
|
1484
|
+
return context.builder.portal_apply(
|
|
1485
|
+
profile=args.profile,
|
|
1486
|
+
payload=file_declaration,
|
|
1487
|
+
schema_version=effective_schema_version,
|
|
1488
|
+
publish=effective_publish,
|
|
1489
|
+
)
|
|
1490
|
+
legacy_args_used = True
|
|
1491
|
+
if legacy_args_used:
|
|
1492
|
+
print(
|
|
1493
|
+
"[deprecation] builder portal apply patch parameters are in a compatibility window; prefer `--file <declaration.json>`",
|
|
1494
|
+
file=sys.stderr,
|
|
1495
|
+
)
|
|
1388
1496
|
payload = load_object_arg(args.payload_file, option_name="--payload-file") if getattr(args, "payload_file", None) else None
|
|
1389
1497
|
payload_obj = payload if isinstance(payload, dict) else {}
|
|
1390
1498
|
effective_dash_name = (args.dash_name or str(payload_obj.get("dash_name") or payload_obj.get("dashName") or payload_obj.get("name") or "")).strip()
|
|
@@ -1413,7 +1521,7 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
1413
1521
|
dash_key=args.dash_key,
|
|
1414
1522
|
dash_name=args.dash_name,
|
|
1415
1523
|
package_id=args.package_id,
|
|
1416
|
-
publish=bool(args.publish),
|
|
1524
|
+
publish=bool(args.publish if args.publish is not None else True),
|
|
1417
1525
|
sections=sections,
|
|
1418
1526
|
layout_preset=args.layout_preset,
|
|
1419
1527
|
visibility=load_object_arg(args.visibility_file, option_name="--visibility-file"),
|
|
@@ -1424,6 +1532,7 @@ def _handle_portal_apply(args: argparse.Namespace, context: CliContext) -> dict:
|
|
|
1424
1532
|
dash_global_config=load_object_arg(args.dash_global_config_file, option_name="--dash-global-config-file"),
|
|
1425
1533
|
config=load_object_arg(args.config_file, option_name="--config-file"),
|
|
1426
1534
|
payload=payload,
|
|
1535
|
+
schema_version=getattr(args, "schema_version", None),
|
|
1427
1536
|
patch_sections=patch_sections,
|
|
1428
1537
|
)
|
|
1429
1538
|
|
|
@@ -184,6 +184,8 @@ BUILDER_PUBLIC_TOOL_SPECS: tuple[PublicToolSpec, ...] = (
|
|
|
184
184
|
PublicToolSpec(BUILDER_DOMAIN, "app_views_get", ("app_views_get",), ("builder", "views", "get"), cli_show_effective_context=True),
|
|
185
185
|
PublicToolSpec(BUILDER_DOMAIN, "app_views_validate", (), ("builder", "views", "validate"), mcp_public=True),
|
|
186
186
|
PublicToolSpec(BUILDER_DOMAIN, "app_views_apply", ("app_views_apply",), ("builder", "views", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
187
|
+
PublicToolSpec(BUILDER_DOMAIN, "portal_schema", (), ("builder", "portal", "schema"), mcp_public=True),
|
|
188
|
+
PublicToolSpec(BUILDER_DOMAIN, "portal_validate", (), ("builder", "portal", "validate"), mcp_public=True),
|
|
187
189
|
PublicToolSpec(BUILDER_DOMAIN, "app_charts_apply", ("app_charts_apply",), ("builder", "charts", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
188
190
|
PublicToolSpec(BUILDER_DOMAIN, "portal_apply", ("portal_apply",), ("builder", "portal", "apply"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
189
191
|
PublicToolSpec(BUILDER_DOMAIN, "portal_delete", ("portal_delete",), ("builder", "portal", "delete"), has_contract=True, cli_show_effective_context=True, cli_context_write=True),
|
|
@@ -482,13 +482,36 @@ def build_builder_server() -> PolicyAwareFastMCP:
|
|
|
482
482
|
def portal_list(profile: str = DEFAULT_PROFILE) -> dict:
|
|
483
483
|
return ai_builder.portal_list(profile=profile)
|
|
484
484
|
|
|
485
|
+
@server.tool()
|
|
486
|
+
def portal_schema(
|
|
487
|
+
schema_version: str | None = None,
|
|
488
|
+
schema_kind: str | None = None,
|
|
489
|
+
) -> dict:
|
|
490
|
+
"""Get a complete portals schema or a focused component schema such as chart, view, grid, or carousel."""
|
|
491
|
+
return ai_builder.portal_schema(schema_version=schema_version, schema_kind=schema_kind)
|
|
492
|
+
|
|
493
|
+
@server.tool()
|
|
494
|
+
def portal_validate(
|
|
495
|
+
declaration: dict | None = None,
|
|
496
|
+
schema_version: str | None = None,
|
|
497
|
+
) -> dict:
|
|
498
|
+
return ai_builder.portal_validate(declaration=declaration or {}, schema_version=schema_version)
|
|
499
|
+
|
|
485
500
|
@server.tool()
|
|
486
501
|
def portal_get(
|
|
487
502
|
profile: str = DEFAULT_PROFILE,
|
|
488
503
|
dash_key: str = "",
|
|
489
504
|
being_draft: bool = True,
|
|
505
|
+
schema_kind: str = "summary",
|
|
506
|
+
schema_version: str | None = None,
|
|
490
507
|
) -> dict:
|
|
491
|
-
return ai_builder.portal_get(
|
|
508
|
+
return ai_builder.portal_get(
|
|
509
|
+
profile=profile,
|
|
510
|
+
dash_key=dash_key,
|
|
511
|
+
being_draft=being_draft,
|
|
512
|
+
schema_kind=schema_kind,
|
|
513
|
+
schema_version=schema_version,
|
|
514
|
+
)
|
|
492
515
|
|
|
493
516
|
@server.tool()
|
|
494
517
|
def view_get(profile: str = DEFAULT_PROFILE, view_key: str = "") -> dict:
|
|
@@ -624,6 +647,10 @@ def build_builder_server() -> PolicyAwareFastMCP:
|
|
|
624
647
|
color: str | None = None,
|
|
625
648
|
hide_copyright: bool | None = None,
|
|
626
649
|
dash_global_config: dict | None = None,
|
|
650
|
+
global_config: dict | None = None,
|
|
651
|
+
style_template_id: int | None = None,
|
|
652
|
+
overall_style: dict | None = None,
|
|
653
|
+
schema_version: str | None = None,
|
|
627
654
|
config: dict | None = None,
|
|
628
655
|
payload: dict | None = None,
|
|
629
656
|
patch_sections: list[dict] | None = None,
|
|
@@ -660,6 +687,10 @@ def build_builder_server() -> PolicyAwareFastMCP:
|
|
|
660
687
|
color=color,
|
|
661
688
|
hide_copyright=hide_copyright,
|
|
662
689
|
dash_global_config=dash_global_config,
|
|
690
|
+
global_config=global_config,
|
|
691
|
+
style_template_id=style_template_id,
|
|
692
|
+
overall_style=overall_style,
|
|
693
|
+
schema_version=schema_version,
|
|
663
694
|
config=config or {},
|
|
664
695
|
payload=payload,
|
|
665
696
|
patch_sections=patch_sections,
|
|
@@ -312,6 +312,13 @@ def parse_workspace_icon(value: str | None) -> tuple[str | None, str | None, str
|
|
|
312
312
|
stripped = str(value).strip()
|
|
313
313
|
if not stripped:
|
|
314
314
|
return None, None, None
|
|
315
|
+
if _looks_like_icon_url(stripped):
|
|
316
|
+
# URL 形态旧图标(如 https://…/dashboard_security.png)不是目录图标:目录名与
|
|
317
|
+
# 色均无法表达。返回 (None, None, None) 而不是按关键字把 URL 归一成假目录名
|
|
318
|
+
# ('dashboard' 命中 → 'view-grid')——后者经编译重编码会销毁存量 URL 图标,
|
|
319
|
+
# 并派生出不存在的默认色(r5 实测 50 门户 color diff 根因)。声明面按「未表达」
|
|
320
|
+
# 处理(读面 gap 记账);更新语义 apply 不触碰未指定图标,URL 原样存活。
|
|
321
|
+
return None, None, None
|
|
315
322
|
if _looks_like_icon_json(stripped):
|
|
316
323
|
try:
|
|
317
324
|
payload = json.loads(stripped)
|
|
@@ -324,6 +331,11 @@ def parse_workspace_icon(value: str | None) -> tuple[str | None, str | None, str
|
|
|
324
331
|
return normalize_workspace_icon_name(stripped), None, None
|
|
325
332
|
|
|
326
333
|
|
|
334
|
+
def _looks_like_icon_url(value: str) -> bool:
|
|
335
|
+
lowered = value.strip().lower()
|
|
336
|
+
return lowered.startswith(("http://", "https://", "//"))
|
|
337
|
+
|
|
338
|
+
|
|
327
339
|
def workspace_icon_config(value: str | None) -> dict[str, str | None]:
|
|
328
340
|
icon_name, icon_color, icon_text = parse_workspace_icon(value)
|
|
329
341
|
raw = str(value).strip() if value not in (None, "") else None
|
|
@@ -442,6 +454,67 @@ def encode_workspace_icon_with_defaults(
|
|
|
442
454
|
)
|
|
443
455
|
|
|
444
456
|
|
|
457
|
+
def update_workspace_icon_from_existing(
|
|
458
|
+
*,
|
|
459
|
+
icon: str | None,
|
|
460
|
+
color: str | None,
|
|
461
|
+
title: str,
|
|
462
|
+
existing_icon: str | None,
|
|
463
|
+
fallback_icon_name: str | None = None,
|
|
464
|
+
) -> str:
|
|
465
|
+
"""更新语义的 dashIcon 重编码(round-trip 保真)。
|
|
466
|
+
|
|
467
|
+
后端 updateDashBaseInfo 对 dashIcon 无校验直接透传落库;生产存量存在三类非目录形态:
|
|
468
|
+
文字图标(iconName 空 + iconText=名字首字)、旧版 URL 图标(icon 键携带 URL)、
|
|
469
|
+
空(iconName/iconText 均空)。encode_workspace_icon_with_defaults 的 fallback 语义
|
|
470
|
+
(为新建资源合成目录图标)会把「声明 icon 未指定」的存量形态重写为 fallback 图标。
|
|
471
|
+
|
|
472
|
+
更新语义规则:
|
|
473
|
+
- 声明 icon 显式指定且与存量 iconName 归一后一致 → JSON 级最小 patch:仅当声明
|
|
474
|
+
显式指定了不同 color 才改写 iconColor,否则存量 JSON 原串透传(byte 级保真);
|
|
475
|
+
iconText 等非目录形态键始终保留——生产存量存在 iconName+iconText 同设形态
|
|
476
|
+
(r7 全量实测 48 门),全量重编码会把 iconText 清成 null。
|
|
477
|
+
- 声明 icon 显式指定且为其他图标 → 走 encode(用户意图覆写);
|
|
478
|
+
- 声明 icon 未指定且无存量 → 走 encode_with_defaults(创建/首次合成);
|
|
479
|
+
- 声明 icon 未指定且有存量:
|
|
480
|
+
- color 也未指定 → 原样保留存量 JSON;
|
|
481
|
+
- color 指定 → JSON 级最小 patch(仅替换 iconColor,iconName/iconText/icon URL
|
|
482
|
+
等存量形态键原样保留)。
|
|
483
|
+
"""
|
|
484
|
+
existing_payload = str(existing_icon or "").strip()
|
|
485
|
+
if existing_payload and _looks_like_icon_json(existing_payload):
|
|
486
|
+
try:
|
|
487
|
+
parsed = json.loads(existing_payload)
|
|
488
|
+
except ValueError:
|
|
489
|
+
parsed = None
|
|
490
|
+
if isinstance(parsed, dict):
|
|
491
|
+
existing_name, existing_color, _ = parse_workspace_icon(existing_payload)
|
|
492
|
+
if icon is None:
|
|
493
|
+
if color is None:
|
|
494
|
+
return existing_payload
|
|
495
|
+
patched = dict(parsed)
|
|
496
|
+
patched["iconColor"] = color
|
|
497
|
+
return json.dumps(patched, ensure_ascii=False, separators=(",", ":"))
|
|
498
|
+
if (
|
|
499
|
+
existing_name is not None
|
|
500
|
+
and not _looks_like_icon_json(icon)
|
|
501
|
+
and not _looks_like_icon_url(icon)
|
|
502
|
+
and normalize_workspace_icon_name(icon) == existing_name
|
|
503
|
+
):
|
|
504
|
+
if color is None or color == existing_color:
|
|
505
|
+
return existing_payload
|
|
506
|
+
patched = dict(parsed)
|
|
507
|
+
patched["iconColor"] = color
|
|
508
|
+
return json.dumps(patched, ensure_ascii=False, separators=(",", ":"))
|
|
509
|
+
return encode_workspace_icon_with_defaults(
|
|
510
|
+
icon=icon,
|
|
511
|
+
color=color,
|
|
512
|
+
title=title,
|
|
513
|
+
fallback_icon_name=fallback_icon_name,
|
|
514
|
+
existing_icon=existing_icon,
|
|
515
|
+
)
|
|
516
|
+
|
|
517
|
+
|
|
445
518
|
def normalize_workspace_icon_name(icon: str | None) -> str | None:
|
|
446
519
|
if not icon:
|
|
447
520
|
return None
|
|
@@ -10,6 +10,7 @@ from ..tools.app_tools import AppTools
|
|
|
10
10
|
from ..tools.navigation_tools import NavigationTools
|
|
11
11
|
from ..tools.package_tools import PackageTools
|
|
12
12
|
from ..tools.portal_tools import PortalTools
|
|
13
|
+
from ..tools.portal_writer import PortalWriter
|
|
13
14
|
from ..tools.qingbi_report_tools import QingbiReportTools
|
|
14
15
|
from ..tools.record_tools import RecordTools
|
|
15
16
|
from ..tools.role_tools import RoleTools
|
|
@@ -39,6 +40,7 @@ class SolutionExecutor:
|
|
|
39
40
|
view_writer: "ViewWriter | None" = None,
|
|
40
41
|
chart_tools: QingbiReportTools,
|
|
41
42
|
portal_tools: PortalTools,
|
|
43
|
+
portal_writer: "PortalWriter | None" = None,
|
|
42
44
|
navigation_tools: NavigationTools,
|
|
43
45
|
workflow_tools: Any | None = None,
|
|
44
46
|
) -> None:
|
|
@@ -51,6 +53,7 @@ class SolutionExecutor:
|
|
|
51
53
|
self.view_writer = view_writer
|
|
52
54
|
self.chart_tools = chart_tools
|
|
53
55
|
self.portal_tools = portal_tools
|
|
56
|
+
self.portal_writer = portal_writer or PortalWriter(portal_tools.sessions, portal_tools.backend)
|
|
54
57
|
self.navigation_tools = navigation_tools
|
|
55
58
|
self.workflow_tools = workflow_tools
|
|
56
59
|
|
|
@@ -624,7 +627,7 @@ class SolutionExecutor:
|
|
|
624
627
|
result = portal_artifact
|
|
625
628
|
if not dash_key:
|
|
626
629
|
create_payload = self._resolve_portal_payload(compiled.portal_plan["create_payload"], store, base_payload=None)
|
|
627
|
-
result = self.
|
|
630
|
+
result = self.portal_writer.create_portal(profile=profile, payload=create_payload)
|
|
628
631
|
portal_result = result.get("result") or {}
|
|
629
632
|
dash_key = portal_result.get("dashKey")
|
|
630
633
|
store.set_artifact("portal", "result", result)
|
|
@@ -655,7 +658,7 @@ class SolutionExecutor:
|
|
|
655
658
|
),
|
|
656
659
|
)
|
|
657
660
|
update_payload = self._resolve_portal_payload(compiled.portal_plan["update_payload"], store, base_payload=base_payload)
|
|
658
|
-
self.
|
|
661
|
+
self.portal_writer.update_portal(profile=profile, dash_key=dash_key, payload=update_payload)
|
|
659
662
|
self._refresh_portal_artifact(profile=profile, store=store, being_draft=True, artifact_key="draft_result")
|
|
660
663
|
|
|
661
664
|
def _build_navigation(self, profile: str, compiled: CompiledSolution, store: RunArtifactStore) -> None:
|
|
@@ -699,13 +699,36 @@ class AiBuilderTools(ToolBase):
|
|
|
699
699
|
def portal_list(profile: str = DEFAULT_PROFILE) -> JSONObject:
|
|
700
700
|
return self.portal_list(profile=profile)
|
|
701
701
|
|
|
702
|
+
@mcp.tool()
|
|
703
|
+
def portal_schema(
|
|
704
|
+
schema_version: str | None = None,
|
|
705
|
+
schema_kind: str | None = None,
|
|
706
|
+
) -> JSONObject:
|
|
707
|
+
"""Get a complete portals schema or a focused component schema such as chart, view, grid, or carousel."""
|
|
708
|
+
return self.portal_schema(schema_version=schema_version, schema_kind=schema_kind)
|
|
709
|
+
|
|
710
|
+
@mcp.tool()
|
|
711
|
+
def portal_validate(
|
|
712
|
+
declaration: JSONObject | None = None,
|
|
713
|
+
schema_version: str | None = None,
|
|
714
|
+
) -> JSONObject:
|
|
715
|
+
return self.portal_validate(declaration=declaration or {}, schema_version=schema_version)
|
|
716
|
+
|
|
702
717
|
@mcp.tool()
|
|
703
718
|
def portal_get(
|
|
704
719
|
profile: str = DEFAULT_PROFILE,
|
|
705
720
|
dash_key: str = "",
|
|
706
721
|
being_draft: bool = True,
|
|
722
|
+
schema_kind: str = "summary",
|
|
723
|
+
schema_version: str | None = None,
|
|
707
724
|
) -> JSONObject:
|
|
708
|
-
return self.portal_get(
|
|
725
|
+
return self.portal_get(
|
|
726
|
+
profile=profile,
|
|
727
|
+
dash_key=dash_key,
|
|
728
|
+
being_draft=being_draft,
|
|
729
|
+
schema_kind=schema_kind,
|
|
730
|
+
schema_version=schema_version,
|
|
731
|
+
)
|
|
709
732
|
|
|
710
733
|
@mcp.tool()
|
|
711
734
|
def view_get(profile: str = DEFAULT_PROFILE, view_key: str = "") -> JSONObject:
|
|
@@ -845,6 +868,10 @@ class AiBuilderTools(ToolBase):
|
|
|
845
868
|
color: str | None = None,
|
|
846
869
|
hide_copyright: bool | None = None,
|
|
847
870
|
dash_global_config: JSONObject | None = None,
|
|
871
|
+
global_config: JSONObject | None = None,
|
|
872
|
+
style_template_id: int | None = None,
|
|
873
|
+
overall_style: JSONObject | None = None,
|
|
874
|
+
schema_version: str | None = None,
|
|
848
875
|
config: JSONObject | None = None,
|
|
849
876
|
payload: JSONObject | None = None,
|
|
850
877
|
patch_sections: list[JSONObject] | None = None,
|
|
@@ -883,6 +910,10 @@ class AiBuilderTools(ToolBase):
|
|
|
883
910
|
color=color,
|
|
884
911
|
hide_copyright=hide_copyright,
|
|
885
912
|
dash_global_config=dash_global_config,
|
|
913
|
+
global_config=global_config,
|
|
914
|
+
style_template_id=style_template_id,
|
|
915
|
+
overall_style=overall_style,
|
|
916
|
+
schema_version=schema_version,
|
|
886
917
|
config=config or {},
|
|
887
918
|
payload=payload,
|
|
888
919
|
patch_sections=patch_sections,
|
|
@@ -2052,16 +2083,58 @@ class AiBuilderTools(ToolBase):
|
|
|
2052
2083
|
suggested_next_call={"tool_name": "portal_list", "arguments": {"profile": profile}},
|
|
2053
2084
|
))
|
|
2054
2085
|
|
|
2055
|
-
def portal_get(
|
|
2056
|
-
|
|
2057
|
-
|
|
2086
|
+
def portal_get(
|
|
2087
|
+
self,
|
|
2088
|
+
*,
|
|
2089
|
+
profile: str,
|
|
2090
|
+
dash_key: str,
|
|
2091
|
+
being_draft: bool = True,
|
|
2092
|
+
schema_kind: str = "summary",
|
|
2093
|
+
schema_version: str | None = None,
|
|
2094
|
+
) -> JSONObject:
|
|
2095
|
+
"""读取门户详情;schema_kind=summary|complete 返回 canonical 声明,raw 为原始归一响应。"""
|
|
2096
|
+
normalized_args = {"dash_key": dash_key, "being_draft": being_draft, "schema_kind": schema_kind, "schema_version": schema_version}
|
|
2058
2097
|
return _publicize_package_fields(_safe_tool_call(
|
|
2059
|
-
lambda: self._facade.portal_get(
|
|
2098
|
+
lambda: self._facade.portal_get(
|
|
2099
|
+
profile=profile,
|
|
2100
|
+
dash_key=dash_key,
|
|
2101
|
+
being_draft=being_draft,
|
|
2102
|
+
schema_kind=schema_kind,
|
|
2103
|
+
schema_version=schema_version,
|
|
2104
|
+
),
|
|
2060
2105
|
error_code="PORTAL_GET_FAILED",
|
|
2061
2106
|
normalized_args=normalized_args,
|
|
2062
2107
|
suggested_next_call={"tool_name": "portal_get", "arguments": {"profile": profile, **normalized_args}},
|
|
2063
2108
|
))
|
|
2064
2109
|
|
|
2110
|
+
def portal_schema(
|
|
2111
|
+
self,
|
|
2112
|
+
*,
|
|
2113
|
+
schema_version: str | None = None,
|
|
2114
|
+
schema_kind: str | None = None,
|
|
2115
|
+
) -> JSONObject:
|
|
2116
|
+
"""读取版本化门户声明 schema(纯离线)。"""
|
|
2117
|
+
return _publicize_package_fields(_safe_tool_call(
|
|
2118
|
+
lambda: self._facade.portal_schema(schema_version=schema_version, schema_kind=schema_kind),
|
|
2119
|
+
error_code="PORTAL_SCHEMA_FAILED",
|
|
2120
|
+
normalized_args={"schema_version": schema_version, "schema_kind": schema_kind},
|
|
2121
|
+
suggested_next_call={"tool_name": "portal_schema", "arguments": {"schema_kind": "summary"}},
|
|
2122
|
+
))
|
|
2123
|
+
|
|
2124
|
+
def portal_validate(
|
|
2125
|
+
self,
|
|
2126
|
+
*,
|
|
2127
|
+
declaration: JSONObject,
|
|
2128
|
+
schema_version: str | None = None,
|
|
2129
|
+
) -> JSONObject:
|
|
2130
|
+
"""纯离线校验一条门户声明(不访问后端)。"""
|
|
2131
|
+
return _publicize_package_fields(_safe_tool_call(
|
|
2132
|
+
lambda: self._facade.portal_validate(declaration=declaration, schema_version=schema_version),
|
|
2133
|
+
error_code="PORTAL_VALIDATE_FAILED",
|
|
2134
|
+
normalized_args={"schema_version": schema_version},
|
|
2135
|
+
suggested_next_call={"tool_name": "portal_schema", "arguments": {"schema_kind": "complete"}},
|
|
2136
|
+
))
|
|
2137
|
+
|
|
2065
2138
|
def portal_read_summary(self, *, profile: str, dash_key: str, being_draft: bool = True) -> JSONObject:
|
|
2066
2139
|
"""执行门户相关逻辑。"""
|
|
2067
2140
|
normalized_args = {"dash_key": dash_key, "being_draft": being_draft}
|
|
@@ -4233,6 +4306,10 @@ class AiBuilderTools(ToolBase):
|
|
|
4233
4306
|
color: str | None = None,
|
|
4234
4307
|
hide_copyright: bool | None = None,
|
|
4235
4308
|
dash_global_config: JSONObject | None = None,
|
|
4309
|
+
global_config: JSONObject | None = None,
|
|
4310
|
+
style_template_id: int | None = None,
|
|
4311
|
+
overall_style: JSONObject | None = None,
|
|
4312
|
+
schema_version: str | None = None,
|
|
4236
4313
|
config: JSONObject | None = None,
|
|
4237
4314
|
payload: JSONObject | None = None,
|
|
4238
4315
|
patch_sections: list[JSONObject] | None = None,
|
|
@@ -4284,6 +4361,14 @@ class AiBuilderTools(ToolBase):
|
|
|
4284
4361
|
request_payload["hide_copyright"] = hide_copyright
|
|
4285
4362
|
if dash_global_config is not None:
|
|
4286
4363
|
request_payload["dash_global_config"] = dash_global_config
|
|
4364
|
+
if global_config is not None:
|
|
4365
|
+
request_payload["global_config"] = global_config
|
|
4366
|
+
if style_template_id is not None:
|
|
4367
|
+
request_payload["style_template_id"] = style_template_id
|
|
4368
|
+
if overall_style is not None:
|
|
4369
|
+
request_payload["overall_style"] = overall_style
|
|
4370
|
+
if schema_version is not None:
|
|
4371
|
+
request_payload["schema_version"] = schema_version
|
|
4287
4372
|
if config:
|
|
4288
4373
|
merged_config = dict(request_payload.get("config") or {}) if isinstance(request_payload.get("config"), dict) else {}
|
|
4289
4374
|
merged_config.update(config)
|
|
@@ -4315,16 +4400,24 @@ class AiBuilderTools(ToolBase):
|
|
|
4315
4400
|
))
|
|
4316
4401
|
normalized_args = request.model_dump(mode="json")
|
|
4317
4402
|
normalized_args["package_id"] = normalized_args.pop("package_tag_id", package_id)
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4403
|
+
portal_creating = not bool(str(request.dash_key or "").strip())
|
|
4404
|
+
# icon/color 目录校验仅对创建语义生效(CLI 防呆,后端 createDash 亦无校验):
|
|
4405
|
+
# 更新语义(dash_key 定位)后端 updateDashBaseInfo 对 dashIcon 无目录校验直接
|
|
4406
|
+
# 透传落库,生产存量存在旧版 hex 色值(#13c2c2 等)——提取透传的重放声明会被
|
|
4407
|
+
# 目录校验拒之门外(WORKSPACE_ICON_COLOR_NOT_FOUND),故更新语义不做目录校验。
|
|
4408
|
+
if portal_creating:
|
|
4409
|
+
icon_failure = _validate_workspace_icon_for_builder(
|
|
4410
|
+
tool_name="portal_apply",
|
|
4411
|
+
icon=str(request.icon or ""),
|
|
4412
|
+
color=str(request.color or ""),
|
|
4413
|
+
creating=True,
|
|
4414
|
+
)
|
|
4415
|
+
else:
|
|
4416
|
+
icon_failure = None
|
|
4324
4417
|
if icon_failure is not None:
|
|
4325
4418
|
return self._attach_apply_envelope(profile, "portal_apply", icon_failure)
|
|
4326
4419
|
result = _publicize_package_fields(_safe_tool_call(
|
|
4327
|
-
lambda: self._facade.portal_apply(profile=profile, request=request),
|
|
4420
|
+
lambda: self._facade.portal_apply(profile=profile, request=request, schema_version=schema_version),
|
|
4328
4421
|
error_code="PORTAL_APPLY_FAILED",
|
|
4329
4422
|
normalized_args=normalized_args,
|
|
4330
4423
|
suggested_next_call={"tool_name": "portal_apply", "arguments": {"profile": profile, **normalized_args}},
|
|
@@ -5738,6 +5831,11 @@ def _publicize_package_fields(value):
|
|
|
5738
5831
|
}
|
|
5739
5832
|
public: JSONObject = {}
|
|
5740
5833
|
for key, item in value.items():
|
|
5834
|
+
if key == "declaration":
|
|
5835
|
+
# canonical 声明保持 schema 契约命名(package_tag_id 等),不做公开名重命名,
|
|
5836
|
+
# 否则 get 投影的 declaration 无法直接回喂 validate/apply(round-trip 破坏)。
|
|
5837
|
+
public[key] = item
|
|
5838
|
+
continue
|
|
5741
5839
|
public_key = key_map.get(key, key)
|
|
5742
5840
|
public[public_key] = _publicize_package_fields(item)
|
|
5743
5841
|
return public
|
|
@@ -14,8 +14,10 @@ class PortalTools(ToolBase):
|
|
|
14
14
|
类型:门户配置工具。
|
|
15
15
|
主要职责:
|
|
16
16
|
1. 查询门户列表与详情;
|
|
17
|
-
2.
|
|
18
|
-
|
|
17
|
+
2. 执行门户发布与删除操作。
|
|
18
|
+
|
|
19
|
+
创建/更新写入已下沉 ``PortalWriter``(tools/portal_writer.py),
|
|
20
|
+
公开层唯一入口是 ``builder portal apply`` / ``portal_apply``。
|
|
19
21
|
"""
|
|
20
22
|
|
|
21
23
|
def register(self, mcp: FastMCP) -> None:
|
|
@@ -32,18 +34,6 @@ class PortalTools(ToolBase):
|
|
|
32
34
|
def portal_get_base_info(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> JSONObject:
|
|
33
35
|
return self.portal_get_base_info(profile=profile, dash_key=dash_key)
|
|
34
36
|
|
|
35
|
-
@mcp.tool()
|
|
36
|
-
def portal_create(profile: str = DEFAULT_PROFILE, payload: JSONObject | None = None) -> JSONObject:
|
|
37
|
-
return self.portal_create(profile=profile, payload=payload or {})
|
|
38
|
-
|
|
39
|
-
@mcp.tool(description=self._high_risk_tool_description(operation="update", target="portal base settings"))
|
|
40
|
-
def portal_update_base_info(profile: str = DEFAULT_PROFILE, dash_key: str = "", payload: JSONObject | None = None) -> JSONObject:
|
|
41
|
-
return self.portal_update_base_info(profile=profile, dash_key=dash_key, payload=payload or {})
|
|
42
|
-
|
|
43
|
-
@mcp.tool(description=self._high_risk_tool_description(operation="update", target="portal configuration"))
|
|
44
|
-
def portal_update(profile: str = DEFAULT_PROFILE, dash_key: str = "", payload: JSONObject | None = None) -> JSONObject:
|
|
45
|
-
return self.portal_update(profile=profile, dash_key=dash_key, payload=payload or {})
|
|
46
|
-
|
|
47
37
|
@mcp.tool(description=self._high_risk_tool_description(operation="delete", target="portal configuration"))
|
|
48
38
|
def portal_delete(profile: str = DEFAULT_PROFILE, dash_key: str = "") -> JSONObject:
|
|
49
39
|
return self.portal_delete(profile=profile, dash_key=dash_key)
|
|
@@ -70,16 +60,6 @@ class PortalTools(ToolBase):
|
|
|
70
60
|
|
|
71
61
|
return self._run(profile, runner, tool_name='门户详情')
|
|
72
62
|
|
|
73
|
-
def portal_create(self, *, profile: str, payload: JSONObject) -> JSONObject:
|
|
74
|
-
"""执行门户相关逻辑。"""
|
|
75
|
-
body = self._require_dict(payload)
|
|
76
|
-
|
|
77
|
-
def runner(session_profile, context):
|
|
78
|
-
result = self.backend.request("POST", context, "/dash", json_body=body)
|
|
79
|
-
return {"profile": profile, "ws_id": session_profile.selected_ws_id, "result": result}
|
|
80
|
-
|
|
81
|
-
return self._run(profile, runner, tool_name='创建门户')
|
|
82
|
-
|
|
83
63
|
def portal_get_base_info(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
84
64
|
"""执行门户相关逻辑。"""
|
|
85
65
|
self._require_dash_key(dash_key)
|
|
@@ -119,36 +99,6 @@ class PortalTools(ToolBase):
|
|
|
119
99
|
|
|
120
100
|
return self._run(profile, runner, tool_name='门户基础信息')
|
|
121
101
|
|
|
122
|
-
def portal_update_base_info(self, *, profile: str, dash_key: str, payload: JSONObject) -> JSONObject:
|
|
123
|
-
"""执行门户相关逻辑。"""
|
|
124
|
-
self._require_dash_key(dash_key)
|
|
125
|
-
body = self._require_dict(payload)
|
|
126
|
-
|
|
127
|
-
def runner(session_profile, context):
|
|
128
|
-
result = self.backend.request("POST", context, f"/dash/{dash_key}/baseInfo", json_body=body)
|
|
129
|
-
return self._attach_human_review_notice(
|
|
130
|
-
{"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result},
|
|
131
|
-
operation="update",
|
|
132
|
-
target="portal base settings",
|
|
133
|
-
)
|
|
134
|
-
|
|
135
|
-
return self._run(profile, runner, tool_name='更新门户基础信息')
|
|
136
|
-
|
|
137
|
-
def portal_update(self, *, profile: str, dash_key: str, payload: JSONObject) -> JSONObject:
|
|
138
|
-
"""执行门户相关逻辑。"""
|
|
139
|
-
self._require_dash_key(dash_key)
|
|
140
|
-
body = self._require_dict(payload)
|
|
141
|
-
|
|
142
|
-
def runner(session_profile, context):
|
|
143
|
-
result = self.backend.request("POST", context, f"/dash/{dash_key}", json_body=body)
|
|
144
|
-
return self._attach_human_review_notice(
|
|
145
|
-
{"profile": profile, "ws_id": session_profile.selected_ws_id, "dash_key": dash_key, "result": result},
|
|
146
|
-
operation="update",
|
|
147
|
-
target="portal configuration",
|
|
148
|
-
)
|
|
149
|
-
|
|
150
|
-
return self._run(profile, runner, tool_name='更新门户配置')
|
|
151
|
-
|
|
152
102
|
def portal_delete(self, *, profile: str, dash_key: str) -> JSONObject:
|
|
153
103
|
"""执行门户相关逻辑。"""
|
|
154
104
|
self._require_dash_key(dash_key)
|