Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions deploy/docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ services:
- DB_NAME=${DB_NAME:-portal_db}
- DB_USERNAME=${DB_USERNAME:-portal_user}
- DB_PASSWORD=${DB_PASSWORD:-himarket_app_2024}
- JWT_SECRET=${JWT_SECRET}
- ACP_REMOTE_HOST=${ACP_REMOTE_HOST:-sandbox-shared}
- ACP_REMOTE_PORT=${ACP_REMOTE_PORT:-8080}
- ACP_DEFAULT_RUNTIME=${ACP_DEFAULT_RUNTIME:-remote}
Expand Down
10 changes: 10 additions & 0 deletions deploy/docker/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -366,6 +366,7 @@ load_config() {
HIMARKET_SERVER_IMAGE HIMARKET_ADMIN_IMAGE HIMARKET_FRONTEND_IMAGE \
MYSQL_IMAGE NACOS_IMAGE HIGRESS_IMAGE REDIS_IMAGE SANDBOX_IMAGE \
MYSQL_ROOT_PASSWORD MYSQL_PASSWORD MYSQL_DATABASE MYSQL_USER \
JWT_SECRET \
NACOS_ADMIN_PASSWORD HIGRESS_USERNAME HIGRESS_PASSWORD \
ADMIN_USERNAME ADMIN_PASSWORD FRONT_USERNAME FRONT_PASSWORD \
HIMARKET_LANGUAGE \
Expand Down Expand Up @@ -593,6 +594,12 @@ interactive_config() {
export DB_USERNAME="${MYSQL_USER:-portal_user}"
export DB_PASSWORD="${MYSQL_PASSWORD}"

# ─── JWT Secret(自动生成随机值) ───
if [[ -z "${JWT_SECRET:-}" ]]; then
JWT_SECRET="$(openssl rand -base64 32)"
fi
export JWT_SECRET

# ─── 服务凭证 ───
log ""
log "$(msg section.credential)"
Expand Down Expand Up @@ -771,6 +778,9 @@ HIGRESS_IMAGE="${HIGRESS_IMAGE}"
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}"
MYSQL_PASSWORD="${MYSQL_PASSWORD}"

# ========== JWT Secret ==========
JWT_SECRET="${JWT_SECRET}"

# ========== 服务凭证 ==========
NACOS_ADMIN_PASSWORD="${NACOS_ADMIN_PASSWORD}"
HIGRESS_USERNAME="${HIGRESS_USERNAME}"
Expand Down
1 change: 1 addition & 0 deletions deploy/helm/himarket/templates/himarket-server-cm.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ data:
ACP_REMOTE_HOST: {{ .Values.sandbox.remoteHost | default "sandbox-shared" | quote }}
ACP_REMOTE_PORT: {{ .Values.sandbox.remotePort | default "8080" | quote }}
ACP_DEFAULT_RUNTIME: {{ .Values.sandbox.defaultRuntime | default "remote" | quote }}
ACP_TERMINAL_ENABLED: {{ .Values.sandbox.terminalEnabled | default false | quote }}
# 其他非敏感配置可以在这里添加
12 changes: 12 additions & 0 deletions deploy/helm/himarket/templates/mysql.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
{{- $existingSecret := (lookup "v1" "Secret" .Release.Namespace "mysql-secret") }}
{{- $existingServerSecret := (lookup "v1" "Secret" .Release.Namespace "himarket-server-secret") }}
{{- $rootPassword := "" }}
{{- $userPassword := "" }}
{{- $jwtSecret := "" }}
{{- if $existingSecret }}
{{- $rootPassword = (index $existingSecret.data "MYSQL_ROOT_PASSWORD" | b64dec) }}
{{- $userPassword = (index $existingSecret.data "MYSQL_PASSWORD" | b64dec) }}
Expand All @@ -16,6 +18,15 @@
{{- $userPassword = randAlphaNum 16 }}
{{- end }}
{{- end }}
{{- if $existingServerSecret }}
{{- $jwtSecret = (index $existingServerSecret.data "JWT_SECRET" | b64dec) }}
{{- else }}
{{- if .Values.server.jwtSecret }}
{{- $jwtSecret = .Values.server.jwtSecret }}
{{- else }}
{{- $jwtSecret = randAlphaNum 32 }}
{{- end }}
{{- end }}
---
# MySQL Secret: 存储敏感的数据库凭据(自动生成随机密码)
apiVersion: v1
Expand Down Expand Up @@ -46,6 +57,7 @@ stringData:
DB_NAME: {{ .Values.mysql.auth.database | quote }}
DB_USERNAME: {{ .Values.mysql.auth.username | quote }}
DB_PASSWORD: {{ $userPassword | quote }}
JWT_SECRET: {{ $jwtSecret | quote }}

---
# MySQL Headless Service: 为 StatefulSet 提供稳定的网络域
Expand Down
3 changes: 3 additions & 0 deletions deploy/helm/himarket/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ server:
port: 80
replicaCount: 1
serverPort: 8080
# JWT Secret(留空则自动生成随机值)
jwtSecret: ""

# MySQL 数据库配置(始终部署内置 MySQL)
mysql:
Expand Down Expand Up @@ -86,6 +88,7 @@ resources:
# 共享沙箱配置
sandbox:
enabled: true
terminalEnabled: false
image:
repository: sandbox
tag: "latest"
Expand Down
16 changes: 15 additions & 1 deletion deploy/helm/install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,7 @@ load_config() {
NACOS_VERSION NACOS_IMAGE_REGISTRY NACOS_IMAGE_REPOSITORY \
HIGRESS_REPO_NAME HIGRESS_REPO_URL HIGRESS_CHART_REF \
MYSQL_ROOT_PASSWORD MYSQL_PASSWORD \
JWT_SECRET \
NACOS_ADMIN_PASSWORD HIGRESS_USERNAME HIGRESS_PASSWORD \
ADMIN_USERNAME ADMIN_PASSWORD FRONT_USERNAME FRONT_PASSWORD \
MYSQL_STORAGE_CLASS MYSQL_STORAGE_SIZE SANDBOX_STORAGE_CLASS SANDBOX_STORAGE_SIZE \
Expand Down Expand Up @@ -753,6 +754,10 @@ interactive_config() {
NACOS_IMAGE_REPOSITORY="${NACOS_IMAGE_REPOSITORY:-nacos/nacos-server}"
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD:-himarket_root_2024}"
MYSQL_PASSWORD="${MYSQL_PASSWORD:-himarket_app_2024}"
# JWT Secret: 升级时沿用已有值,全新安装时自动生成
if [[ -z "${JWT_SECRET:-}" ]]; then
JWT_SECRET="$(openssl rand -base64 32)"
fi
NACOS_ADMIN_PASSWORD="${NACOS_ADMIN_PASSWORD:-nacos}"
HIGRESS_USERNAME="${HIGRESS_USERNAME:-admin}"
HIGRESS_PASSWORD="${HIGRESS_PASSWORD:-admin}"
Expand Down Expand Up @@ -808,6 +813,11 @@ interactive_config() {
prompt MYSQL_ROOT_PASSWORD "MySQL root password" "himarket_root_2024"
prompt MYSQL_PASSWORD "MySQL app password" "himarket_app_2024"

# JWT Secret: 自动生成随机值(无需用户交互)
if [[ -z "${JWT_SECRET:-}" ]]; then
JWT_SECRET="$(openssl rand -base64 32)"
fi

log ""
log "$(msg section.credential)"
prompt NACOS_ADMIN_PASSWORD "Nacos admin password" "nacos"
Expand Down Expand Up @@ -1008,6 +1018,9 @@ HIGRESS_CHART_REF="${HIGRESS_CHART_REF}"
MYSQL_ROOT_PASSWORD="${MYSQL_ROOT_PASSWORD}"
MYSQL_PASSWORD="${MYSQL_PASSWORD}"

# ========== JWT Secret ==========
JWT_SECRET="${JWT_SECRET}"

# ========== 服务凭证 ==========
NACOS_ADMIN_PASSWORD="${NACOS_ADMIN_PASSWORD}"
HIGRESS_USERNAME="${HIGRESS_USERNAME}"
Expand Down Expand Up @@ -1129,7 +1142,8 @@ deploy_all() {
--set "mysql.persistence.storageClass=${MYSQL_STORAGE_CLASS}" \
--set "mysql.persistence.size=${MYSQL_STORAGE_SIZE}" \
--set "sandbox.persistence.storageClass=${SANDBOX_STORAGE_CLASS}" \
--set "sandbox.persistence.size=${SANDBOX_STORAGE_SIZE}"
--set "sandbox.persistence.size=${SANDBOX_STORAGE_SIZE}" \
--set "server.jwtSecret=${JWT_SECRET}"

# 7. 等待 MySQL Pod 就绪 + 初始化 Nacos 数据库
init_nacos_db_in_cluster "${NS}" "${MYSQL_ROOT_PASSWORD}" "${NACOS_DB_NAME}"
Expand Down
3 changes: 2 additions & 1 deletion himarket-bootstrap/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,11 @@ springdoc:
packages-to-scan: com.alibaba.himarket.controller

jwt:
secret: YourJWTSecret
secret: ${JWT_SECRET:YourJWTSecret}
expiration: 7d

acp:
terminal-enabled: ${ACP_TERMINAL_ENABLED:false}
default-provider: ${ACP_DEFAULT_PROVIDER:qwen-code}
default-runtime: ${ACP_DEFAULT_RUNTIME:remote}
remote:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

package com.alibaba.himarket.support.portal;

import java.util.Map;
import lombok.Data;

@Data
Expand All @@ -27,4 +28,6 @@ public class PortalUiConfig {
private String logo;

private String icon;

private Map<String, Boolean> menuVisibility;
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,12 @@
@ConfigurationProperties(prefix = "acp")
public class AcpProperties {

/**
* 是否启用终端功能。
* 设为 false 时后端拒绝 Terminal WebSocket 连接,前端隐藏终端面板。
*/
private boolean terminalEnabled = true;

/**
* 默认使用的 CLI provider key(对应 providers map 中的 key)
*/
Expand All @@ -32,6 +38,14 @@ public class AcpProperties {
*/
private RemoteConfig remote = new RemoteConfig();

public boolean isTerminalEnabled() {
return terminalEnabled;
}

public void setTerminalEnabled(boolean terminalEnabled) {
this.terminalEnabled = terminalEnabled;
}

public String getDefaultProvider() {
return defaultProvider;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,12 @@ private MarketModelInfo buildMarketModelInfo(ProductResult product) {
.build();
}

@Operation(summary = "获取 HiCoding 功能开关状态")
@GetMapping("/features")
public Map<String, Boolean> getFeatures() {
return Map.of("terminalEnabled", acpProperties.isTerminalEnabled());
}

@Operation(summary = "获取可用的 CLI Provider 列表(含运行时兼容性信息)")
@GetMapping
public List<CliProviderInfo> listProviders() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/

package com.alibaba.himarket.controller;

import com.alibaba.himarket.core.annotation.PublicAccess;
import com.alibaba.himarket.service.PortalService;
import com.alibaba.himarket.support.portal.PortalUiConfig;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/portal-config")
@Tag(name = "门户配置(公开)")
@PublicAccess
@RequiredArgsConstructor
public class PortalConfigController {

private final PortalService portalService;

@Operation(summary = "获取门户 UI 配置")
@GetMapping("/ui")
public PortalUiConfig getUiConfig() {
String portalId = portalService.getDefaultPortal();
if (portalId == null) {
return new PortalUiConfig();
}
try {
var portalResult = portalService.getPortal(portalId);
if (portalResult.getPortalUiConfig() == null) {
return new PortalUiConfig();
}
return portalResult.getPortalUiConfig();
} catch (Exception e) {
return new PortalUiConfig();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -390,6 +390,9 @@ private List<Map<String, Object>> convertChildren(

for (Map<String, Object> item : sidecarItems) {
String name = (String) item.get("name");
if (name != null && name.startsWith(".")) {
continue;
}
String type = (String) item.get("type");
String childPath =
parentPath.endsWith("/") ? parentPath + name : parentPath + "/" + name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,12 @@ public TerminalWebSocketHandler(

@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
if (!acpProperties.isTerminalEnabled()) {
logger.info("Terminal feature is disabled, closing connection: id={}", session.getId());
session.close(CloseStatus.NORMAL);
return;
}

String userId = (String) session.getAttributes().get("userId");
if (userId == null) {
logger.error("No userId in session attributes, closing terminal connection");
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
import {Card, Form, Switch, message} from 'antd'
import {Portal} from '@/types'
import {portalApi} from '@/lib/api'

interface PortalMenuSettingsProps {
portal: Portal
onRefresh?: () => void
}

const MENU_ITEMS = [
{key: "chat", label: "HiChat"},
{key: "coding", label: "HiCoding"},
{key: "agents", label: "智能体"},
{key: "mcp", label: "MCP"},
{key: "models", label: "模型"},
{key: "apis", label: "API"},
{key: "skills", label: "Skills"},
]

export function PortalMenuSettings({portal, onRefresh}: PortalMenuSettingsProps) {
const [form] = Form.useForm()

const getMenuVisibility = (key: string): boolean => {
return portal.portalUiConfig?.menuVisibility?.[key] ?? true
}

const handleToggle = async (key: string, checked: boolean) => {
const currentVisibility = {...(portal.portalUiConfig?.menuVisibility || {})}
const newVisibility = {...currentVisibility, [key]: checked}

// 至少保留一个菜单项可见
const visibleCount = MENU_ITEMS.filter(
item => newVisibility[item.key] ?? true
).length
if (visibleCount === 0) {
message.warning('至少保留一个菜单项为可见状态')
// 恢复 form 中的值
form.setFieldValue(key, true)
return
}

try {
await portalApi.updatePortal(portal.portalId, {
name: portal.name,
description: portal.description,
portalSettingConfig: portal.portalSettingConfig,
portalDomainConfig: portal.portalDomainConfig,
portalUiConfig: {
...portal.portalUiConfig,
menuVisibility: newVisibility,
},
})
message.success('菜单配置保存成功')
onRefresh?.()
} catch {
message.error('保存菜单配置失败')
// 恢复 form 中的值
form.setFieldValue(key, !checked)
}
}

const initialValues = MENU_ITEMS.reduce((acc, item) => {
acc[item.key] = getMenuVisibility(item.key)
return acc
}, {} as Record<string, boolean>)

return (
<div className="p-6 space-y-6">
<div>
<h1 className="text-2xl font-bold mb-2">菜单管理</h1>
<p className="text-gray-600">控制开发者门户导航栏的菜单项显隐</p>
</div>

<Form
form={form}
layout="vertical"
initialValues={initialValues}
>
<Card>
<div className="space-y-6">
<h3 className="text-lg font-medium">导航菜单项</h3>
<div className="grid grid-cols-2 gap-6">
{MENU_ITEMS.map(item => (
<Form.Item
key={item.key}
name={item.key}
label={item.label}
valuePropName="checked"
>
<Switch
onChange={(checked) => handleToggle(item.key, checked)}
/>
</Form.Item>
))}
</div>
</div>
</Card>
</Form>
</div>
)
}
Loading
Loading