已合并
fix:manager和runtime的表字段对齐 #534
王欣创建于 24 天前
fix:manager和runtime的表字段对齐 #534
已合并
共 65 个文件变更+1510-1519
| @@ -76,7 +76,7 @@ uv run --no-sync python scripts/load_test.py --base-url http://127.0.0.1:30091/a | |||
| 76 | ## 环境 | 76 | ## 环境 |
| 77 | 77 | ||
| 78 | - Python 3.11–3.13;Redis 须开 AOF/RDB(单实例/哨兵 `redis://`,**Redis Cluster 用 `redis+cluster://`**,键前缀已 hash tag 同槽化,见 `docs/feature/2026-08-redis-cluster.md`;cluster 无库号,URL 禁带 `/N`);DB 用 MySQL/PostgreSQL(禁 SQLite 回退——server 模式;local 模式调试可用)。 | 78 | - Python 3.11–3.13;Redis 须开 AOF/RDB(单实例/哨兵 `redis://`,**Redis Cluster 用 `redis+cluster://`**,键前缀已 hash tag 同槽化,见 `docs/feature/2026-08-redis-cluster.md`;cluster 无库号,URL 禁带 `/N`);DB 用 MySQL/PostgreSQL(禁 SQLite 回退——server 模式;local 模式调试可用)。 |
| 79 | -- **存量库升级:历次 schema 变更须先按 `docs/feature/` 对应篇目手工 ALTER 再发版**(框架建表只 create_all 不补列)——容器表拆分三列(`2026-08-container-table-split.md`)、routing_scope `enabled`/`expires_at`(`2026-09-routing-scope-enabled-expires.md`)、模板表策略四列 RENAME(`2026-09-template-table-runtime-terms.md`);具体 SQL 见各篇。 | 79 | +- **存量库升级:历次 schema 变更须先按 `docs/feature/` 对应篇目手工 ALTER 再发版**(框架建表只 create_all 不补列/不 DROP)——容器表拆分三列(`2026-08-container-table-split.md`)、routing_scope `enabled`/`expires_at`(`2026-09-routing-scope-enabled-expires.md`)、模板表策略四列 RENAME(`2026-09-template-table-runtime-terms.md`)、**legacy 内联容器列 DROP**(`2026-09-drop-legacy-inline-template-columns.md`)、**data 三元组对齐**(`2026-09-data-triad-alignment.md`);具体 SQL 见各篇。 |
| 80 | - 框架:`service/openjiuwen_runtime/service`(App/Envelope/SystemContext);App 范式参考 `applications/echo/echo_server.py`(**不是** a2a_service)。 | 80 | - 框架:`service/openjiuwen_runtime/service`(App/Envelope/SystemContext);App 范式参考 `applications/echo/echo_server.py`(**不是** a2a_service)。 |
| 81 | - 部署:`applications/agent_runtime/scripts/deploy.sh local|server`(server 读 `.env.production.local`)。 | 81 | - 部署:`applications/agent_runtime/scripts/deploy.sh local|server`(server 读 `.env.production.local`)。 |
| 82 | - 本仓库是嵌套 git 仓库(独立于外层 jiuwenclaw),提交分开做。 | 82 | - 本仓库是嵌套 git 仓库(独立于外层 jiuwenclaw),提交分开做。 |
| @@ -49,6 +49,7 @@ LINK_BINDING_STATE_TABLE_DEF = TableDefinition( | |||
| 49 | ColumnDefinition( | 49 | ColumnDefinition( |
| 50 | "status", "string", length=32, nullable=False, default="active" | 50 | "status", "string", length=32, nullable=False, default="active" |
| 51 | ), | 51 | ), |
| 52 | + ColumnDefinition("data", "json", nullable=True), | ||
| 52 | ColumnDefinition("created_at", "datetime", nullable=False), | 53 | ColumnDefinition("created_at", "datetime", nullable=False), |
| 53 | ColumnDefinition("updated_at", "datetime", nullable=False), | 54 | ColumnDefinition("updated_at", "datetime", nullable=False), |
| 54 | ], | 55 | ], |
| @@ -111,7 +112,7 @@ async def sync_local_link_binding_state(db: Any, config: LinkMTLSConfig) -> Any | |||
| 111 | if row is None: | 112 | if row is None: |
| 112 | return await db.create( | 113 | return await db.create( |
| 113 | LINK_BINDING_STATE_TABLE, | 114 | LINK_BINDING_STATE_TABLE, |
| 114 | - {"created_at": now, **payload}, | 115 | + {"created_at": now, "data": None, **payload}, |
| 115 | ) | 116 | ) |
| 116 | previous_epoch = int(getattr(row, "mtls_binding_epoch", 0) or 0) | 117 | previous_epoch = int(getattr(row, "mtls_binding_epoch", 0) or 0) |
| 117 | filters = {"service_role": LOCAL_SERVICE_ROLE} | 118 | filters = {"service_role": LOCAL_SERVICE_ROLE} |
| @@ -76,6 +76,7 @@ SERVICE_CONFIG_CONTAINER_TABLE_DEF = TableDefinition( | |||
| 76 | ColumnDefinition("command", "json", nullable=True), | 76 | ColumnDefinition("command", "json", nullable=True), |
| 77 | ColumnDefinition("args", "json", nullable=True), | 77 | ColumnDefinition("args", "json", nullable=True), |
| 78 | ColumnDefinition("readiness_probe", "json", nullable=True), | 78 | ColumnDefinition("readiness_probe", "json", nullable=True), |
| 79 | + ColumnDefinition("data", "json", nullable=True), | ||
| 79 | ColumnDefinition("created_at", "datetime", nullable=False), | 80 | ColumnDefinition("created_at", "datetime", nullable=False), |
| 80 | ColumnDefinition("updated_at", "datetime", nullable=False), | 81 | ColumnDefinition("updated_at", "datetime", nullable=False), |
| 81 | ], | 82 | ], |
| @@ -783,6 +784,8 @@ def container_row_from_spec(spec: dict[str, Any]) -> dict[str, Any]: | |||
| 783 | } | 784 | } |
| 784 | for column in _CONTAINER_SECTION_COLUMNS: | 785 | for column in _CONTAINER_SECTION_COLUMNS: |
| 785 | row[column] = spec.get(column) | 786 | row[column] = spec.get(column) |
| 787 | + data = spec.get("data") | ||
| 788 | + row["data"] = data if isinstance(data, dict) else None | ||
| 786 | return row | 789 | return row |
| 787 | 790 | ||
| 788 | 791 | ||
| @@ -292,7 +292,7 @@ MANAGER_WEB_GATEWAY_WS_TARGET=http://127.0.0.1:19000 | |||
| 292 | MANAGER_WEB_RESOLVER=kube-dns.kube-system.svc.cluster.local | 292 | MANAGER_WEB_RESOLVER=kube-dns.kube-system.svc.cluster.local |
| 293 | ``` | 293 | ``` |
| 294 | 294 | ||
| 295 | -生产 nginx 将 `/chat`、`/ws`、`/gateway-api`、`/file-api`、`/share-api` 按 Cookie `jiuwenclaw_id` 动态反代到各实例 `instance_info.data` 中的 `user_web_host` / `gateway_web_*_host`(空则回退上述 `MANAGER_WEB_*_TARGET`)。`MANAGER_WEB_RESOLVER` 供变量 `proxy_pass` 做 DNS 解析(集群DNS的ClusterIP或完整服务名)。 | 295 | +生产 nginx 将 `/chat`、`/ws`、`/gateway-api`、`/file-api`、`/share-api` 按 Cookie `jiuwenclaw_id` 动态反代到各实例 `instance_info.user_web_host` 与 `instance_info.data` 中的 `gateway_web_*_host`(空则回退上述 `MANAGER_WEB_*_TARGET`)。`MANAGER_WEB_RESOLVER` 供变量 `proxy_pass` 做 DNS 解析(集群DNS的ClusterIP或完整服务名)。 |
| 296 | 296 | ||
| 297 | 完整变量列表见 `applications/manager/.env.example`。 | 297 | 完整变量列表见 `applications/manager/.env.example`。 |
| 298 | --- | 298 | --- |
Mapplications/manager/manager_server/scripts/enterprise_config_demo_data_config.py+36-19文件内容审核中,请稍后刷新重试
| @@ -97,15 +97,15 @@ async def check_config_host_alive( | |||
| 97 | 97 | ||
| 98 | async def require_config_hosts_reachable( | 98 | async def require_config_hosts_reachable( |
| 99 | *, | 99 | *, |
| 100 | - gateway_config_host: str | None = None, | 100 | + gateway_host: str | None = None, |
| 101 | - runtime_config_host: str | None = None, | 101 | + runtime_host: str | None = None, |
| 102 | user_web_host: str | None = None, | 102 | user_web_host: str | None = None, |
| 103 | timeout: float = _DEFAULT_TIMEOUT, | 103 | timeout: float = _DEFAULT_TIMEOUT, |
| 104 | ) -> None: | 104 | ) -> None: |
| 105 | """对非空的 Gateway / Runtime / User Web host 依次探活;任一失败即抛错。""" | 105 | """对非空的 Gateway / Runtime / User Web host 依次探活;任一失败即抛错。""" |
| 106 | - if gateway_config_host: | 106 | + if gateway_host: |
| 107 | - await probe_config_host(gateway_config_host, side="gateway", timeout=timeout) | 107 | + await probe_config_host(gateway_host, side="gateway", timeout=timeout) |
| 108 | - if runtime_config_host: | 108 | + if runtime_host: |
| 109 | - await probe_config_host(runtime_config_host, side="runtime", timeout=timeout) | 109 | + await probe_config_host(runtime_host, side="runtime", timeout=timeout) |
| 110 | if user_web_host: | 110 | if user_web_host: |
| 111 | await probe_config_host(user_web_host, side="user_web", timeout=timeout) | 111 | await probe_config_host(user_web_host, side="user_web", timeout=timeout) |
Mapplications/manager/manager_server/src/manager_server/core/instance/instance_data_lifecycle.py+2-2
| @@ -218,7 +218,7 @@ async def purge_manager_instance_data( | |||
| 218 | 218 | ||
| 219 | 219 | ||
| 220 | async def _purge_gateway_via_http(jiuwenclaw_id: str) -> bool: | 220 | async def _purge_gateway_via_http(jiuwenclaw_id: str) -> bool: |
| 221 | - """有 gateway_config_host 则 HTTP purge;否则返回 False(不抛错)。""" | 221 | + """有 gateway_host 则 HTTP purge;否则返回 False(不抛错)。""" |
| 222 | jid = str(jiuwenclaw_id or "").strip() | 222 | jid = str(jiuwenclaw_id or "").strip() |
| 223 | if not jid: | 223 | if not jid: |
| 224 | return False | 224 | return False |
| @@ -263,7 +263,7 @@ async def purge_runtime_instance_data( | |||
| 263 | 263 | ||
| 264 | Runtime 只有全量快照替换,无按实例 purge API;空投影 | 264 | Runtime 只有全量快照替换,无按实例 purge API;空投影 |
| 265 | ``{containers:[], templates:[], scopes:[]}`` 即删除该实例此前下发的配置。 | 265 | ``{containers:[], templates:[], scopes:[]}`` 即删除该实例此前下发的配置。 |
| 266 | - 无 ``runtime_config_host``(且无全局回退)或推送失败时跳过(不抛错)。 | 266 | + 无 ``runtime_host``(且无全局回退)或推送失败时跳过(不抛错)。 |
| 267 | """ | 267 | """ |
| 268 | from manager_server.core.instance_resource.runtime_config_sync import ( | 268 | from manager_server.core.instance_resource.runtime_config_sync import ( |
| 269 | sync_runtime_config, | 269 | sync_runtime_config, |
| @@ -26,14 +26,16 @@ logger = logging.getLogger(__name__) | |||
| 26 | 26 | ||
| 27 | _INSTANCE_TABLE = INSTANCE_INFO_TABLE_DEF.table_name | 27 | _INSTANCE_TABLE = INSTANCE_INFO_TABLE_DEF.table_name |
| 28 | _LOG_MASKING_SEEDED_KEY = "log_masking_seeded" | 28 | _LOG_MASKING_SEEDED_KEY = "log_masking_seeded" |
| 29 | -# 用户面反代上游存在 instance_info.data 中(非独立列) | 29 | +# 仍留在 instance_info.data 中的用户面反代上游(user_web_* 已独立成列) |
| 30 | _USER_FACE_HOST_KEYS = ( | 30 | _USER_FACE_HOST_KEYS = ( |
| 31 | - "user_web_host", | ||
| 32 | "gateway_web_http_host", | 31 | "gateway_web_http_host", |
| 33 | "gateway_web_ws_host", | 32 | "gateway_web_ws_host", |
| 34 | ) | 33 | ) |
| 35 | -_USER_WEB_STATUS_KEY = "user_web_status" | 34 | +_PROMOTED_USER_WEB_KEYS = ( |
| 36 | -_USER_WEB_LAST_ALIVE_KEY = "user_web_last_alive" | 35 | + "user_web_host", |
| 36 | + "user_web_status", | ||
| 37 | + "user_web_last_alive", | ||
| 38 | +) | ||
| 37 | 39 | ||
| 38 | ServiceSide = Literal["gateway", "runtime", "user_web"] | 40 | ServiceSide = Literal["gateway", "runtime", "user_web"] |
| 39 | 41 | ||
| @@ -44,13 +46,10 @@ _DB_INSTANCE_SORT_FIELDS = frozenset({ | |||
| 44 | "gateway_last_alive", | 46 | "gateway_last_alive", |
| 45 | "runtime_status", | 47 | "runtime_status", |
| 46 | "runtime_last_alive", | 48 | "runtime_last_alive", |
| 47 | - "updated_at", | ||
| 48 | -}) | ||
| 49 | -_MEMORY_INSTANCE_SORT_FIELDS = frozenset({ | ||
| 50 | "user_web_status", | 49 | "user_web_status", |
| 51 | "user_web_last_alive", | 50 | "user_web_last_alive", |
| 51 | + "updated_at", | ||
| 52 | }) | 52 | }) |
| 53 | -_ALLOWED_INSTANCE_SORT_FIELDS = _DB_INSTANCE_SORT_FIELDS | _MEMORY_INSTANCE_SORT_FIELDS | ||
| 54 | _DEFAULT_INSTANCE_ORDER_BY: list[tuple[str, bool]] = [("updated_at", True)] | 53 | _DEFAULT_INSTANCE_ORDER_BY: list[tuple[str, bool]] = [("updated_at", True)] |
| 55 | 54 | ||
| 56 | _MAX_JIUWENCLAW_ID_ATTEMPTS = 10 | 55 | _MAX_JIUWENCLAW_ID_ATTEMPTS = 10 |
| @@ -200,16 +199,12 @@ def _matches_instance_search(row: Any, query: str) -> bool: | |||
| 200 | str(getattr(row, "jiuwenclaw_name", "") or ""), | 199 | str(getattr(row, "jiuwenclaw_name", "") or ""), |
| 201 | str(getattr(row, "gateway_status", "") or ""), | 200 | str(getattr(row, "gateway_status", "") or ""), |
| 202 | str(getattr(row, "runtime_status", "") or ""), | 201 | str(getattr(row, "runtime_status", "") or ""), |
| 203 | - str(_user_web_status_from_data(_instance_data_dict(row)) or ""), | 202 | + str(getattr(row, "user_web_status", "") or ""), |
| 204 | str(getattr(row, "namespace", "") or ""), | 203 | str(getattr(row, "namespace", "") or ""), |
| 205 | ] | 204 | ] |
| 206 | return any(needle in field.lower() for field in fields) | 205 | return any(needle in field.lower() for field in fields) |
| 207 | 206 | ||
| 208 | 207 | ||
| 209 | -def _matches_user_web_status(row: Any, status: str) -> bool: | ||
| 210 | - return _user_web_status_from_data(_instance_data_dict(row)) == status | ||
| 211 | - | ||
| 212 | - | ||
| 213 | def _instance_row_to_summary(row: Any) -> dict: | 208 | def _instance_row_to_summary(row: Any) -> dict: |
| 214 | data = _instance_data_dict(row) | 209 | data = _instance_data_dict(row) |
| 215 | face = _user_face_hosts_from_data(data) | 210 | face = _user_face_hosts_from_data(data) |
| @@ -218,19 +213,22 @@ def _instance_row_to_summary(row: Any) -> dict: | |||
| 218 | jiuwenclaw_name=row.jiuwenclaw_name, | 213 | jiuwenclaw_name=row.jiuwenclaw_name, |
| 219 | namespace=getattr(row, "namespace", None) or "default", | 214 | namespace=getattr(row, "namespace", None) or "default", |
| 220 | space_id=row.space_id, | 215 | space_id=row.space_id, |
| 221 | - gateway_config_host=str(getattr(row, "gateway_config_host", "") or ""), | 216 | + gateway_host=str(getattr(row, "gateway_host", "") or ""), |
| 222 | gateway_status=row.gateway_status, | 217 | gateway_status=row.gateway_status, |
| 223 | gateway_last_alive=iso_datetime( | 218 | gateway_last_alive=iso_datetime( |
| 224 | getattr(row, "gateway_last_alive", None) | 219 | getattr(row, "gateway_last_alive", None) |
| 225 | ), | 220 | ), |
| 226 | - runtime_config_host=str(getattr(row, "runtime_config_host", "") or ""), | 221 | + runtime_host=str(getattr(row, "runtime_host", "") or ""), |
| 227 | runtime_status=row.runtime_status, | 222 | runtime_status=row.runtime_status, |
| 228 | runtime_last_alive=iso_datetime( | 223 | runtime_last_alive=iso_datetime( |
| 229 | getattr(row, "runtime_last_alive", None) | 224 | getattr(row, "runtime_last_alive", None) |
| 230 | ), | 225 | ), |
| 231 | - user_web_status=_user_web_status_from_data(data), | 226 | + user_web_host=_norm_host(getattr(row, "user_web_host", None)), |
| 232 | - user_web_last_alive=_user_web_last_alive_from_data(data), | 227 | + user_web_status=str(getattr(row, "user_web_status", None) or "pending").strip() |
| 233 | - user_web_host=face["user_web_host"], | 228 | + or "pending", |
| 229 | + user_web_last_alive=iso_datetime( | ||
| 230 | + getattr(row, "user_web_last_alive", None) | ||
| 231 | + ), | ||
| 234 | gateway_web_http_host=face["gateway_web_http_host"], | 232 | gateway_web_http_host=face["gateway_web_http_host"], |
| 235 | gateway_web_ws_host=face["gateway_web_ws_host"], | 233 | gateway_web_ws_host=face["gateway_web_ws_host"], |
| 236 | created_at=iso_datetime(row.created_at), | 234 | created_at=iso_datetime(row.created_at), |
| @@ -254,19 +252,12 @@ def _instance_data_dict(row: Any | None) -> dict[str, Any]: | |||
| 254 | return dict(data) if isinstance(data, dict) else {} | 252 | return dict(data) if isinstance(data, dict) else {} |
| 255 | 253 | ||
| 256 | 254 | ||
| 257 | -def _user_web_status_from_data(data: dict[str, Any] | None) -> str: | 255 | +def _strip_promoted_user_web_keys(data: dict[str, Any]) -> dict[str, Any]: |
| 258 | - src = data if isinstance(data, dict) else {} | 256 | + """避免已提列字段继续滞留在 data JSON。""" |
| 259 | - return str(src.get(_USER_WEB_STATUS_KEY) or "pending").strip() or "pending" | 257 | + cleaned = dict(data) |
| 260 | - | 258 | + for key in _PROMOTED_USER_WEB_KEYS: |
| 261 | - | 259 | + cleaned.pop(key, None) |
| 262 | -def _user_web_last_alive_from_data(data: dict[str, Any] | None) -> str | None: | 260 | + return cleaned |
| 263 | - src = data if isinstance(data, dict) else {} | ||
| 264 | - raw = src.get(_USER_WEB_LAST_ALIVE_KEY) | ||
| 265 | - if raw is None or raw == "": | ||
| 266 | - return None | ||
| 267 | - if isinstance(raw, str): | ||
| 268 | - return raw.strip() or None | ||
| 269 | - return iso_datetime(raw) | ||
| 270 | 261 | ||
| 271 | 262 | ||
| 272 | def _user_face_hosts_from_data(data: dict[str, Any] | None) -> dict[str, str | None]: | 263 | def _user_face_hosts_from_data(data: dict[str, Any] | None) -> dict[str, str | None]: |
| @@ -278,8 +269,8 @@ def _merge_user_face_hosts_into_data( | |||
| 278 | data: dict[str, Any] | None, | 269 | data: dict[str, Any] | None, |
| 279 | hosts: dict[str, Any], | 270 | hosts: dict[str, Any], |
| 280 | ) -> dict[str, Any]: | 271 | ) -> dict[str, Any]: |
| 281 | - """把用户面 host 写入 data;空字符串删除对应键。""" | 272 | + """把 gateway_web_* host 写入 data;空字符串删除对应键。""" |
| 282 | - merged = dict(data or {}) | 273 | + merged = _strip_promoted_user_web_keys(dict(data or {})) |
| 283 | for key in _USER_FACE_HOST_KEYS: | 274 | for key in _USER_FACE_HOST_KEYS: |
| 284 | if key not in hosts: | 275 | if key not in hosts: |
| 285 | continue | 276 | continue |
| @@ -291,24 +282,6 @@ def _merge_user_face_hosts_into_data( | |||
| 291 | return merged | 282 | return merged |
| 292 | 283 | ||
| 293 | 284 | ||
| 294 | -def _sort_rows_by_user_web( | ||
| 295 | - rows: list[Any], | ||
| 296 | - *, | ||
| 297 | - sort_by: str, | ||
| 298 | - sort_order: str | None, | ||
| 299 | -) -> list[Any]: | ||
| 300 | - reverse = str(sort_order or "asc").strip().lower() == "desc" | ||
| 301 | - | ||
| 302 | - def key(row: Any) -> tuple: | ||
| 303 | - data = _instance_data_dict(row) | ||
| 304 | - if sort_by == "user_web_status": | ||
| 305 | - return (_user_web_status_from_data(data).lower(),) | ||
| 306 | - alive = _user_web_last_alive_from_data(data) or "" | ||
| 307 | - return (alive,) | ||
| 308 | - | ||
| 309 | - return sorted(rows, key=key, reverse=reverse) | ||
| 310 | - | ||
| 311 | - | ||
| 312 | async def is_log_masking_seeded(handler: DBHandler, jiuwenclaw_id: str) -> bool: | 285 | async def is_log_masking_seeded(handler: DBHandler, jiuwenclaw_id: str) -> bool: |
| 313 | """``instance_info.data.log_masking_seeded`` 为真时表示 builtin 种子已执行过。""" | 286 | """``instance_info.data.log_masking_seeded`` 为真时表示 builtin 种子已执行过。""" |
| 314 | jid = str(jiuwenclaw_id or "").strip() | 287 | jid = str(jiuwenclaw_id or "").strip() |
| @@ -328,6 +301,9 @@ async def create_instance_row(handler: DBHandler, row_data: dict[str, Any]) -> A | |||
| 328 | payload.setdefault("gateway_last_alive", None) | 301 | payload.setdefault("gateway_last_alive", None) |
| 329 | payload.setdefault("runtime_status", "pending") | 302 | payload.setdefault("runtime_status", "pending") |
| 330 | payload.setdefault("runtime_last_alive", None) | 303 | payload.setdefault("runtime_last_alive", None) |
| 304 | + payload.setdefault("user_web_host", "") | ||
| 305 | + payload.setdefault("user_web_status", "pending") | ||
| 306 | + payload.setdefault("user_web_last_alive", None) | ||
| 331 | payload.setdefault("space_id", "default") | 307 | payload.setdefault("space_id", "default") |
| 332 | return await handler.create(_INSTANCE_TABLE, payload) | 308 | return await handler.create(_INSTANCE_TABLE, payload) |
| 333 | 309 | ||
| @@ -425,7 +401,7 @@ async def maybe_full_sync_gateway_on_online( | |||
| 425 | await require_gateway_endpoint(jid) | 401 | await require_gateway_endpoint(jid) |
| 426 | except ValueError: | 402 | except ValueError: |
| 427 | logger.info( | 403 | logger.info( |
| 428 | - "[Instance] skip full sync on online: no gateway_config_host " | 404 | + "[Instance] skip full sync on online: no gateway_host " |
| 429 | "jiuwenclaw_id=%s prev_status=%s", | 405 | "jiuwenclaw_id=%s prev_status=%s", |
| 430 | jid, | 406 | jid, |
| 431 | prev, | 407 | prev, |
| @@ -511,7 +487,6 @@ async def apply_health_probe_result( | |||
| 511 | - alive:置 online,刷新 last_alive;Gateway / Runtime 从 pending/offline → online | 487 | - alive:置 online,刷新 last_alive;Gateway / Runtime 从 pending/offline → online |
| 512 | 时各自全量下发(Gateway → WS bootstrap;Runtime → config_sync) | 488 | 时各自全量下发(Gateway → WS bootstrap;Runtime → config_sync) |
| 513 | - 失败:仅当当前为 online 时置 offline;pending 保持不变 | 489 | - 失败:仅当当前为 online 时置 offline;pending 保持不变 |
| 514 | - - user_web:状态写入 ``instance_info.data``(非独立列) | ||
| 515 | """ | 490 | """ |
| 516 | jid = str(jiuwenclaw_id or "").strip() | 491 | jid = str(jiuwenclaw_id or "").strip() |
| 517 | if not jid: | 492 | if not jid: |
| @@ -522,36 +497,6 @@ async def apply_health_probe_result( | |||
| 522 | side = _resolve_service_side(service_type) | 497 | side = _resolve_service_side(service_type) |
| 523 | now = utc_now() | 498 | now = utc_now() |
| 524 | 499 | ||
| 525 | - if side == "user_web": | ||
| 526 | - data = _instance_data_dict(row) | ||
| 527 | - prev_status = _user_web_status_from_data(data) | ||
| 528 | - if alive: | ||
| 529 | - data[_USER_WEB_STATUS_KEY] = "online" | ||
| 530 | - data[_USER_WEB_LAST_ALIVE_KEY] = iso_datetime(now) | ||
| 531 | - await handler.update( | ||
| 532 | - _INSTANCE_TABLE, | ||
| 533 | - {"jiuwenclaw_id": jid}, | ||
| 534 | - { | ||
| 535 | - "data": data, | ||
| 536 | - "updated_at": now, | ||
| 537 | - "updated_by": "health-probe", | ||
| 538 | - }, | ||
| 539 | - ) | ||
| 540 | - return True | ||
| 541 | - if prev_status == "online": | ||
| 542 | - data[_USER_WEB_STATUS_KEY] = "offline" | ||
| 543 | - await handler.update( | ||
| 544 | - _INSTANCE_TABLE, | ||
| 545 | - {"jiuwenclaw_id": jid}, | ||
| 546 | - { | ||
| 547 | - "data": data, | ||
| 548 | - "updated_at": now, | ||
| 549 | - "updated_by": "health-probe", | ||
| 550 | - }, | ||
| 551 | - ) | ||
| 552 | - return True | ||
| 553 | - return False | ||
| 554 | - | ||
| 555 | status_key = f"{side}_status" | 500 | status_key = f"{side}_status" |
| 556 | prev_status = str(getattr(row, status_key, "") or "") | 501 | prev_status = str(getattr(row, status_key, "") or "") |
| 557 | 502 | ||
| @@ -611,6 +556,7 @@ async def list_instance_rows( | |||
| 611 | *, | 556 | *, |
| 612 | gateway_status: str | None = None, | 557 | gateway_status: str | None = None, |
| 613 | runtime_status: str | None = None, | 558 | runtime_status: str | None = None, |
| 559 | + user_web_status: str | None = None, | ||
| 614 | offset: int, | 560 | offset: int, |
| 615 | limit: int, | 561 | limit: int, |
| 616 | sort_by: str | None = None, | 562 | sort_by: str | None = None, |
| @@ -621,6 +567,8 @@ async def list_instance_rows( | |||
| 621 | filters["gateway_status"] = gateway_status | 567 | filters["gateway_status"] = gateway_status |
| 622 | if runtime_status: | 568 | if runtime_status: |
| 623 | filters["runtime_status"] = runtime_status | 569 | filters["runtime_status"] = runtime_status |
| 570 | + if user_web_status: | ||
| 571 | + filters["user_web_status"] = user_web_status | ||
| 624 | total = await handler.count_records(_INSTANCE_TABLE, filters) | 572 | total = await handler.count_records(_INSTANCE_TABLE, filters) |
| 625 | db_sort = sort_by if sort_by in _DB_INSTANCE_SORT_FIELDS else None | 573 | db_sort = sort_by if sort_by in _DB_INSTANCE_SORT_FIELDS else None |
| 626 | order_by = resolve_order_by( | 574 | order_by = resolve_order_by( |
| @@ -653,8 +601,9 @@ async def merge_instance_data( | |||
| 653 | row = await get_instance_row(handler, jiuwenclaw_id) | 601 | row = await get_instance_row(handler, jiuwenclaw_id) |
| 654 | if row is None: | 602 | if row is None: |
| 655 | return None | 603 | return None |
| 656 | - merged = dict(getattr(row, "data", None) or {}) | 604 | + merged = _strip_promoted_user_web_keys(dict(getattr(row, "data", None) or {})) |
| 657 | merged.update(patch) | 605 | merged.update(patch) |
| 606 | + merged = _strip_promoted_user_web_keys(merged) | ||
| 658 | now = utc_now() | 607 | now = utc_now() |
| 659 | return await handler.update( | 608 | return await handler.update( |
| 660 | _INSTANCE_TABLE, | 609 | _INSTANCE_TABLE, |
| @@ -669,12 +618,12 @@ class InstanceService: | |||
| 669 | 618 | ||
| 670 | async def create(self, body: CreateInstanceBody) -> dict: | 619 | async def create(self, body: CreateInstanceBody) -> dict: |
| 671 | jiuwenclaw_id = await generate_unique_jiuwenclaw_id(self._handler) | 620 | jiuwenclaw_id = await generate_unique_jiuwenclaw_id(self._handler) |
| 672 | - gateway_host = _norm_host(body.gateway_config_host) | 621 | + gateway_host = _norm_host(body.gateway_host) |
| 673 | - runtime_host = _norm_host(body.runtime_config_host) | 622 | + runtime_host = _norm_host(body.runtime_host) |
| 674 | if not gateway_host: | 623 | if not gateway_host: |
| 675 | - raise ValueError("gateway_config_host is required") | 624 | + raise ValueError("gateway_host is required") |
| 676 | if not runtime_host: | 625 | if not runtime_host: |
| 677 | - raise ValueError("runtime_config_host is required") | 626 | + raise ValueError("runtime_host is required") |
| 678 | user_web_host = _norm_host(body.user_web_host) | 627 | user_web_host = _norm_host(body.user_web_host) |
| 679 | gateway_web_http_host = _norm_host(body.gateway_web_http_host) | 628 | gateway_web_http_host = _norm_host(body.gateway_web_http_host) |
| 680 | if not user_web_host: | 629 | if not user_web_host: |
| @@ -684,15 +633,19 @@ class InstanceService: | |||
| 684 | await _assert_config_host_available( | 633 | await _assert_config_host_available( |
| 685 | self._handler, | 634 | self._handler, |
| 686 | gateway_host, | 635 | gateway_host, |
| 687 | - column="gateway_config_host", | 636 | + column="gateway_host", |
| 688 | ) | 637 | ) |
| 689 | await _assert_config_host_available( | 638 | await _assert_config_host_available( |
| 690 | self._handler, | 639 | self._handler, |
| 691 | runtime_host, | 640 | runtime_host, |
| 692 | - column="runtime_config_host", | 641 | + column="runtime_host", |
| 642 | + ) | ||
| 643 | + await _assert_config_host_available( | ||
| 644 | + self._handler, | ||
| 645 | + user_web_host, | ||
| 646 | + column="user_web_host", | ||
| 693 | ) | 647 | ) |
| 694 | face_hosts = { | 648 | face_hosts = { |
| 695 | - "user_web_host": user_web_host, | ||
| 696 | "gateway_web_http_host": gateway_web_http_host, | 649 | "gateway_web_http_host": gateway_web_http_host, |
| 697 | "gateway_web_ws_host": body.gateway_web_ws_host, | 650 | "gateway_web_ws_host": body.gateway_web_ws_host, |
| 698 | } | 651 | } |
| @@ -701,10 +654,9 @@ class InstanceService: | |||
| 701 | dict(body.data) if isinstance(body.data, dict) else {}, | 654 | dict(body.data) if isinstance(body.data, dict) else {}, |
| 702 | face_hosts, | 655 | face_hosts, |
| 703 | ) | 656 | ) |
| 704 | - data.setdefault(_USER_WEB_STATUS_KEY, "pending") | ||
| 705 | await require_config_hosts_reachable( | 657 | await require_config_hosts_reachable( |
| 706 | - gateway_config_host=gateway_host, | 658 | + gateway_host=gateway_host, |
| 707 | - runtime_config_host=runtime_host, | 659 | + runtime_host=runtime_host, |
| 708 | user_web_host=user_web_host, | 660 | user_web_host=user_web_host, |
| 709 | ) | 661 | ) |
| 710 | namespace = _norm_namespace(body.namespace) | 662 | namespace = _norm_namespace(body.namespace) |
| @@ -713,10 +665,12 @@ class InstanceService: | |||
| 713 | "jiuwenclaw_name": body.jiuwenclaw_name.strip(), | 665 | "jiuwenclaw_name": body.jiuwenclaw_name.strip(), |
| 714 | "description": body.description, | 666 | "description": body.description, |
| 715 | "namespace": namespace, | 667 | "namespace": namespace, |
| 716 | - "gateway_config_host": gateway_host, | 668 | + "gateway_host": gateway_host, |
| 717 | "gateway_status": "pending", | 669 | "gateway_status": "pending", |
| 718 | - "runtime_config_host": runtime_host, | 670 | + "runtime_host": runtime_host, |
| 719 | "runtime_status": "pending", | 671 | "runtime_status": "pending", |
| 672 | + "user_web_host": user_web_host, | ||
| 673 | + "user_web_status": "pending", | ||
| 720 | "data": data or None, | 674 | "data": data or None, |
| 721 | "space_id": (body.space_id or "default").strip() or "default", | 675 | "space_id": (body.space_id or "default").strip() or "default", |
| 722 | "created_by": (body.created_by or "system").strip() or "system", | 676 | "created_by": (body.created_by or "system").strip() or "system", |
| @@ -765,35 +719,33 @@ class InstanceService: | |||
| 765 | return { | 719 | return { |
| 766 | "jiuwenclaw_id": jiuwenclaw_id, | 720 | "jiuwenclaw_id": jiuwenclaw_id, |
| 767 | "namespace": getattr(row, "namespace", namespace), | 721 | "namespace": getattr(row, "namespace", namespace), |
| 768 | - "gateway_config_host": getattr(row, "gateway_config_host", gateway_host), | 722 | + "gateway_host": getattr(row, "gateway_host", gateway_host), |
| 769 | "gateway_status": getattr(row, "gateway_status", "pending"), | 723 | "gateway_status": getattr(row, "gateway_status", "pending"), |
| 770 | - "runtime_config_host": getattr(row, "runtime_config_host", runtime_host), | 724 | + "runtime_host": getattr(row, "runtime_host", runtime_host), |
| 771 | "runtime_status": getattr(row, "runtime_status", "pending"), | 725 | "runtime_status": getattr(row, "runtime_status", "pending"), |
| 772 | - "user_web_status": _user_web_status_from_data(_instance_data_dict(row)), | 726 | + "user_web_host": getattr(row, "user_web_host", user_web_host), |
| 727 | + "user_web_status": getattr(row, "user_web_status", "pending"), | ||
| 773 | } | 728 | } |
| 774 | 729 | ||
| 775 | async def list_instances(self, query: InstanceListQuery) -> dict: | 730 | async def list_instances(self, query: InstanceListQuery) -> dict: |
| 776 | page = max(query.page, 1) | 731 | page = max(query.page, 1) |
| 777 | page_size = min(max(query.page_size, 1), 200) | 732 | page_size = min(max(query.page_size, 1), 200) |
| 778 | search_query = (query.search or "").strip() | 733 | search_query = (query.search or "").strip() |
| 779 | - user_web_filter = str(query.user_web_status or "").strip() | 734 | + user_web_filter = str(query.user_web_status or "").strip() or None |
| 780 | sort_by = str(query.sort_by or "").strip() or None | 735 | sort_by = str(query.sort_by or "").strip() or None |
| 781 | - needs_memory = bool( | 736 | + needs_memory = bool(search_query) |
| 782 | - search_query | ||
| 783 | - or user_web_filter | ||
| 784 | - or (sort_by in _MEMORY_INSTANCE_SORT_FIELDS) | ||
| 785 | - ) | ||
| 786 | 737 | ||
| 787 | filters: dict[str, Any] = {} | 738 | filters: dict[str, Any] = {} |
| 788 | if query.gateway_status: | 739 | if query.gateway_status: |
| 789 | filters["gateway_status"] = query.gateway_status | 740 | filters["gateway_status"] = query.gateway_status |
| 790 | if query.runtime_status: | 741 | if query.runtime_status: |
| 791 | filters["runtime_status"] = query.runtime_status | 742 | filters["runtime_status"] = query.runtime_status |
| 743 | + if user_web_filter: | ||
| 744 | + filters["user_web_status"] = user_web_filter | ||
| 792 | 745 | ||
| 793 | if needs_memory: | 746 | if needs_memory: |
| 794 | - db_sort = sort_by if sort_by in _DB_INSTANCE_SORT_FIELDS else None | ||
| 795 | order_by = resolve_order_by( | 747 | order_by = resolve_order_by( |
| 796 | - db_sort, | 748 | + sort_by if sort_by in _DB_INSTANCE_SORT_FIELDS else None, |
| 797 | query.sort_order, | 749 | query.sort_order, |
| 798 | allowed_sort_fields=_DB_INSTANCE_SORT_FIELDS, | 750 | allowed_sort_fields=_DB_INSTANCE_SORT_FIELDS, |
| 799 | default_order_by=_DEFAULT_INSTANCE_ORDER_BY, | 751 | default_order_by=_DEFAULT_INSTANCE_ORDER_BY, |
| @@ -809,12 +761,6 @@ class InstanceService: | |||
| 809 | ) | 761 | ) |
| 810 | if search_query: | 762 | if search_query: |
| 811 | rows = [r for r in rows if _matches_instance_search(r, search_query)] | 763 | rows = [r for r in rows if _matches_instance_search(r, search_query)] |
| 812 | - if user_web_filter: | ||
| 813 | - rows = [r for r in rows if _matches_user_web_status(r, user_web_filter)] | ||
| 814 | - if sort_by in _MEMORY_INSTANCE_SORT_FIELDS: | ||
| 815 | - rows = _sort_rows_by_user_web( | ||
| 816 | - rows, sort_by=sort_by, sort_order=query.sort_order | ||
| 817 | - ) | ||
| 818 | total = len(rows) | 764 | total = len(rows) |
| 819 | offset = (page - 1) * page_size | 765 | offset = (page - 1) * page_size |
| 820 | page_rows = rows[offset: offset + page_size] | 766 | page_rows = rows[offset: offset + page_size] |
| @@ -830,6 +776,7 @@ class InstanceService: | |||
| 830 | self._handler, | 776 | self._handler, |
| 831 | gateway_status=query.gateway_status, | 777 | gateway_status=query.gateway_status, |
| 832 | runtime_status=query.runtime_status, | 778 | runtime_status=query.runtime_status, |
| 779 | + user_web_status=user_web_filter, | ||
| 833 | offset=offset, | 780 | offset=offset, |
| 834 | limit=page_size, | 781 | limit=page_size, |
| 835 | sort_by=sort_by, | 782 | sort_by=sort_by, |
| @@ -892,18 +839,18 @@ class InstanceService: | |||
| 892 | updates[field] = str(updates[field]).strip() | 839 | updates[field] = str(updates[field]).strip() |
| 893 | if "namespace" in updates and updates["namespace"] is not None: | 840 | if "namespace" in updates and updates["namespace"] is not None: |
| 894 | updates["namespace"] = _norm_namespace(updates["namespace"]) | 841 | updates["namespace"] = _norm_namespace(updates["namespace"]) |
| 895 | - if "gateway_config_host" in updates: | 842 | + if "gateway_host" in updates: |
| 896 | - updates["gateway_config_host"] = _norm_host(updates["gateway_config_host"]) | 843 | + updates["gateway_host"] = _norm_host(updates["gateway_host"]) |
| 897 | - if "runtime_config_host" in updates: | 844 | + if "runtime_host" in updates: |
| 898 | - updates["runtime_config_host"] = _norm_host(updates["runtime_config_host"]) | 845 | + updates["runtime_host"] = _norm_host(updates["runtime_host"]) |
| 846 | + if "user_web_host" in updates: | ||
| 847 | + updates["user_web_host"] = _norm_host(updates["user_web_host"]) | ||
| 848 | + if not updates["user_web_host"]: | ||
| 849 | + raise ValueError("user_web_host is required") | ||
| 899 | 850 | ||
| 900 | face_host_updates = { | 851 | face_host_updates = { |
| 901 | key: updates.pop(key) for key in _USER_FACE_HOST_KEYS if key in updates | 852 | key: updates.pop(key) for key in _USER_FACE_HOST_KEYS if key in updates |
| 902 | } | 853 | } |
| 903 | - if "user_web_host" in face_host_updates and not _norm_host( | ||
| 904 | - face_host_updates.get("user_web_host") | ||
| 905 | - ): | ||
| 906 | - raise ValueError("user_web_host is required") | ||
| 907 | if "gateway_web_http_host" in face_host_updates and not _norm_host( | 854 | if "gateway_web_http_host" in face_host_updates and not _norm_host( |
| 908 | face_host_updates.get("gateway_web_http_host") | 855 | face_host_updates.get("gateway_web_http_host") |
| 909 | ): | 856 | ): |
| @@ -931,49 +878,54 @@ class InstanceService: | |||
| 931 | exclude_jiuwenclaw_id=jid, | 878 | exclude_jiuwenclaw_id=jid, |
| 932 | ) | 879 | ) |
| 933 | 880 | ||
| 934 | - if "gateway_config_host" in updates: | 881 | + if "gateway_host" in updates: |
| 935 | - gateway_host = updates["gateway_config_host"] | 882 | + gateway_host = updates["gateway_host"] |
| 936 | if gateway_host and _norm_host( | 883 | if gateway_host and _norm_host( |
| 937 | - getattr(row, "gateway_config_host", None) | 884 | + getattr(row, "gateway_host", None) |
| 938 | ) != gateway_host: | 885 | ) != gateway_host: |
| 939 | await _assert_config_host_available( | 886 | await _assert_config_host_available( |
| 940 | self._handler, | 887 | self._handler, |
| 941 | gateway_host, | 888 | gateway_host, |
| 942 | - column="gateway_config_host", | 889 | + column="gateway_host", |
| 943 | exclude_jiuwenclaw_id=jid, | 890 | exclude_jiuwenclaw_id=jid, |
| 944 | ) | 891 | ) |
| 945 | - if "runtime_config_host" in updates: | 892 | + if "runtime_host" in updates: |
| 946 | - runtime_host = updates["runtime_config_host"] | 893 | + runtime_host = updates["runtime_host"] |
| 947 | if runtime_host and _norm_host( | 894 | if runtime_host and _norm_host( |
| 948 | - getattr(row, "runtime_config_host", None) | 895 | + getattr(row, "runtime_host", None) |
| 949 | ) != runtime_host: | 896 | ) != runtime_host: |
| 950 | await _assert_config_host_available( | 897 | await _assert_config_host_available( |
| 951 | self._handler, | 898 | self._handler, |
| 952 | runtime_host, | 899 | runtime_host, |
| 953 | - column="runtime_config_host", | 900 | + column="runtime_host", |
| 901 | + exclude_jiuwenclaw_id=jid, | ||
| 902 | + ) | ||
| 903 | + if "user_web_host" in updates: | ||
| 904 | + user_web_host = updates["user_web_host"] | ||
| 905 | + if user_web_host and _norm_host( | ||
| 906 | + getattr(row, "user_web_host", None) | ||
| 907 | + ) != user_web_host: | ||
| 908 | + await _assert_config_host_available( | ||
| 909 | + self._handler, | ||
| 910 | + user_web_host, | ||
| 911 | + column="user_web_host", | ||
| 954 | exclude_jiuwenclaw_id=jid, | 912 | exclude_jiuwenclaw_id=jid, |
| 955 | ) | 913 | ) |
| 956 | 914 | ||
| 957 | - user_web_probe: str | None = None | ||
| 958 | - if "user_web_host" in face_host_updates: | ||
| 959 | - merged_data = updates.get("data") | ||
| 960 | - if isinstance(merged_data, dict): | ||
| 961 | - user_web_probe = _norm_host(merged_data.get("user_web_host")) | ||
| 962 | - else: | ||
| 963 | - user_web_probe = _norm_host(face_host_updates.get("user_web_host")) | ||
| 964 | - | ||
| 965 | await require_config_hosts_reachable( | 915 | await require_config_hosts_reachable( |
| 966 | - gateway_config_host=( | 916 | + gateway_host=( |
| 967 | - updates["gateway_config_host"] | 917 | + updates["gateway_host"] |
| 968 | - if "gateway_config_host" in updates | 918 | + if "gateway_host" in updates |
| 969 | else None | 919 | else None |
| 970 | ), | 920 | ), |
| 971 | - runtime_config_host=( | 921 | + runtime_host=( |
| 972 | - updates["runtime_config_host"] | 922 | + updates["runtime_host"] |
| 973 | - if "runtime_config_host" in updates | 923 | + if "runtime_host" in updates |
| 974 | else None | 924 | else None |
| 975 | ), | 925 | ), |
| 976 | - user_web_host=user_web_probe, | 926 | + user_web_host=( |
| 927 | + updates["user_web_host"] if "user_web_host" in updates else None | ||
| 928 | + ), | ||
| 977 | ) | 929 | ) |
| 978 | 930 | ||
| 979 | updates["updated_at"] = utc_now() | 931 | updates["updated_at"] = utc_now() |
| @@ -14,7 +14,7 @@ _HEALTH_PATH = "/healthz" | |||
| 14 | 14 | ||
| 15 | 15 | ||
| 16 | async def fetch_runtime_identity_from_health( | 16 | async def fetch_runtime_identity_from_health( |
| 17 | - runtime_config_host: str, | 17 | + runtime_host: str, |
| 18 | *, | 18 | *, |
| 19 | timeout: float = _DEFAULT_TIMEOUT, | 19 | timeout: float = _DEFAULT_TIMEOUT, |
| 20 | ) -> dict[str, Any]: | 20 | ) -> dict[str, Any]: |
| @@ -22,13 +22,13 @@ async def fetch_runtime_identity_from_health( | |||
| 22 | 22 | ||
| 23 | 返回 ``{"namespace"}``(若响应体含该字段,否则 ``default``);探活失败抛 ``ValueError``。 | 23 | 返回 ``{"namespace"}``(若响应体含该字段,否则 ``default``);探活失败抛 ``ValueError``。 |
| 24 | """ | 24 | """ |
| 25 | - base = str(runtime_config_host or "").strip().rstrip("/") | 25 | + base = str(runtime_host or "").strip().rstrip("/") |
| 26 | if not base: | 26 | if not base: |
| 27 | - raise ValueError("runtime_config_host is empty") | 27 | + raise ValueError("runtime_host is empty") |
| 28 | link_mtls = ManagerLinkMTLSConfig.from_env() | 28 | link_mtls = ManagerLinkMTLSConfig.from_env() |
| 29 | base = link_mtls.resolve_endpoint(base, role="runtime") | 29 | base = link_mtls.resolve_endpoint(base, role="runtime") |
| 30 | if not base.startswith(("http://", "https://")): | 30 | if not base.startswith(("http://", "https://")): |
| 31 | - raise ValueError(f"runtime_config_host must be an http(s) URL, got {runtime_config_host!r}") | 31 | + raise ValueError(f"runtime_host must be an http(s) URL, got {runtime_host!r}") |
| 32 | 32 | ||
| 33 | url = f"{base}{_HEALTH_PATH}" | 33 | url = f"{base}{_HEALTH_PATH}" |
| 34 | try: | 34 | try: |
Mapplications/manager/manager_server/src/manager_server/infrastructure/match_expr.py+29-0文件内容审核中,请稍后刷新重试
| @@ -1,19 +1,30 @@ | |||
| 1 | # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved. | 1 | # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved. |
| 2 | -"""Gateway Config Receiver HTTP 客户端。""" | 2 | +"""Gateway / Runtime Config 下发 HTTP 客户端。 |
| 3 | + | ||
| 4 | +``http_request`` 为公用传输层(endpoint + mTLS + httpx); | ||
| 5 | +``gateway_request`` / ``runtime_request`` 在其上封装各自响应约定。 | ||
| 6 | +""" | ||
| 3 | 7 | ||
| 4 | from __future__ import annotations | 8 | from __future__ import annotations |
| 5 | 9 | ||
| 6 | -from typing import Any | 10 | +from typing import Any, Literal |
| 7 | 11 | ||
| 8 | import httpx | 12 | import httpx |
| 13 | +from openjiuwen_runtime.foundation.db.handler import DBHandler | ||
| 9 | 14 | ||
| 15 | +from manager_server.infrastructure.config import settings | ||
| 10 | from manager_server.infrastructure.db import get_db_handler | 16 | from manager_server.infrastructure.db import get_db_handler |
| 11 | from manager_server.infrastructure.logger import get_logger | 17 | from manager_server.infrastructure.logger import get_logger |
| 12 | -from manager_server.manager_config_push.endpoint import require_gateway_endpoint | 18 | +from manager_server.manager_config_push.endpoint import ( |
| 19 | + require_gateway_endpoint, | ||
| 20 | + require_runtime_endpoint, | ||
| 21 | +) | ||
| 13 | from manager_server.security.link_mtls import ManagerLinkMTLSConfig | 22 | from manager_server.security.link_mtls import ManagerLinkMTLSConfig |
| 14 | 23 | ||
| 15 | logger = get_logger(__name__) | 24 | logger = get_logger(__name__) |
| 16 | 25 | ||
| 26 | +LinkRole = Literal["gateway", "runtime"] | ||
| 27 | + | ||
| 17 | 28 | ||
| 18 | def _contains_credential_replace(value: Any) -> bool: | 29 | def _contains_credential_replace(value: Any) -> bool: |
| 19 | if isinstance(value, dict): | 30 | if isinstance(value, dict): |
| @@ -25,6 +36,90 @@ def _contains_credential_replace(value: Any) -> bool: | |||
| 25 | return False | 36 | return False |
| 26 | 37 | ||
| 27 | 38 | ||
| 39 | +async def http_request( | ||
| 40 | + jiuwenclaw_id: str, | ||
| 41 | + role: LinkRole, | ||
| 42 | + method: str, | ||
| 43 | + path: str, | ||
| 44 | + json_body: dict[str, Any] | None = None, | ||
| 45 | + *, | ||
| 46 | + endpoint: str, | ||
| 47 | + timeout: float = 30.0, | ||
| 48 | + handler: DBHandler | None = None, | ||
| 49 | + network_target: Any | None = None, | ||
| 50 | +) -> httpx.Response: | ||
| 51 | + """Manager → Gateway/Runtime 的公用 HTTP 请求。 | ||
| 52 | + | ||
| 53 | + ``endpoint`` 由调用方解析后传入(``gateway_host`` / ``runtime_host``); | ||
| 54 | + 本函数挂 mTLS、发 ``method path``。业务响应解析由 | ||
| 55 | + ``gateway_request`` / ``runtime_request`` 各自处理。 | ||
| 56 | + """ | ||
| 57 | + jid = str(jiuwenclaw_id or "").strip() | ||
| 58 | + if not jid: | ||
| 59 | + raise ValueError("jiuwenclaw_id is required") | ||
| 60 | + if not path.startswith("/"): | ||
| 61 | + raise ValueError(f"path must start with /: {path!r}") | ||
| 62 | + base = str(endpoint or "").strip().rstrip("/") | ||
| 63 | + if not base: | ||
| 64 | + raise ValueError(f"{role} endpoint is required") | ||
| 65 | + | ||
| 66 | + link_mtls = ManagerLinkMTLSConfig.from_env() | ||
| 67 | + db = handler | ||
| 68 | + if db is None and link_mtls.enforced: | ||
| 69 | + db = get_db_handler() | ||
| 70 | + link_target = await link_mtls.target( | ||
| 71 | + db, | ||
| 72 | + jid, | ||
| 73 | + role=role, | ||
| 74 | + endpoint=base, | ||
| 75 | + ) | ||
| 76 | + endpoint = link_target.endpoint | ||
| 77 | + headers = link_target.headers | ||
| 78 | + url = f"{endpoint}{path}" | ||
| 79 | + payload = dict(json_body or {}) | ||
| 80 | + | ||
| 81 | + try: | ||
| 82 | + async with httpx.AsyncClient( | ||
| 83 | + timeout=timeout, | ||
| 84 | + trust_env=False, | ||
| 85 | + **link_target.client_kwargs, | ||
| 86 | + ) as client: | ||
| 87 | + if network_target is None: | ||
| 88 | + resp = await client.request( | ||
| 89 | + method.upper(), | ||
| 90 | + url, | ||
| 91 | + json=payload, | ||
| 92 | + **({"headers": headers} if headers else {}), | ||
| 93 | + ) | ||
| 94 | + else: | ||
| 95 | + from manager_server.core.template.a2a_discovery import _pinned_request | ||
| 96 | + | ||
| 97 | + pinned = _pinned_request(client, url, *network_target) | ||
| 98 | + request = client.build_request( | ||
| 99 | + method.upper(), | ||
| 100 | + pinned.url, | ||
| 101 | + json=payload, | ||
| 102 | + headers={**headers, "Host": pinned.headers["Host"]}, | ||
| 103 | + extensions=pinned.extensions, | ||
| 104 | + ) | ||
| 105 | + resp = await client.send(request) | ||
| 106 | + except Exception as exc: | ||
| 107 | + raise ValueError( | ||
| 108 | + f"{role} HTTP push failed jiuwenclaw_id={jid!r} url={url}: {exc}" | ||
| 109 | + ) from exc | ||
| 110 | + | ||
| 111 | + if resp.status_code >= 400: | ||
| 112 | + detail = resp.text[:500] | ||
| 113 | + try: | ||
| 114 | + detail = resp.json().get("detail") or detail | ||
| 115 | + except Exception: # noqa: BLE001, S110 | ||
| 116 | + pass | ||
| 117 | + raise ValueError( | ||
| 118 | + f"{role} HTTP push rejected status={resp.status_code} detail={detail!r}" | ||
| 119 | + ) | ||
| 120 | + return resp | ||
| 121 | + | ||
| 122 | + | ||
| 28 | async def gateway_request( | 123 | async def gateway_request( |
| 29 | jiuwenclaw_id: str, | 124 | jiuwenclaw_id: str, |
| 30 | method: str, | 125 | method: str, |
| @@ -38,23 +133,12 @@ async def gateway_request( | |||
| 38 | ``path`` 为绝对路径,如 ``/api/v1/logging``;``business`` 为业务字段 JSON。 | 133 | ``path`` 为绝对路径,如 ``/api/v1/logging``;``business`` 为业务字段 JSON。 |
| 39 | """ | 134 | """ |
| 40 | jid = str(jiuwenclaw_id or "").strip() | 135 | jid = str(jiuwenclaw_id or "").strip() |
| 41 | - if not jid: | ||
| 42 | - raise ValueError("jiuwenclaw_id is required") | ||
| 43 | - if not path.startswith("/"): | ||
| 44 | - raise ValueError(f"path must start with /: {path!r}") | ||
| 45 | - | ||
| 46 | endpoint = await require_gateway_endpoint(jid) | 136 | endpoint = await require_gateway_endpoint(jid) |
| 47 | - link_mtls = ManagerLinkMTLSConfig.from_env() | ||
| 48 | - link_target = await link_mtls.target( | ||
| 49 | - get_db_handler() if link_mtls.enforced else None, | ||
| 50 | - jid, | ||
| 51 | - role="gateway", | ||
| 52 | - endpoint=endpoint, | ||
| 53 | - ) | ||
| 54 | - endpoint = link_target.endpoint | ||
| 55 | payload = dict(business or {}) | 137 | payload = dict(business or {}) |
| 56 | network_target = None | 138 | network_target = None |
| 57 | - if path.startswith("/api/v1/a2a-outbound-templates") and _contains_credential_replace(payload): | 139 | + if path.startswith("/api/v1/a2a-outbound-templates") and _contains_credential_replace( |
| 140 | + payload | ||
| 141 | + ): | ||
| 58 | from manager_server.core.template.a2a_discovery import _normalize_url, _validate_target | 142 | from manager_server.core.template.a2a_discovery import _normalize_url, _validate_target |
| 59 | 143 | ||
| 60 | data = payload.get("data") or {} | 144 | data = payload.get("data") or {} |
| @@ -72,43 +156,16 @@ async def gateway_request( | |||
| 72 | f"A2A credential sync blocked by network access settings: {exc}" | 156 | f"A2A credential sync blocked by network access settings: {exc}" |
| 73 | ) from exc | 157 | ) from exc |
| 74 | 158 | ||
| 75 | - headers = link_target.headers | 159 | + resp = await http_request( |
| 76 | - | 160 | + jid, |
| 77 | - url = f"{endpoint}{path}" | 161 | + "gateway", |
| 78 | - try: | 162 | + method, |
| 79 | - async with httpx.AsyncClient( | 163 | + path, |
| 80 | - timeout=timeout, | 164 | + payload, |
| 81 | - trust_env=False, | 165 | + endpoint=endpoint, |
| 82 | - **link_target.client_kwargs, | 166 | + timeout=timeout, |
| 83 | - ) as client: | 167 | + network_target=network_target, |
| 84 | - if network_target is None: | 168 | + ) |
| 85 | - resp = await client.request( | ||
| 86 | - method.upper(), url, json=payload, **({"headers": headers} if headers else {}) | ||
| 87 | - ) | ||
| 88 | - else: | ||
| 89 | - from manager_server.core.template.a2a_discovery import _pinned_request | ||
| 90 | - | ||
| 91 | - pinned = _pinned_request(client, url, *network_target) | ||
| 92 | - request = client.build_request( | ||
| 93 | - method.upper(), | ||
| 94 | - pinned.url, | ||
| 95 | - json=payload, | ||
| 96 | - headers={**headers, "Host": pinned.headers["Host"]}, | ||
| 97 | - extensions=pinned.extensions, | ||
| 98 | - ) | ||
| 99 | - resp = await client.send(request) | ||
| 100 | - except Exception as exc: | ||
| 101 | - raise ValueError( | ||
| 102 | - f"gateway HTTP push failed jiuwenclaw_id={jid!r} url={url}: {exc}" | ||
| 103 | - ) from exc | ||
| 104 | - | ||
| 105 | - if resp.status_code >= 400: | ||
| 106 | - detail = resp.text[:500] | ||
| 107 | - try: | ||
| 108 | - detail = resp.json().get("detail") or detail | ||
| 109 | - except Exception: # noqa: BLE001, S110 | ||
| 110 | - pass | ||
| 111 | - raise ValueError(f"gateway HTTP push rejected status={resp.status_code} detail={detail!r}") | ||
| 112 | 169 | ||
| 113 | result = None | 170 | result = None |
| 114 | try: | 171 | try: |
| @@ -128,3 +185,47 @@ async def gateway_request( | |||
| 128 | "result": result, | 185 | "result": result, |
| 129 | "transport": "http", | 186 | "transport": "http", |
| 130 | } | 187 | } |
| 188 | + | ||
| 189 | + | ||
| 190 | +async def runtime_request( | ||
| 191 | + jiuwenclaw_id: str, | ||
| 192 | + method: str, | ||
| 193 | + path: str, | ||
| 194 | + payload: dict[str, Any] | None = None, | ||
| 195 | + *, | ||
| 196 | + timeout: float | None = None, | ||
| 197 | + handler: DBHandler | None = None, | ||
| 198 | +) -> dict[str, Any]: | ||
| 199 | + """向 Agent Runtime 发一次同步写请求。 | ||
| 200 | + | ||
| 201 | + ``path`` 为绝对路径,如 ``/api/session/config_sync``。 | ||
| 202 | + 无 ``runtime_host`` 时抛 ``ValueError``(与 gateway_request 对称)。 | ||
| 203 | + """ | ||
| 204 | + jid = str(jiuwenclaw_id or "").strip() | ||
| 205 | + endpoint = await require_runtime_endpoint(jid) | ||
| 206 | + resp = await http_request( | ||
| 207 | + jid, | ||
| 208 | + "runtime", | ||
| 209 | + method, | ||
| 210 | + path, | ||
| 211 | + payload, | ||
| 212 | + endpoint=endpoint, | ||
| 213 | + timeout=( | ||
| 214 | + settings.agent_runtime_sync_timeout if timeout is None else float(timeout) | ||
| 215 | + ), | ||
| 216 | + handler=handler, | ||
| 217 | + ) | ||
| 218 | + body: Any = None | ||
| 219 | + try: | ||
| 220 | + body = resp.json() | ||
| 221 | + except Exception: # noqa: BLE001, S110 | ||
| 222 | + body = None | ||
| 223 | + if isinstance(body, dict) and body.get("ok") is False: | ||
| 224 | + raise ValueError(body.get("error_message") or "agent runtime request failed") | ||
| 225 | + logger.info( | ||
| 226 | + "[ManagerConfigPush] runtime ok jiuwenclaw_id=%s %s %s", | ||
| 227 | + jid, | ||
| 228 | + method.upper(), | ||
| 229 | + path, | ||
| 230 | + ) | ||
| 231 | + return body if isinstance(body, dict) else {"ok": True} | ||
| @@ -11,14 +11,14 @@ from manager_server.core.instance.instance_service import get_instance_row, list | |||
| 11 | 11 | ||
| 12 | 12 | ||
| 13 | def resolve_gateway_endpoint(row: Any) -> str | None: | 13 | def resolve_gateway_endpoint(row: Any) -> str | None: |
| 14 | - """解析 Gateway 配置下发基址(``gateway_config_host``)。""" | 14 | + """解析 Gateway 配置下发基址(``gateway_host``)。""" |
| 15 | - host = str(getattr(row, "gateway_config_host", None) or "").strip().rstrip("/") | 15 | + host = str(getattr(row, "gateway_host", None) or "").strip().rstrip("/") |
| 16 | return host or None | 16 | return host or None |
| 17 | 17 | ||
| 18 | 18 | ||
| 19 | def resolve_runtime_endpoint(row: Any) -> str | None: | 19 | def resolve_runtime_endpoint(row: Any) -> str | None: |
| 20 | - """解析 Runtime ``config_sync`` 基址(``runtime_config_host``)。""" | 20 | + """解析 Runtime ``config_sync`` 基址(``runtime_host``)。""" |
| 21 | - host = str(getattr(row, "runtime_config_host", None) or "").strip().rstrip("/") | 21 | + host = str(getattr(row, "runtime_host", None) or "").strip().rstrip("/") |
| 22 | return host or None | 22 | return host or None |
| 23 | 23 | ||
| 24 | 24 | ||
| @@ -31,14 +31,14 @@ async def require_gateway_endpoint(jiuwenclaw_id: str) -> str: | |||
| 31 | endpoint = resolve_gateway_endpoint(row) | 31 | endpoint = resolve_gateway_endpoint(row) |
| 32 | if not endpoint: | 32 | if not endpoint: |
| 33 | raise ValueError( | 33 | raise ValueError( |
| 34 | - f"no gateway_config_host for jiuwenclaw_id={jiuwenclaw_id!r}; " | 34 | + f"no gateway_host for jiuwenclaw_id={jiuwenclaw_id!r}; " |
| 35 | - "set gateway_config_host on the instance" | 35 | + "set gateway_host on the instance" |
| 36 | ) | 36 | ) |
| 37 | return endpoint | 37 | return endpoint |
| 38 | 38 | ||
| 39 | 39 | ||
| 40 | async def require_runtime_endpoint(jiuwenclaw_id: str) -> str: | 40 | async def require_runtime_endpoint(jiuwenclaw_id: str) -> str: |
| 41 | - """按实例解析 Runtime 基址(``runtime_config_host``)。""" | 41 | + """按实例解析 Runtime 基址(``runtime_host``)。""" |
| 42 | from manager_server.infrastructure.db import get_db_handler | 42 | from manager_server.infrastructure.db import get_db_handler |
| 43 | 43 | ||
| 44 | row = await get_instance_row(get_db_handler(), jiuwenclaw_id) | 44 | row = await get_instance_row(get_db_handler(), jiuwenclaw_id) |
| @@ -47,8 +47,8 @@ async def require_runtime_endpoint(jiuwenclaw_id: str) -> str: | |||
| 47 | endpoint = resolve_runtime_endpoint(row) | 47 | endpoint = resolve_runtime_endpoint(row) |
| 48 | if not endpoint: | 48 | if not endpoint: |
| 49 | raise ValueError( | 49 | raise ValueError( |
| 50 | - f"no runtime_config_host for jiuwenclaw_id={jiuwenclaw_id!r}; " | 50 | + f"no runtime_host for jiuwenclaw_id={jiuwenclaw_id!r}; " |
| 51 | - "set runtime_config_host on the instance" | 51 | + "set runtime_host on the instance" |
| 52 | ) | 52 | ) |
| 53 | return endpoint | 53 | return endpoint |
| 54 | 54 | ||
| @@ -18,21 +18,28 @@ INSTANCE_INFO_TABLE_DEF = TableDefinition( | |||
| 18 | ColumnDefinition( | 18 | ColumnDefinition( |
| 19 | "namespace", "string", length=64, nullable=False, default="default" | 19 | "namespace", "string", length=64, nullable=False, default="default" |
| 20 | ), | 20 | ), |
| 21 | - # Gateway 侧(config_host → status → last_alive) | 21 | + # Gateway 侧(host → status → last_alive) |
| 22 | - ColumnDefinition("gateway_config_host", "string", length=512, nullable=False), | 22 | + ColumnDefinition("gateway_host", "string", length=512, nullable=False), |
| 23 | ColumnDefinition( | 23 | ColumnDefinition( |
| 24 | "gateway_status", "string", length=32, nullable=False, default="pending" | 24 | "gateway_status", "string", length=32, nullable=False, default="pending" |
| 25 | ), | 25 | ), |
| 26 | ColumnDefinition("gateway_last_alive", "datetime", nullable=True), | 26 | ColumnDefinition("gateway_last_alive", "datetime", nullable=True), |
| 27 | # Runtime 侧 | 27 | # Runtime 侧 |
| 28 | - ColumnDefinition("runtime_config_host", "string", length=512, nullable=False), | 28 | + ColumnDefinition("runtime_host", "string", length=512, nullable=False), |
| 29 | ColumnDefinition( | 29 | ColumnDefinition( |
| 30 | "runtime_status", "string", length=32, nullable=False, default="pending" | 30 | "runtime_status", "string", length=32, nullable=False, default="pending" |
| 31 | ), | 31 | ), |
| 32 | ColumnDefinition("runtime_last_alive", "datetime", nullable=True), | 32 | ColumnDefinition("runtime_last_alive", "datetime", nullable=True), |
| 33 | + # User Web 侧(探活地址 / 状态 / 上次存活;存量库由 init 补列 + 回填) | ||
| 34 | + ColumnDefinition( | ||
| 35 | + "user_web_host", "string", length=512, nullable=False, default="" | ||
| 36 | + ), | ||
| 37 | + ColumnDefinition( | ||
| 38 | + "user_web_status", "string", length=32, nullable=False, default="pending" | ||
| 39 | + ), | ||
| 40 | + ColumnDefinition("user_web_last_alive", "datetime", nullable=True), | ||
| 33 | ColumnDefinition("space_id", "string", length=64, nullable=False, default="default"), | 41 | ColumnDefinition("space_id", "string", length=64, nullable=False, default="default"), |
| 34 | - # 扩展 JSON:用户面反代上游 user_web_host / gateway_web_http_host /gateway_web_ws_host | 42 | + # 扩展 JSON:gateway_web_http_host / gateway_web_ws_host 等用户面反代上游 |
| 35 | - # 以及 User Web 探活 user_web_status / user_web_last_alive 等 | ||
| 36 | ColumnDefinition("data", "json", nullable=True), | 43 | ColumnDefinition("data", "json", nullable=True), |
| 37 | ColumnDefinition("created_at", "datetime", nullable=False), | 44 | ColumnDefinition("created_at", "datetime", nullable=False), |
| 38 | ColumnDefinition("created_by", "string", length=64, nullable=False), | 45 | ColumnDefinition("created_by", "string", length=64, nullable=False), |
| @@ -40,7 +47,6 @@ INSTANCE_INFO_TABLE_DEF = TableDefinition( | |||
| 40 | ColumnDefinition("updated_by", "string", length=64, nullable=True), | 47 | ColumnDefinition("updated_by", "string", length=64, nullable=True), |
| 41 | ], | 48 | ], |
| 42 | indexes=[ | 49 | indexes=[ |
| 43 | - IndexDefinition(["gateway_status"], unique=False), | ||
| 44 | IndexDefinition(["updated_at"], unique=False), | 50 | IndexDefinition(["updated_at"], unique=False), |
| 45 | ], | 51 | ], |
| 46 | ) | 52 | ) |
| @@ -200,6 +200,7 @@ A2A_ACCESS_POLICY_TEMPLATE_TABLE_DEF = TableDefinition( | |||
| 200 | ColumnDefinition("member_template_ids", "json", nullable=False, default=list), | 200 | ColumnDefinition("member_template_ids", "json", nullable=False, default=list), |
| 201 | ColumnDefinition("enabled", "boolean", nullable=False, default=True), | 201 | ColumnDefinition("enabled", "boolean", nullable=False, default=True), |
| 202 | ColumnDefinition("revision", "integer", nullable=False, default=1), | 202 | ColumnDefinition("revision", "integer", nullable=False, default=1), |
| 203 | + ColumnDefinition("data", "json", nullable=True), | ||
| 203 | ColumnDefinition("created_at", "datetime", nullable=False), | 204 | ColumnDefinition("created_at", "datetime", nullable=False), |
| 204 | ColumnDefinition("updated_at", "datetime", nullable=False), | 205 | ColumnDefinition("updated_at", "datetime", nullable=False), |
| 205 | ], | 206 | ], |
| @@ -231,6 +232,8 @@ PERMISSIONS_TEMPLATE_TABLE_DEF = TableDefinition( | |||
| 231 | ) | 232 | ) |
| 232 | 233 | ||
| 233 | # 容器规格目录(与 Runtime service_config_container 对齐;平台全局,不带 jiuwenclaw_id)。 | 234 | # 容器规格目录(与 Runtime service_config_container 对齐;平台全局,不带 jiuwenclaw_id)。 |
| 235 | +# 模板经 main_container_id / sidecar_container_ids 引用;create/update 时 UPSERT 本表。 | ||
| 236 | +# command/args 与 Runtime 对齐(存量库须手工 ALTER 补列)。 | ||
| 234 | # 段落 JSON 列为内部规范形(snake 键);模板经 main_container_id / sidecar_container_ids 引用。 | 237 | # 段落 JSON 列为内部规范形(snake 键);模板经 main_container_id / sidecar_container_ids 引用。 |
| 235 | 238 | ||
| 236 | MCP_TEMPLATE_TABLE_DEF = TableDefinition( | 239 | MCP_TEMPLATE_TABLE_DEF = TableDefinition( |
| @@ -283,7 +286,10 @@ SERVICE_CONFIG_CONTAINER_TABLE_DEF = TableDefinition( | |||
| 283 | ColumnDefinition("resources", "json", nullable=True), | 286 | ColumnDefinition("resources", "json", nullable=True), |
| 284 | ColumnDefinition("volume_mounts", "json", nullable=True), | 287 | ColumnDefinition("volume_mounts", "json", nullable=True), |
| 285 | ColumnDefinition("security_context", "json", nullable=True), | 288 | ColumnDefinition("security_context", "json", nullable=True), |
| 289 | + ColumnDefinition("command", "json", nullable=True), | ||
| 290 | + ColumnDefinition("args", "json", nullable=True), | ||
| 286 | ColumnDefinition("readiness_probe", "json", nullable=True), | 291 | ColumnDefinition("readiness_probe", "json", nullable=True), |
| 292 | + ColumnDefinition("data", "json", nullable=True), | ||
| 287 | ColumnDefinition("created_at", "datetime", nullable=False), | 293 | ColumnDefinition("created_at", "datetime", nullable=False), |
| 288 | ColumnDefinition("updated_at", "datetime", nullable=False), | 294 | ColumnDefinition("updated_at", "datetime", nullable=False), |
| 289 | ], | 295 | ], |
| @@ -305,51 +311,26 @@ SERVICE_CONFIG_TEMPLATE_TABLE_DEF = TableDefinition( | |||
| 305 | ColumnDefinition("template_id", "string", length=100, nullable=False), | 311 | ColumnDefinition("template_id", "string", length=100, nullable=False), |
| 306 | ColumnDefinition("template_name", "string", length=128, nullable=False, default=""), | 312 | ColumnDefinition("template_name", "string", length=128, nullable=False, default=""), |
| 307 | ColumnDefinition("description", "string", length=512, nullable=True), | 313 | ColumnDefinition("description", "string", length=512, nullable=True), |
| 308 | - # 以下容器级列与 Runtime 对齐:三段式引用落地前仍保留内联形态(读兼容); | ||
| 309 | - # 新写入可同时填 main_container_id / sidecar_container_ids / volumes。 | ||
| 310 | - ColumnDefinition("agent_image", "string", length=512, nullable=False, default=""), | ||
| 311 | ColumnDefinition("namespace", "string", length=128, nullable=False, default="default"), | 314 | ColumnDefinition("namespace", "string", length=128, nullable=False, default="default"), |
| 312 | ColumnDefinition("node_name", "string", length=128, nullable=True), | 315 | ColumnDefinition("node_name", "string", length=128, nullable=True), |
| 313 | - ColumnDefinition("run_as_user", "integer", nullable=True), | 316 | + # Pod 级 securityContext.fsGroup(与 Runtime 对齐;存量库须手工 ALTER) |
| 314 | - ColumnDefinition("run_as_group", "integer", nullable=True), | 317 | + ColumnDefinition("fs_group", "integer", nullable=True), |
| 315 | ColumnDefinition("pod_name", "string", length=128, nullable=False, default="agentserver"), | 318 | ColumnDefinition("pod_name", "string", length=128, nullable=False, default="agentserver"), |
| 316 | - ColumnDefinition("container_name", "string", length=128, nullable=False, default="agent"), | ||
| 317 | - ColumnDefinition("container_port", "integer", nullable=False, default=8080), | ||
| 318 | - ColumnDefinition("port_name", "string", length=64, nullable=False, default="http"), | ||
| 319 | - ColumnDefinition("sse_port", "integer", nullable=False, default=8080), | ||
| 320 | ColumnDefinition("sse_path", "string", length=128, nullable=False, default="/sse"), | 319 | ColumnDefinition("sse_path", "string", length=128, nullable=False, default="/sse"), |
| 321 | - ColumnDefinition("health_path", "string", length=128, nullable=False, default="/health"), | ||
| 322 | - ColumnDefinition("agent_env", "json", nullable=True), | ||
| 323 | - ColumnDefinition( | ||
| 324 | - "image_pull_policy", | ||
| 325 | - "string", | ||
| 326 | - length=64, | ||
| 327 | - nullable=False, | ||
| 328 | - default="IfNotPresent", | ||
| 329 | - ), | ||
| 330 | ColumnDefinition("kubeconfig", "string", length=512, nullable=True), | 320 | ColumnDefinition("kubeconfig", "string", length=512, nullable=True), |
| 331 | - ColumnDefinition("readiness_initial_delay", "integer", nullable=False, default=5), | ||
| 332 | - ColumnDefinition("readiness_period", "integer", nullable=False, default=5), | ||
| 333 | ColumnDefinition("ready_timeout", "integer", nullable=False, default=300), | 321 | ColumnDefinition("ready_timeout", "integer", nullable=False, default=300), |
| 334 | ColumnDefinition("ready_poll_interval", "integer", nullable=False, default=2), | 322 | ColumnDefinition("ready_poll_interval", "integer", nullable=False, default=2), |
| 335 | - ColumnDefinition("nfs_server", "string", length=256, nullable=True), | 323 | + # 容器规格经 main_container_id / sidecar_container_ids 引用目录; |
| 336 | - ColumnDefinition("nfs_path", "string", length=256, nullable=True), | 324 | + # volumes 为 Pod 级卷定义(与 Runtime 三段式对齐)。 |
| 337 | - ColumnDefinition("nfs_mount_path", "string", length=256, nullable=True), | ||
| 338 | - ColumnDefinition("agent_cpu_request", "string", length=32, nullable=True), | ||
| 339 | - ColumnDefinition("agent_memory_request", "string", length=32, nullable=True), | ||
| 340 | - ColumnDefinition("agent_cpu_limit", "string", length=32, nullable=True), | ||
| 341 | - ColumnDefinition("agent_memory_limit", "string", length=32, nullable=True), | ||
| 342 | - ColumnDefinition("sidecars", "json", nullable=True), | ||
| 343 | - ColumnDefinition("agent_host_path_mounts", "json", nullable=True), | ||
| 344 | - ColumnDefinition("agent_configmap_mounts", "json", nullable=True), | ||
| 345 | - ColumnDefinition("agent_pvc_mounts", "json", nullable=True), | ||
| 346 | ColumnDefinition("main_container_id", "string", length=100, nullable=True), | 325 | ColumnDefinition("main_container_id", "string", length=100, nullable=True), |
| 347 | ColumnDefinition("sidecar_container_ids", "json", nullable=True), | 326 | ColumnDefinition("sidecar_container_ids", "json", nullable=True), |
| 348 | ColumnDefinition("volumes", "json", nullable=True), | 327 | ColumnDefinition("volumes", "json", nullable=True), |
| 349 | - ColumnDefinition("min_idle_services", "integer", nullable=False, default=0), | 328 | + # 策略四列与 Runtime/wire 术语同名(曾用 EE 名 min_idle_services/ |
| 350 | - ColumnDefinition("service_concurrency", "integer", nullable=False, default=2), | 329 | + # service_concurrency/service_ttl/session_concurrency;存量库须手工 RENAME)。 |
| 351 | - ColumnDefinition("service_ttl", "integer", nullable=False, default=300), | 330 | + ColumnDefinition("min_idle_pods", "integer", nullable=False, default=0), |
| 352 | - ColumnDefinition("session_concurrency", "integer", nullable=False, default=3), | 331 | + ColumnDefinition("pod_concurrency", "integer", nullable=False, default=2), |
| 332 | + ColumnDefinition("pod_ttl", "integer", nullable=False, default=300), | ||
| 333 | + ColumnDefinition("scope_concurrency", "integer", nullable=False, default=3), | ||
| 353 | ColumnDefinition("session_ttl", "integer", nullable=False, default=60), | 334 | ColumnDefinition("session_ttl", "integer", nullable=False, default=60), |
| 354 | ColumnDefinition("message_timeout", "integer", nullable=False, default=600), | 335 | ColumnDefinition("message_timeout", "integer", nullable=False, default=600), |
| 355 | ColumnDefinition("enabled", "boolean", nullable=False, default=True), | 336 | ColumnDefinition("enabled", "boolean", nullable=False, default=True), |
| @@ -20,13 +20,13 @@ class CreateInstanceBody(BaseModel): | |||
| 20 | namespace: str = Field(default="default", max_length=64) | 20 | namespace: str = Field(default="default", max_length=64) |
| 21 | space_id: str = Field(default="default", max_length=64) | 21 | space_id: str = Field(default="default", max_length=64) |
| 22 | created_by: str = Field(default="system", max_length=64) | 22 | created_by: str = Field(default="system", max_length=64) |
| 23 | - gateway_config_host: str = Field(..., min_length=1, max_length=512) | 23 | + gateway_host: str = Field(..., min_length=1, max_length=512) |
| 24 | - runtime_config_host: str = Field(..., min_length=1, max_length=512) | 24 | + runtime_host: str = Field(..., min_length=1, max_length=512) |
| 25 | user_web_host: str = Field( | 25 | user_web_host: str = Field( |
| 26 | ..., | 26 | ..., |
| 27 | min_length=1, | 27 | min_length=1, |
| 28 | max_length=512, | 28 | max_length=512, |
| 29 | - description="写入 instance_info.data.user_web_host", | 29 | + description="写入 instance_info.user_web_host", |
| 30 | ) | 30 | ) |
| 31 | gateway_web_http_host: str = Field( | 31 | gateway_web_http_host: str = Field( |
| 32 | ..., | 32 | ..., |
| @@ -45,20 +45,20 @@ class CreateInstanceBody(BaseModel): | |||
| 45 | class InstanceUpdateBody(BaseModel): | 45 | class InstanceUpdateBody(BaseModel): |
| 46 | """更新 instance_info(未传字段不修改)。 | 46 | """更新 instance_info(未传字段不修改)。 |
| 47 | 47 | ||
| 48 | - Gateway/Runtime 的 status 与 last_alive 由 Manager 探活维护,不可通过本接口修改。 | 48 | + Gateway / Runtime / User Web 的 status 与 last_alive 由 Manager 探活维护, |
| 49 | - 用户面反代 host 字段写入 ``data`` JSON,不落独立列。 | 49 | + 不可通过本接口修改。gateway_web_*_host 仍写入 ``data`` JSON。 |
| 50 | """ | 50 | """ |
| 51 | 51 | ||
| 52 | jiuwenclaw_name: str | None = Field(default=None, max_length=128) | 52 | jiuwenclaw_name: str | None = Field(default=None, max_length=128) |
| 53 | description: str | None = Field(default=None, max_length=4096) | 53 | description: str | None = Field(default=None, max_length=4096) |
| 54 | namespace: str | None = Field(default=None, max_length=64) | 54 | namespace: str | None = Field(default=None, max_length=64) |
| 55 | space_id: str | None = Field(default=None, max_length=64) | 55 | space_id: str | None = Field(default=None, max_length=64) |
| 56 | - gateway_config_host: str | None = Field(default=None, max_length=512) | 56 | + gateway_host: str | None = Field(default=None, max_length=512) |
| 57 | - runtime_config_host: str | None = Field(default=None, max_length=512) | 57 | + runtime_host: str | None = Field(default=None, max_length=512) |
| 58 | user_web_host: str | None = Field( | 58 | user_web_host: str | None = Field( |
| 59 | default=None, | 59 | default=None, |
| 60 | max_length=512, | 60 | max_length=512, |
| 61 | - description="写入 instance_info.data.user_web_host", | 61 | + description="写入 instance_info.user_web_host", |
| 62 | ) | 62 | ) |
| 63 | gateway_web_http_host: str | None = Field( | 63 | gateway_web_http_host: str | None = Field( |
| 64 | default=None, | 64 | default=None, |
| @@ -79,22 +79,22 @@ class InstanceSummary(BaseModel): | |||
| 79 | jiuwenclaw_name: str | 79 | jiuwenclaw_name: str |
| 80 | namespace: str | 80 | namespace: str |
| 81 | space_id: str | 81 | space_id: str |
| 82 | - gateway_config_host: str | 82 | + gateway_host: str |
| 83 | gateway_status: str | 83 | gateway_status: str |
| 84 | gateway_last_alive: str | None = None | 84 | gateway_last_alive: str | None = None |
| 85 | - runtime_config_host: str | 85 | + runtime_host: str |
| 86 | runtime_status: str | 86 | runtime_status: str |
| 87 | runtime_last_alive: str | None = None | 87 | runtime_last_alive: str | None = None |
| 88 | + user_web_host: str | None = None | ||
| 88 | user_web_status: str = Field( | 89 | user_web_status: str = Field( |
| 89 | default="pending", | 90 | default="pending", |
| 90 | - description="来自 instance_info.data.user_web_status", | 91 | + description="instance_info.user_web_status", |
| 91 | ) | 92 | ) |
| 92 | user_web_last_alive: str | None = Field( | 93 | user_web_last_alive: str | None = Field( |
| 93 | default=None, | 94 | default=None, |
| 94 | - description="来自 instance_info.data.user_web_last_alive", | 95 | + description="instance_info.user_web_last_alive", |
| 95 | ) | 96 | ) |
| 96 | - # 以下三项来自 data JSON,响应中展开便于前端展示 | 97 | + # 以下两项来自 data JSON,响应中展开便于前端展示 |
| 97 | - user_web_host: str | None = None | ||
| 98 | gateway_web_http_host: str | None = None | 98 | gateway_web_http_host: str | None = None |
| 99 | gateway_web_ws_host: str | None = None | 99 | gateway_web_ws_host: str | None = None |
| 100 | created_at: str | None = None | 100 | created_at: str | None = None |
| @@ -108,7 +108,7 @@ class InstanceListQuery(BaseModel): | |||
| 108 | runtime_status: str | None = None | 108 | runtime_status: str | None = None |
| 109 | user_web_status: str | None = Field( | 109 | user_web_status: str | None = Field( |
| 110 | default=None, | 110 | default=None, |
| 111 | - description="按 data.user_web_status 内存过滤", | 111 | + description="按 instance_info.user_web_status 过滤", |
| 112 | ) | 112 | ) |
| 113 | search: str | None = Field( | 113 | search: str | None = Field( |
| 114 | default=None, | 114 | default=None, |
| @@ -120,7 +120,7 @@ class InstanceListQuery(BaseModel): | |||
| 120 | description=( | 120 | description=( |
| 121 | "排序字段:jiuwenclaw_name、gateway_status、gateway_last_alive、" | 121 | "排序字段:jiuwenclaw_name、gateway_status、gateway_last_alive、" |
| 122 | "runtime_status、runtime_last_alive、user_web_status、user_web_last_alive、" | 122 | "runtime_status、runtime_last_alive、user_web_status、user_web_last_alive、" |
| 123 | - "namespace、updated_at(user_web_* 为内存排序)" | 123 | + "namespace、updated_at" |
| 124 | ), | 124 | ), |
| 125 | ) | 125 | ) |
| 126 | sort_order: str | None = Field(default=None, description="排序方向:asc、desc") | 126 | sort_order: str | None = Field(default=None, description="排序方向:asc、desc") |
| @@ -8,7 +8,6 @@ from __future__ import annotations | |||
| 8 | 8 | ||
| 9 | from typing import Annotated, Any, Literal | 9 | from typing import Annotated, Any, Literal |
| 10 | from urllib.parse import unquote, urlparse, urlsplit | 10 | from urllib.parse import unquote, urlparse, urlsplit |
| 11 | -import re | ||
| 12 | 11 | ||
| 13 | from croniter import croniter | 12 | from croniter import croniter |
| 14 | from pydantic import ( | 13 | from pydantic import ( |
| @@ -31,19 +30,6 @@ from manager_server.infrastructure.template_ref import ( | |||
| 31 | ModelTypeLiteral = Literal["default", "video", "audio", "vision", "image_gen"] | 30 | ModelTypeLiteral = Literal["default", "video", "audio", "vision", "image_gen"] |
| 32 | ExtensionComponentLiteral = Literal["gateway", "agent_server"] | 31 | ExtensionComponentLiteral = Literal["gateway", "agent_server"] |
| 33 | ExtensionHookTypeLiteral = Literal["pre_request", "post_request", "error", "schedule"] | 32 | ExtensionHookTypeLiteral = Literal["pre_request", "post_request", "error", "schedule"] |
| 34 | -ImagePullPolicyLiteral = Literal["Always", "IfNotPresent", "Never"] | ||
| 35 | -TemplateIdPath = Annotated[str, Field(min_length=1, max_length=100)] | ||
| 36 | -TemplateRefField = Annotated[dict[str, list[str]], BeforeValidator(normalize_template_ref)] | ||
| 37 | -OptionalTemplateRefField = Annotated[ | ||
| 38 | - dict[str, list[str]] | None, | ||
| 39 | - BeforeValidator(normalize_template_ref_optional), | ||
| 40 | -] | ||
| 41 | - | ||
| 42 | - | ||
| 43 | -ModelTypeLiteral = Literal["default", "video", "audio", "vision", "image_gen"] | ||
| 44 | -ExtensionComponentLiteral = Literal["gateway", "agent_server"] | ||
| 45 | -ExtensionHookTypeLiteral = Literal["pre_request", "post_request", "error", "schedule"] | ||
| 46 | -ImagePullPolicyLiteral = Literal["Always", "IfNotPresent", "Never"] | ||
| 47 | TemplateIdPath = Annotated[str, Field(min_length=1, max_length=100)] | 33 | TemplateIdPath = Annotated[str, Field(min_length=1, max_length=100)] |
| 48 | TemplateRefField = Annotated[dict[str, list[str]], BeforeValidator(normalize_template_ref)] | 34 | TemplateRefField = Annotated[dict[str, list[str]], BeforeValidator(normalize_template_ref)] |
| 49 | OptionalTemplateRefField = Annotated[ | 35 | OptionalTemplateRefField = Annotated[ |
| @@ -614,6 +600,7 @@ class A2AAccessPolicyTemplateCreateBody(SafeTextMixin): | |||
| 614 | mode: A2AAccessPolicyMode | 600 | mode: A2AAccessPolicyMode |
| 615 | member_template_ids: list[TemplateIdPath] = Field(default_factory=list) | 601 | member_template_ids: list[TemplateIdPath] = Field(default_factory=list) |
| 616 | enabled: bool = True | 602 | enabled: bool = True |
| 603 | + data: dict[str, Any] | None = None | ||
| 617 | 604 | ||
| 618 | 605 | ||
| 619 | 606 | ||
| @@ -629,6 +616,7 @@ class A2AAccessPolicyTemplateUpdateBody(SafeTextMixin): | |||
| 629 | mode: A2AAccessPolicyMode | None = None | 616 | mode: A2AAccessPolicyMode | None = None |
| 630 | member_template_ids: list[TemplateIdPath] | None = None | 617 | member_template_ids: list[TemplateIdPath] | None = None |
| 631 | enabled: bool | None = None | 618 | enabled: bool | None = None |
| 619 | + data: dict[str, Any] | None = None | ||
| 632 | 620 | ||
| 633 | 621 | ||
| 634 | 622 | ||
| @@ -656,6 +644,7 @@ class A2AAccessPolicyTemplateOut(BaseModel): | |||
| 656 | enabled: bool | 644 | enabled: bool |
| 657 | revision: int | 645 | revision: int |
| 658 | reference_count: int = 0 | 646 | reference_count: int = 0 |
| 647 | + data: dict[str, Any] | None = None | ||
| 659 | created_at: str | None | 648 | created_at: str | None |
| 660 | updated_at: str | None | 649 | updated_at: str | None |
| 661 | 650 | ||
| @@ -810,159 +799,32 @@ class McpTemplateOut(BaseModel): | |||
| 810 | updated_at: str | None | 799 | updated_at: str | None |
| 811 | 800 | ||
| 812 | 801 | ||
| 813 | -# 与库表类型上限一致:integer → 有符号 32 位;autoscale_interval → DECIMAL(10,3) | 802 | +# 与库表类型上限一致:integer → 有符号 32 位 |
| 814 | 803 | ||
| 815 | _SERVICE_INT_MAX = 2_147_483_647 | 804 | _SERVICE_INT_MAX = 2_147_483_647 |
| 816 | 805 | ||
| 817 | -# K8s resource quantity:CPU 如 500m / 2 / 0.5;内存须带单位 Ki/Mi/Gi/K/M/G | ||
| 818 | -_K8S_CPU_RE = re.compile(r"^(?:(?:0|[1-9]\d*)(?:\.\d+)?|\.\d+)m?$") | ||
| 819 | -_K8S_MEMORY_RE = re.compile(r"^(?:(?:0|[1-9]\d*)(?:\.\d+)?|\.\d+)(?:Ki|Mi|Gi|K|M|G)$") | ||
| 820 | - | ||
| 821 | - | ||
| 822 | -def _normalize_resource_quantity(value: Any) -> str | None: | ||
| 823 | - if value is None: | ||
| 824 | - return None | ||
| 825 | - text = str(value).strip() | ||
| 826 | - return text or None | ||
| 827 | - | ||
| 828 | - | ||
| 829 | -def _validate_k8s_cpu(value: str | None) -> str | None: | ||
| 830 | - if value is None: | ||
| 831 | - return None | ||
| 832 | - if len(value) > 32: | ||
| 833 | - raise ValueError("at most 32 characters") | ||
| 834 | - if not _K8S_CPU_RE.fullmatch(value): | ||
| 835 | - raise ValueError("must be a valid Kubernetes CPU quantity (e.g. '500m', '2', '0.5')") | ||
| 836 | - return value | ||
| 837 | - | ||
| 838 | - | ||
| 839 | -def _validate_k8s_memory(value: str | None) -> str | None: | ||
| 840 | - if value is None: | ||
| 841 | - return None | ||
| 842 | - if len(value) > 32: | ||
| 843 | - raise ValueError("at most 32 characters") | ||
| 844 | - if not _K8S_MEMORY_RE.fullmatch(value): | ||
| 845 | - raise ValueError( | ||
| 846 | - "must be a Kubernetes memory quantity with unit " | ||
| 847 | - "Ki/Mi/Gi/K/M/G (e.g. '512Mi', '2Gi', '128M')" | ||
| 848 | - ) | ||
| 849 | - return value | ||
| 850 | - | ||
| 851 | - | ||
| 852 | -K8sCpuQuantity = Annotated[ | ||
| 853 | - str | None, | ||
| 854 | - BeforeValidator(_normalize_resource_quantity), | ||
| 855 | - AfterValidator(_validate_k8s_cpu), | ||
| 856 | -] | ||
| 857 | -K8sMemoryQuantity = Annotated[ | ||
| 858 | - str | None, | ||
| 859 | - BeforeValidator(_normalize_resource_quantity), | ||
| 860 | - AfterValidator(_validate_k8s_memory), | ||
| 861 | -] | ||
| 862 | - | ||
| 863 | - | ||
| 864 | -def is_valid_unix_abs_path(value: str) -> bool: | ||
| 865 | - """校验绝对 Unix 路径:以 / 开头,禁止 \\、空段、. 与 ..。""" | ||
| 866 | - if not value or len(value) > 512: | ||
| 867 | - return False | ||
| 868 | - if "\0" in value or "\\" in value: | ||
| 869 | - return False | ||
| 870 | - if not value.startswith("/"): | ||
| 871 | - return False | ||
| 872 | - if value == "/": | ||
| 873 | - return True | ||
| 874 | - core = value.rstrip("/") | ||
| 875 | - if not core.startswith("/"): | ||
| 876 | - return False | ||
| 877 | - for segment in core[1:].split("/"): | ||
| 878 | - if not segment or segment in (".", ".."): | ||
| 879 | - return False | ||
| 880 | - return True | ||
| 881 | - | ||
| 882 | - | ||
| 883 | -def _normalize_required_nfs_path(value: Any) -> str: | ||
| 884 | - if value is None: | ||
| 885 | - return "/" | ||
| 886 | - text = str(value).strip() | ||
| 887 | - return text or "/" | ||
| 888 | - | ||
| 889 | - | ||
| 890 | -def _validate_required_nfs_path(value: str) -> str: | ||
| 891 | - if not is_valid_unix_abs_path(value): | ||
| 892 | - raise ValueError("must be an absolute Unix path (e.g. '/', '/data/nfs')") | ||
| 893 | - return value | ||
| 894 | - | ||
| 895 | - | ||
| 896 | -def _normalize_optional_unix_path(value: Any) -> str | None: | ||
| 897 | - if value is None: | ||
| 898 | - return None | ||
| 899 | - text = str(value).strip() | ||
| 900 | - return text or None | ||
| 901 | - | ||
| 902 | - | ||
| 903 | -def _validate_optional_unix_path(value: str | None) -> str | None: | ||
| 904 | - if value is None: | ||
| 905 | - return None | ||
| 906 | - if not is_valid_unix_abs_path(value): | ||
| 907 | - raise ValueError("must be an absolute Unix path (e.g. '/mnt/nfs')") | ||
| 908 | - return value | ||
| 909 | - | ||
| 910 | - | ||
| 911 | -NfsExportPath = Annotated[ | ||
| 912 | - str, | ||
| 913 | - BeforeValidator(_normalize_required_nfs_path), | ||
| 914 | - AfterValidator(_validate_required_nfs_path), | ||
| 915 | -] | ||
| 916 | -OptionalUnixAbsPath = Annotated[ | ||
| 917 | - str | None, | ||
| 918 | - BeforeValidator(_normalize_optional_unix_path), | ||
| 919 | - AfterValidator(_validate_optional_unix_path), | ||
| 920 | -] | ||
| 921 | - | ||
| 922 | 806 | ||
| 923 | class ServiceConfigTemplateCreateBody(SafeTextMixin): | 807 | class ServiceConfigTemplateCreateBody(SafeTextMixin): |
| 924 | model_config = ConfigDict(str_strip_whitespace=True) | 808 | model_config = ConfigDict(str_strip_whitespace=True) |
| 925 | 809 | ||
| 926 | template_name: str = Field(..., min_length=1, max_length=128) | 810 | template_name: str = Field(..., min_length=1, max_length=128) |
| 927 | description: str | None = Field(default=None, max_length=512) | 811 | description: str | None = Field(default=None, max_length=512) |
| 928 | - agent_image: str = Field(default="", max_length=512) | ||
| 929 | namespace: str = Field(default="default", max_length=128) | 812 | namespace: str = Field(default="default", max_length=128) |
| 930 | node_name: str | None = Field(default=None, max_length=128) | 813 | node_name: str | None = Field(default=None, max_length=128) |
| 931 | - run_as_user: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) | 814 | + fs_group: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) |
| 932 | - run_as_group: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) | ||
| 933 | pod_name: str = Field(default="agentserver", max_length=128) | 815 | pod_name: str = Field(default="agentserver", max_length=128) |
| 934 | - container_name: str = Field(default="agent", min_length=1, max_length=128) | ||
| 935 | - container_port: int = Field(default=8080, ge=1, le=65535) | ||
| 936 | - port_name: str = Field(default="http", max_length=64) | ||
| 937 | - sse_port: int = Field(default=8080, ge=1, le=65535) | ||
| 938 | sse_path: str = Field(default="/sse", max_length=128) | 816 | sse_path: str = Field(default="/sse", max_length=128) |
| 939 | - health_path: str = Field(default="/health", max_length=128) | ||
| 940 | - agent_env: dict[str, str] | None = None | ||
| 941 | - image_pull_policy: ImagePullPolicyLiteral = Field(default="IfNotPresent") | ||
| 942 | kubeconfig: str | None = Field(default=None, max_length=512) | 817 | kubeconfig: str | None = Field(default=None, max_length=512) |
| 943 | - readiness_initial_delay: int = Field(default=5, ge=0, le=_SERVICE_INT_MAX) | ||
| 944 | - readiness_period: int = Field(default=5, ge=1, le=_SERVICE_INT_MAX) | ||
| 945 | ready_timeout: int = Field(default=300, ge=1, le=_SERVICE_INT_MAX) | 818 | ready_timeout: int = Field(default=300, ge=1, le=_SERVICE_INT_MAX) |
| 946 | ready_poll_interval: int = Field(default=2, ge=1, le=_SERVICE_INT_MAX) | 819 | ready_poll_interval: int = Field(default=2, ge=1, le=_SERVICE_INT_MAX) |
| 947 | - nfs_server: str | None = Field(default=None, max_length=256) | ||
| 948 | - nfs_path: OptionalUnixAbsPath = None | ||
| 949 | - nfs_mount_path: OptionalUnixAbsPath = None | ||
| 950 | - agent_cpu_request: K8sCpuQuantity = None | ||
| 951 | - agent_memory_request: K8sMemoryQuantity = None | ||
| 952 | - agent_cpu_limit: K8sCpuQuantity = None | ||
| 953 | - agent_memory_limit: K8sMemoryQuantity = None | ||
| 954 | - sidecars: list[dict[str, Any]] | None = None | ||
| 955 | - agent_host_path_mounts: list[dict[str, Any]] | None = None | ||
| 956 | - agent_configmap_mounts: list[dict[str, Any]] | None = None | ||
| 957 | - agent_pvc_mounts: list[dict[str, Any]] | None = None | ||
| 958 | main_container_id: str | None = Field(default=None, max_length=100) | 820 | main_container_id: str | None = Field(default=None, max_length=100) |
| 959 | sidecar_container_ids: list[str] | None = None | 821 | sidecar_container_ids: list[str] | None = None |
| 960 | volumes: list[dict[str, Any]] | None = None | 822 | volumes: list[dict[str, Any]] | None = None |
| 961 | - min_idle_services: int = Field(default=0, ge=0, le=_SERVICE_INT_MAX) | 823 | + min_idle_pods: int = Field(default=0, ge=0, le=_SERVICE_INT_MAX) |
| 962 | - service_concurrency: int = Field(default=2, ge=1, le=_SERVICE_INT_MAX) | 824 | + pod_concurrency: int = Field(default=2, ge=1, le=_SERVICE_INT_MAX) |
| 963 | - service_ttl: int = Field(default=300, ge=1, le=_SERVICE_INT_MAX) | 825 | + pod_ttl: int = Field(default=300, ge=1, le=_SERVICE_INT_MAX) |
| 964 | message_timeout: int = Field(default=600, ge=1, le=_SERVICE_INT_MAX) | 826 | message_timeout: int = Field(default=600, ge=1, le=_SERVICE_INT_MAX) |
| 965 | - session_concurrency: int = Field(default=3, ge=1, le=_SERVICE_INT_MAX) | 827 | + scope_concurrency: int = Field(default=3, ge=1, le=_SERVICE_INT_MAX) |
| 966 | session_ttl: int = Field(default=60, ge=1, le=_SERVICE_INT_MAX) | 828 | session_ttl: int = Field(default=60, ge=1, le=_SERVICE_INT_MAX) |
| 967 | enabled: bool = True | 829 | enabled: bool = True |
| 968 | data: dict[str, Any] | None = None | 830 | data: dict[str, Any] | None = None |
| @@ -973,44 +835,22 @@ class ServiceConfigTemplateUpdateBody(SafeTextMixin): | |||
| 973 | 835 | ||
| 974 | template_name: str | None = Field(default=None, min_length=1, max_length=128) | 836 | template_name: str | None = Field(default=None, min_length=1, max_length=128) |
| 975 | description: str | None = Field(default=None, max_length=512) | 837 | description: str | None = Field(default=None, max_length=512) |
| 976 | - agent_image: str | None = Field(default=None, max_length=512) | ||
| 977 | namespace: str | None = Field(default=None, max_length=128) | 838 | namespace: str | None = Field(default=None, max_length=128) |
| 978 | node_name: str | None = Field(default=None, max_length=128) | 839 | node_name: str | None = Field(default=None, max_length=128) |
| 979 | - run_as_user: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) | 840 | + fs_group: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) |
| 980 | - run_as_group: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) | ||
| 981 | pod_name: str | None = Field(default=None, max_length=128) | 841 | pod_name: str | None = Field(default=None, max_length=128) |
| 982 | - container_name: str | None = Field(default=None, min_length=1, max_length=128) | ||
| 983 | - container_port: int | None = Field(default=None, ge=1, le=65535) | ||
| 984 | - port_name: str | None = Field(default=None, max_length=64) | ||
| 985 | - sse_port: int | None = Field(default=None, ge=1, le=65535) | ||
| 986 | sse_path: str | None = Field(default=None, max_length=128) | 842 | sse_path: str | None = Field(default=None, max_length=128) |
| 987 | - health_path: str | None = Field(default=None, max_length=128) | ||
| 988 | - agent_env: dict[str, str] | None = None | ||
| 989 | - image_pull_policy: ImagePullPolicyLiteral | None = None | ||
| 990 | kubeconfig: str | None = Field(default=None, max_length=512) | 843 | kubeconfig: str | None = Field(default=None, max_length=512) |
| 991 | - readiness_initial_delay: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) | ||
| 992 | - readiness_period: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | ||
| 993 | ready_timeout: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | 844 | ready_timeout: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) |
| 994 | ready_poll_interval: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | 845 | ready_poll_interval: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) |
| 995 | - nfs_server: str | None = Field(default=None, max_length=256) | ||
| 996 | - nfs_path: OptionalUnixAbsPath = None | ||
| 997 | - nfs_mount_path: OptionalUnixAbsPath = None | ||
| 998 | - agent_cpu_request: K8sCpuQuantity = None | ||
| 999 | - agent_memory_request: K8sMemoryQuantity = None | ||
| 1000 | - agent_cpu_limit: K8sCpuQuantity = None | ||
| 1001 | - agent_memory_limit: K8sMemoryQuantity = None | ||
| 1002 | - sidecars: list[dict[str, Any]] | None = None | ||
| 1003 | - agent_host_path_mounts: list[dict[str, Any]] | None = None | ||
| 1004 | - agent_configmap_mounts: list[dict[str, Any]] | None = None | ||
| 1005 | - agent_pvc_mounts: list[dict[str, Any]] | None = None | ||
| 1006 | main_container_id: str | None = Field(default=None, max_length=100) | 846 | main_container_id: str | None = Field(default=None, max_length=100) |
| 1007 | sidecar_container_ids: list[str] | None = None | 847 | sidecar_container_ids: list[str] | None = None |
| 1008 | volumes: list[dict[str, Any]] | None = None | 848 | volumes: list[dict[str, Any]] | None = None |
| 1009 | - min_idle_services: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) | 849 | + min_idle_pods: int | None = Field(default=None, ge=0, le=_SERVICE_INT_MAX) |
| 1010 | - service_concurrency: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | 850 | + pod_concurrency: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) |
| 1011 | - service_ttl: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | 851 | + pod_ttl: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) |
| 1012 | message_timeout: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | 852 | message_timeout: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) |
| 1013 | - session_concurrency: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | 853 | + scope_concurrency: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) |
| 1014 | session_ttl: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) | 854 | session_ttl: int | None = Field(default=None, ge=1, le=_SERVICE_INT_MAX) |
| 1015 | enabled: bool | None = None | 855 | enabled: bool | None = None |
| 1016 | data: dict[str, Any] | None = None | 856 | data: dict[str, Any] | None = None |
| @@ -1024,7 +864,7 @@ class ServiceConfigTemplateListQuery(BaseModel): | |||
| 1024 | search: str | None = Field(default=None, max_length=256) | 864 | search: str | None = Field(default=None, max_length=256) |
| 1025 | sort_by: str | None = Field( | 865 | sort_by: str | None = Field( |
| 1026 | default=None, | 866 | default=None, |
| 1027 | - description="排序字段:template_name、description、agent_image、updated_at", | 867 | + description="排序字段:template_name、description、updated_at", |
| 1028 | ) | 868 | ) |
| 1029 | sort_order: str | None = Field(default=None, description="排序方向:asc、desc") | 869 | sort_order: str | None = Field(default=None, description="排序方向:asc、desc") |
| 1030 | 870 | ||
| @@ -1034,44 +874,23 @@ class ServiceConfigTemplateOut(BaseModel): | |||
| 1034 | template_id: str | 874 | template_id: str |
| 1035 | template_name: str | 875 | template_name: str |
| 1036 | description: str | None | 876 | description: str | None |
| 1037 | - agent_image: str | ||
| 1038 | namespace: str | 877 | namespace: str |
| 1039 | node_name: str | None | 878 | node_name: str | None |
| 1040 | - run_as_user: int | None | 879 | + fs_group: int | None |
| 1041 | - run_as_group: int | None | ||
| 1042 | pod_name: str | 880 | pod_name: str |
| 1043 | - container_name: str | ||
| 1044 | - container_port: int | ||
| 1045 | - port_name: str | ||
| 1046 | - sse_port: int | ||
| 1047 | sse_path: str | 881 | sse_path: str |
| 1048 | - health_path: str | ||
| 1049 | - agent_env: dict[str, Any] | None | ||
| 1050 | - image_pull_policy: str | ||
| 1051 | kubeconfig: str | None | 882 | kubeconfig: str | None |
| 1052 | - readiness_initial_delay: int | ||
| 1053 | - readiness_period: int | ||
| 1054 | ready_timeout: int | 883 | ready_timeout: int |
| 1055 | ready_poll_interval: int | 884 | ready_poll_interval: int |
| 1056 | - nfs_server: str | None | ||
| 1057 | - nfs_path: str | None | ||
| 1058 | - nfs_mount_path: str | None | ||
| 1059 | - agent_cpu_request: str | None | ||
| 1060 | - agent_memory_request: str | None | ||
| 1061 | - agent_cpu_limit: str | None | ||
| 1062 | - agent_memory_limit: str | None | ||
| 1063 | - sidecars: list[dict[str, Any]] | None | ||
| 1064 | - agent_host_path_mounts: list[dict[str, Any]] | None | ||
| 1065 | - agent_configmap_mounts: list[dict[str, Any]] | None | ||
| 1066 | - agent_pvc_mounts: list[dict[str, Any]] | None | ||
| 1067 | main_container_id: str | None | 885 | main_container_id: str | None |
| 1068 | sidecar_container_ids: list[str] | None | 886 | sidecar_container_ids: list[str] | None |
| 1069 | volumes: list[dict[str, Any]] | None | 887 | volumes: list[dict[str, Any]] | None |
| 1070 | - min_idle_services: int | 888 | + main_image: str | None = None |
| 1071 | - service_concurrency: int | 889 | + min_idle_pods: int |
| 1072 | - service_ttl: int | 890 | + pod_concurrency: int |
| 891 | + pod_ttl: int | ||
| 1073 | message_timeout: int | 892 | message_timeout: int |
| 1074 | - session_concurrency: int | 893 | + scope_concurrency: int |
| 1075 | session_ttl: int | 894 | session_ttl: int |
| 1076 | enabled: bool | 895 | enabled: bool |
| 1077 | data: dict[str, Any] | None | 896 | data: dict[str, Any] | None |
| @@ -104,9 +104,9 @@ async def _create_instance_row( | |||
| 104 | "jiuwenclaw_name": "integration-test", | 104 | "jiuwenclaw_name": "integration-test", |
| 105 | "description": None, | 105 | "description": None, |
| 106 | "namespace": "default", | 106 | "namespace": "default", |
| 107 | - "gateway_config_host": "http://127.0.0.1:18080", | 107 | + "gateway_host": "http://127.0.0.1:18080", |
| 108 | "gateway_status": "online", | 108 | "gateway_status": "online", |
| 109 | - "runtime_config_host": "http://127.0.0.1:18081", | 109 | + "runtime_host": "http://127.0.0.1:18081", |
| 110 | "runtime_status": "pending", | 110 | "runtime_status": "pending", |
| 111 | "data": data, | 111 | "data": data, |
| 112 | "space_id": "default", | 112 | "space_id": "default", |
Mapplications/manager/manager_server/tests/unit_tests/manager/test_instance_routers.py+19-19文件内容审核中,请稍后刷新重试
Mapplications/manager/manager_server/tests/unit_tests/manager/test_runtime_config_sync.py+140-97文件内容审核中,请稍后刷新重试
| @@ -33,7 +33,7 @@ cd ../../.. | |||
| 33 | 33 | ||
| 34 | 5273 是唯一外部入口:`/auth`、`/user`、`/manager` 由 Manager SPA 提供。 | 34 | 5273 是唯一外部入口:`/auth`、`/user`、`/manager` 由 Manager SPA 提供。 |
| 35 | 普通用户登录后由 `/user` 先调用 `POST /api/v1/user-console/active-cluster` 写入 Cookie `jiuwenclaw_id`,再跳转 `/chat/`。 | 35 | 普通用户登录后由 `/user` 先调用 `POST /api/v1/user-console/active-cluster` 写入 Cookie `jiuwenclaw_id`,再跳转 `/chat/`。 |
| 36 | -生产 nginx 按该 Cookie 动态反代到实例 `data.user_web_host` / `data.gateway_web_*_host`(空则回退 `MANAGER_WEB_*_TARGET`)。 | 36 | +生产 nginx 按该 Cookie 动态反代到实例 `user_web_host` / `data.gateway_web_*_host`(空则回退 `MANAGER_WEB_*_TARGET`)。 |
| 37 | `/api` 转发 Manager Server,`/idp` 转发 Identity Center;`/ws`、`/gateway-api`、`/file-api`、`/share-api` 同理动态选 Gateway。 | 37 | `/api` 转发 Manager Server,`/idp` 转发 Identity Center;`/ws`、`/gateway-api`、`/file-api`、`/share-api` 同理动态选 Gateway。 |
| 38 | 独立的 `/gateway-api` 前缀用于避免与 Manager `/api/v1/*` 冲突。Vite 本地仍为静态 `MANAGER_WEB_*_TARGET` 代理。 | 38 | 独立的 `/gateway-api` 前缀用于避免与 Manager `/api/v1/*` 冲突。Vite 本地仍为静态 `MANAGER_WEB_*_TARGET` 代理。 |
| 39 | 39 | ||
| @@ -34,8 +34,8 @@ const HOST_PORT = { | |||
| 34 | 34 | ||
| 35 | /** 手动模式默认短名(同命名空间,不含 ns 后缀) */ | 35 | /** 手动模式默认短名(同命名空间,不含 ns 后缀) */ |
| 36 | const DEFAULT_MANUAL_HOSTS = { | 36 | const DEFAULT_MANUAL_HOSTS = { |
| 37 | - gatewayConfigHost: `http://${HOST_SVC.gateway}:${HOST_PORT.gatewayConfig}`, | 37 | + gatewayHost: `http://${HOST_SVC.gateway}:${HOST_PORT.gatewayConfig}`, |
| 38 | - runtimeConfigHost: `http://${HOST_SVC.runtime}:${HOST_PORT.runtimeConfig}`, | 38 | + runtimeHost: `http://${HOST_SVC.runtime}:${HOST_PORT.runtimeConfig}`, |
| 39 | userWebHost: `http://${HOST_SVC.web}:${HOST_PORT.userWeb}`, | 39 | userWebHost: `http://${HOST_SVC.web}:${HOST_PORT.userWeb}`, |
| 40 | gatewayWebHttpHost: `http://${HOST_SVC.gateway}:${HOST_PORT.gatewayWebHttp}`, | 40 | gatewayWebHttpHost: `http://${HOST_SVC.gateway}:${HOST_PORT.gatewayWebHttp}`, |
| 41 | gatewayWebWsHost: `http://${HOST_SVC.gateway}:${HOST_PORT.gatewayWebWs}`, | 41 | gatewayWebWsHost: `http://${HOST_SVC.gateway}:${HOST_PORT.gatewayWebWs}`, |
| @@ -49,16 +49,16 @@ const FIELD_MAX_LENGTH = { | |||
| 49 | jiuwenclaw_name: 128, | 49 | jiuwenclaw_name: 128, |
| 50 | description: 4096, | 50 | description: 4096, |
| 51 | namespace: 63, | 51 | namespace: 63, |
| 52 | - gateway_config_host: 512, | 52 | + gateway_host: 512, |
| 53 | - runtime_config_host: 512, | 53 | + runtime_host: 512, |
| 54 | user_web_host: 512, | 54 | user_web_host: 512, |
| 55 | gateway_web_http_host: 512, | 55 | gateway_web_http_host: 512, |
| 56 | gateway_web_ws_host: 512, | 56 | gateway_web_ws_host: 512, |
| 57 | } as const; | 57 | } as const; |
| 58 | 58 | ||
| 59 | type HostBundle = { | 59 | type HostBundle = { |
| 60 | - gatewayConfigHost: string; | 60 | + gatewayHost: string; |
| 61 | - runtimeConfigHost: string; | 61 | + runtimeHost: string; |
| 62 | userWebHost: string; | 62 | userWebHost: string; |
| 63 | gatewayWebHttpHost: string; | 63 | gatewayWebHttpHost: string; |
| 64 | gatewayWebWsHost: string; | 64 | gatewayWebWsHost: string; |
| @@ -67,8 +67,8 @@ type HostBundle = { | |||
| 67 | function buildHostsFromNamespace(ns: string): HostBundle { | 67 | function buildHostsFromNamespace(ns: string): HostBundle { |
| 68 | const namespace = ns.trim(); | 68 | const namespace = ns.trim(); |
| 69 | return { | 69 | return { |
| 70 | - gatewayConfigHost: `http://${HOST_SVC.gateway}.${namespace}:${HOST_PORT.gatewayConfig}`, | 70 | + gatewayHost: `http://${HOST_SVC.gateway}.${namespace}:${HOST_PORT.gatewayConfig}`, |
| 71 | - runtimeConfigHost: `http://${HOST_SVC.runtime}.${namespace}:${HOST_PORT.runtimeConfig}`, | 71 | + runtimeHost: `http://${HOST_SVC.runtime}.${namespace}:${HOST_PORT.runtimeConfig}`, |
| 72 | userWebHost: `http://${HOST_SVC.web}.${namespace}:${HOST_PORT.userWeb}`, | 72 | userWebHost: `http://${HOST_SVC.web}.${namespace}:${HOST_PORT.userWeb}`, |
| 73 | gatewayWebHttpHost: `http://${HOST_SVC.gateway}.${namespace}:${HOST_PORT.gatewayWebHttp}`, | 73 | gatewayWebHttpHost: `http://${HOST_SVC.gateway}.${namespace}:${HOST_PORT.gatewayWebHttp}`, |
| 74 | gatewayWebWsHost: `http://${HOST_SVC.gateway}.${namespace}:${HOST_PORT.gatewayWebWs}`, | 74 | gatewayWebWsHost: `http://${HOST_SVC.gateway}.${namespace}:${HOST_PORT.gatewayWebWs}`, |
| @@ -110,7 +110,7 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 110 | 110 | ||
| 111 | const [name, setName] = useState(''); | 111 | const [name, setName] = useState(''); |
| 112 | const [description, setDescription] = useState(''); | 112 | const [description, setDescription] = useState(''); |
| 113 | - const [hostMode, setHostMode] = useState<HostMode>('auto'); | 113 | + const [hostMode, setHostMode] = useState<HostMode>('manual'); |
| 114 | /** 仅自动页签使用;与手动页签互不影响 */ | 114 | /** 仅自动页签使用;与手动页签互不影响 */ |
| 115 | const [autoNamespace, setAutoNamespace] = useState(DEFAULT_NAMESPACE); | 115 | const [autoNamespace, setAutoNamespace] = useState(DEFAULT_NAMESPACE); |
| 116 | /** 仅手动页签使用;与自动页签互不影响 */ | 116 | /** 仅手动页签使用;与自动页签互不影响 */ |
| @@ -131,7 +131,7 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 131 | const next = { | 131 | const next = { |
| 132 | name: '', | 132 | name: '', |
| 133 | description: '', | 133 | description: '', |
| 134 | - hostMode: 'auto' as HostMode, | 134 | + hostMode: 'manual' as HostMode, |
| 135 | autoNamespace: DEFAULT_NAMESPACE, | 135 | autoNamespace: DEFAULT_NAMESPACE, |
| 136 | manualHosts: { ...DEFAULT_MANUAL_HOSTS }, | 136 | manualHosts: { ...DEFAULT_MANUAL_HOSTS }, |
| 137 | }; | 137 | }; |
| @@ -177,8 +177,8 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 177 | 177 | ||
| 178 | const requiredChecks: { label: string; invalid: boolean }[] = [ | 178 | const requiredChecks: { label: string; invalid: boolean }[] = [ |
| 179 | { label: t('instanceForm.name'), invalid: !name.trim() }, | 179 | { label: t('instanceForm.name'), invalid: !name.trim() }, |
| 180 | - { label: t('instanceForm.gatewayConfigHost'), invalid: !hosts.gatewayConfigHost.trim() }, | 180 | + { label: t('instanceForm.gatewayHost'), invalid: !hosts.gatewayHost.trim() }, |
| 181 | - { label: t('instanceForm.runtimeConfigHost'), invalid: !hosts.runtimeConfigHost.trim() }, | 181 | + { label: t('instanceForm.runtimeHost'), invalid: !hosts.runtimeHost.trim() }, |
| 182 | { label: t('instanceForm.userWebHost'), invalid: !hosts.userWebHost.trim() }, | 182 | { label: t('instanceForm.userWebHost'), invalid: !hosts.userWebHost.trim() }, |
| 183 | { label: t('instanceForm.gatewayWebHttpHost'), invalid: !hosts.gatewayWebHttpHost.trim() }, | 183 | { label: t('instanceForm.gatewayWebHttpHost'), invalid: !hosts.gatewayWebHttpHost.trim() }, |
| 184 | ]; | 184 | ]; |
| @@ -188,8 +188,8 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 188 | return; | 188 | return; |
| 189 | } | 189 | } |
| 190 | const requiredHosts: { label: string; value: string }[] = [ | 190 | const requiredHosts: { label: string; value: string }[] = [ |
| 191 | - { label: t('instanceForm.gatewayConfigHost'), value: hosts.gatewayConfigHost }, | 191 | + { label: t('instanceForm.gatewayHost'), value: hosts.gatewayHost }, |
| 192 | - { label: t('instanceForm.runtimeConfigHost'), value: hosts.runtimeConfigHost }, | 192 | + { label: t('instanceForm.runtimeHost'), value: hosts.runtimeHost }, |
| 193 | { label: t('instanceForm.userWebHost'), value: hosts.userWebHost }, | 193 | { label: t('instanceForm.userWebHost'), value: hosts.userWebHost }, |
| 194 | { label: t('instanceForm.gatewayWebHttpHost'), value: hosts.gatewayWebHttpHost }, | 194 | { label: t('instanceForm.gatewayWebHttpHost'), value: hosts.gatewayWebHttpHost }, |
| 195 | ]; | 195 | ]; |
| @@ -220,8 +220,8 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 220 | ...(namespace !== undefined ? { namespace } : {}), | 220 | ...(namespace !== undefined ? { namespace } : {}), |
| 221 | space_id: 'default', | 221 | space_id: 'default', |
| 222 | created_by: 'system', | 222 | created_by: 'system', |
| 223 | - gateway_config_host: hosts.gatewayConfigHost.trim().slice(0, FIELD_MAX_LENGTH.gateway_config_host), | 223 | + gateway_host: hosts.gatewayHost.trim().slice(0, FIELD_MAX_LENGTH.gateway_host), |
| 224 | - runtime_config_host: hosts.runtimeConfigHost.trim().slice(0, FIELD_MAX_LENGTH.runtime_config_host), | 224 | + runtime_host: hosts.runtimeHost.trim().slice(0, FIELD_MAX_LENGTH.runtime_host), |
| 225 | user_web_host: hosts.userWebHost.trim().slice(0, FIELD_MAX_LENGTH.user_web_host), | 225 | user_web_host: hosts.userWebHost.trim().slice(0, FIELD_MAX_LENGTH.user_web_host), |
| 226 | gateway_web_http_host: hosts.gatewayWebHttpHost.trim().slice( | 226 | gateway_web_http_host: hosts.gatewayWebHttpHost.trim().slice( |
| 227 | 0, | 227 | 0, |
| @@ -276,15 +276,6 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 276 | 276 | ||
| 277 | <div className="flex flex-col items-center"> | 277 | <div className="flex flex-col items-center"> |
| 278 | <div className="tabs-bar" role="tablist" aria-label={t('instanceForm.hostMode')}> | 278 | <div className="tabs-bar" role="tablist" aria-label={t('instanceForm.hostMode')}> |
| 279 | - <button | ||
| 280 | - type="button" | ||
| 281 | - role="tab" | ||
| 282 | - aria-selected={hostMode === 'auto'} | ||
| 283 | - className={`tab ${hostMode === 'auto' ? 'active' : ''}`} | ||
| 284 | - onClick={() => setHostMode('auto')} | ||
| 285 | - > | ||
| 286 | - {t('instanceForm.hostModeAuto')} | ||
| 287 | - </button> | ||
| 288 | <button | 279 | <button |
| 289 | type="button" | 280 | type="button" |
| 290 | role="tab" | 281 | role="tab" |
| @@ -294,6 +285,15 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 294 | > | 285 | > |
| 295 | {t('instanceForm.hostModeManual')} | 286 | {t('instanceForm.hostModeManual')} |
| 296 | </button> | 287 | </button> |
| 288 | + <button | ||
| 289 | + type="button" | ||
| 290 | + role="tab" | ||
| 291 | + aria-selected={hostMode === 'auto'} | ||
| 292 | + className={`tab ${hostMode === 'auto' ? 'active' : ''}`} | ||
| 293 | + onClick={() => setHostMode('auto')} | ||
| 294 | + > | ||
| 295 | + {t('instanceForm.hostModeAuto')} | ||
| 296 | + </button> | ||
| 297 | </div> | 297 | </div> |
| 298 | <p className="text-xs text-muted mt-1 text-center"> | 298 | <p className="text-xs text-muted mt-1 text-center"> |
| 299 | {hostMode === 'auto' ? t('instanceForm.hostModeAutoHint') : t('instanceForm.hostModeManualHint')} | 299 | {hostMode === 'auto' ? t('instanceForm.hostModeAutoHint') : t('instanceForm.hostModeManualHint')} |
| @@ -311,8 +311,8 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 311 | placeholder={DEFAULT_NAMESPACE} | 311 | placeholder={DEFAULT_NAMESPACE} |
| 312 | /> | 312 | /> |
| 313 | </div> | 313 | </div> |
| 314 | - <HostPreview label={t('instanceForm.gatewayConfigHost')} value={autoHosts.gatewayConfigHost} /> | 314 | + <HostPreview label={t('instanceForm.gatewayHost')} value={autoHosts.gatewayHost} /> |
| 315 | - <HostPreview label={t('instanceForm.runtimeConfigHost')} value={autoHosts.runtimeConfigHost} /> | 315 | + <HostPreview label={t('instanceForm.runtimeHost')} value={autoHosts.runtimeHost} /> |
| 316 | <HostPreview label={t('instanceForm.userWebHost')} value={autoHosts.userWebHost} /> | 316 | <HostPreview label={t('instanceForm.userWebHost')} value={autoHosts.userWebHost} /> |
| 317 | <HostPreview | 317 | <HostPreview |
| 318 | label={t('instanceForm.gatewayWebHttpHost')} | 318 | label={t('instanceForm.gatewayWebHttpHost')} |
| @@ -323,21 +323,21 @@ export function CreateInstanceModal({ open, onClose, onCreated }: Props) { | |||
| 323 | ) : ( | 323 | ) : ( |
| 324 | <> | 324 | <> |
| 325 | <div> | 325 | <div> |
| 326 | - <FieldLabel required>{t('instanceForm.gatewayConfigHost')}</FieldLabel> | 326 | + <FieldLabel required>{t('instanceForm.gatewayHost')}</FieldLabel> |
| 327 | <LimitedTextInput | 327 | <LimitedTextInput |
| 328 | - value={manualHosts.gatewayConfigHost} | 328 | + value={manualHosts.gatewayHost} |
| 329 | - maxLength={FIELD_MAX_LENGTH.gateway_config_host} | 329 | + maxLength={FIELD_MAX_LENGTH.gateway_host} |
| 330 | - onChange={(gatewayConfigHost) => patchManual({ gatewayConfigHost })} | 330 | + onChange={(gatewayHost) => patchManual({ gatewayHost })} |
| 331 | - placeholder={DEFAULT_MANUAL_HOSTS.gatewayConfigHost} | 331 | + placeholder={DEFAULT_MANUAL_HOSTS.gatewayHost} |
| 332 | /> | 332 | /> |
| 333 | </div> | 333 | </div> |
| 334 | <div> | 334 | <div> |
| 335 | - <FieldLabel required>{t('instanceForm.runtimeConfigHost')}</FieldLabel> | 335 | + <FieldLabel required>{t('instanceForm.runtimeHost')}</FieldLabel> |
| 336 | <LimitedTextInput | 336 | <LimitedTextInput |
| 337 | - value={manualHosts.runtimeConfigHost} | 337 | + value={manualHosts.runtimeHost} |
| 338 | - maxLength={FIELD_MAX_LENGTH.runtime_config_host} | 338 | + maxLength={FIELD_MAX_LENGTH.runtime_host} |
| 339 | - onChange={(runtimeConfigHost) => patchManual({ runtimeConfigHost })} | 339 | + onChange={(runtimeHost) => patchManual({ runtimeHost })} |
| 340 | - placeholder={DEFAULT_MANUAL_HOSTS.runtimeConfigHost} | 340 | + placeholder={DEFAULT_MANUAL_HOSTS.runtimeHost} |
| 341 | /> | 341 | /> |
| 342 | </div> | 342 | </div> |
| 343 | <div> | 343 | <div> |
Mapplications/manager/manager_web/src/pages/templates/ServiceConfigTemplateEditPage.tsx+47-83文件内容审核中,请稍后刷新重试
Mapplications/manager/manager_web/src/pages/templates/ServiceConfigTemplatesPage.tsx+6-11文件内容审核中,请稍后刷新重试
| @@ -3,10 +3,10 @@ export interface InstanceSummary { | |||
| 3 | jiuwenclaw_name: string; | 3 | jiuwenclaw_name: string; |
| 4 | namespace: string; | 4 | namespace: string; |
| 5 | space_id: string; | 5 | space_id: string; |
| 6 | - gateway_config_host: string; | 6 | + gateway_host: string; |
| 7 | gateway_status: string; | 7 | gateway_status: string; |
| 8 | gateway_last_alive?: string | null; | 8 | gateway_last_alive?: string | null; |
| 9 | - runtime_config_host: string; | 9 | + runtime_host: string; |
| 10 | runtime_status: string; | 10 | runtime_status: string; |
| 11 | runtime_last_alive?: string | null; | 11 | runtime_last_alive?: string | null; |
| 12 | user_web_status?: string; | 12 | user_web_status?: string; |
| @@ -31,8 +31,8 @@ export interface CreateInstanceBody { | |||
| 31 | namespace?: string; | 31 | namespace?: string; |
| 32 | space_id?: string; | 32 | space_id?: string; |
| 33 | created_by?: string; | 33 | created_by?: string; |
| 34 | - gateway_config_host: string; | 34 | + gateway_host: string; |
| 35 | - runtime_config_host: string; | 35 | + runtime_host: string; |
| 36 | user_web_host?: string; | 36 | user_web_host?: string; |
| 37 | gateway_web_http_host?: string; | 37 | gateway_web_http_host?: string; |
| 38 | gateway_web_ws_host?: string; | 38 | gateway_web_ws_host?: string; |
| @@ -44,8 +44,8 @@ export interface UpdateInstanceBody { | |||
| 44 | description?: string; | 44 | description?: string; |
| 45 | namespace?: string; | 45 | namespace?: string; |
| 46 | space_id?: string; | 46 | space_id?: string; |
| 47 | - gateway_config_host?: string; | 47 | + gateway_host?: string; |
| 48 | - runtime_config_host?: string; | 48 | + runtime_host?: string; |
| 49 | user_web_host?: string; | 49 | user_web_host?: string; |
| 50 | gateway_web_http_host?: string; | 50 | gateway_web_http_host?: string; |
| 51 | gateway_web_ws_host?: string; | 51 | gateway_web_ws_host?: string; |