Skip to content

Commit

Permalink
feat: Enhance AI console functions (#365)
Browse files Browse the repository at this point in the history
  • Loading branch information
CH3CHO authored Dec 16, 2024
1 parent 4d33d01 commit 6e8806e
Show file tree
Hide file tree
Showing 52 changed files with 3,853 additions and 582 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
fetch-depth: 1

- name: Build Higress Console Package
run: mvn clean package -f ./backend/pom.xml
run: mvn clean package -f ./backend/pom.xml -Dpmd.language=en

- name: Upload Higress Console Package
uses: actions/upload-artifact@v3
Expand Down
2 changes: 1 addition & 1 deletion backend/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@ fi
if [ -n "$DEV" ]; then
BUILD_ARGS="$BUILD_ARGS -Dapp.build.dev=$DEV"
fi
./mvnw clean package -Dmaven.test.skip=true $BUILD_ARGS
./mvnw clean package -Dmaven.test.skip=true -Dpmd.language=en $BUILD_ARGS
docker build -t higress-console:0.0.1 -f Dockerfile .
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ private UserConfigKey() {}
public static final String SYSTEM_INITIALIZED = "system.initialized";
public static final String LOGIN_PAGE_PROMPT_KEY = "login.prompt";
public static final String DASHBOARD_URL = "dashboard.url";
public static final String DASHBOARD_URL_PREFIX = "dashboard.url.";
public static final String CHAT_ENABLED = "chat.enabled";
public static final String ADMIN_PASSWORD_CHANGE_DISABLED = "admin.password-change.disabled";

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@
import org.springframework.web.bind.annotation.RestController;

import com.alibaba.higress.console.controller.dto.DashboardInfo;
import com.alibaba.higress.console.controller.dto.DashboardType;
import com.alibaba.higress.console.controller.dto.Response;
import com.alibaba.higress.sdk.exception.ValidationException;
import com.alibaba.higress.console.service.DashboardService;
import com.alibaba.higress.sdk.exception.ValidationException;

/**
* @author CH3CHO
Expand All @@ -48,25 +49,41 @@ public void setDashboardService(DashboardService dashboardService) {
@GetMapping("/init")
public ResponseEntity<Response<DashboardInfo>> init(@RequestParam(required = false) Boolean force) {
dashboardService.initializeDashboard(Boolean.TRUE.equals(force));
return info();
return info(null);
}

@GetMapping("/info")
public ResponseEntity<Response<DashboardInfo>> info() {
return ResponseEntity.ok(Response.success(dashboardService.getDashboardInfo()));
public ResponseEntity<Response<DashboardInfo>> info(@RequestParam(required = false) String type) {
DashboardType dashboardType = toDashboardType(type);
return ResponseEntity.ok(Response.success(dashboardService.getDashboardInfo(dashboardType)));
}

@PutMapping("/info")
public ResponseEntity<Response<DashboardInfo>> setUrl(@RequestBody DashboardInfo dashboardInfo) {
public ResponseEntity<Response<DashboardInfo>> setUrl(@RequestParam(required = false) String type,
@RequestBody DashboardInfo dashboardInfo) {
DashboardType dashboardType = toDashboardType(type);
if (StringUtils.isEmpty(dashboardInfo.getUrl())) {
throw new ValidationException("Missing required parameter: url");
}
dashboardService.setDashboardUrl(dashboardInfo.getUrl());
return info();
dashboardService.setDashboardUrl(dashboardType, dashboardInfo.getUrl());
return info(dashboardType.toString());
}

@GetMapping("/configData")
public ResponseEntity<Response<String>> getConfigData(@RequestParam @NotBlank String dataSourceUid) {
return ResponseEntity.ok(Response.success(dashboardService.buildConfigData(dataSourceUid)));
public ResponseEntity<Response<String>> getConfigData(@RequestParam(required = false) String type,
@RequestParam @NotBlank String dataSourceUid) {
DashboardType dashboardType = toDashboardType(type);
return ResponseEntity.ok(Response.success(dashboardService.buildConfigData(dashboardType, dataSourceUid)));
}

private static DashboardType toDashboardType(String type) {
if (StringUtils.isEmpty(type)) {
return DashboardType.MAIN;
}
try {
return DashboardType.valueOf(type.toUpperCase());
} catch (IllegalArgumentException e) {
throw new ValidationException("Unknown dashboard type: " + type);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -214,8 +214,8 @@ private ResponseEntity<Response<WasmPluginInstance>> queryInstance(WasmPluginIns
String name) {
WasmPluginInstance instance = wasmPluginInstanceService.query(scope, target, name, false);
if (instance == null) {
instance = WasmPluginInstance.builder().scope(scope).target(target).pluginName(name).internal(false)
.enabled(false).build();
instance = WasmPluginInstance.builder().pluginName(name).internal(false).enabled(false).build();
instance.setTarget(scope, target);
}
return ControllerUtil.buildResponseEntity(instance);
}
Expand All @@ -234,8 +234,7 @@ private ResponseEntity<Response<WasmPluginInstance>> addOrUpdateInstance(WasmPlu
if (plugin == null) {
throw new ValidationException("Unsupported plugin: " + name);
}
instance.setScope(scope);
instance.setTarget(target);
instance.setTarget(scope, target);
instance = wasmPluginInstanceService.addOrUpdate(instance);
return ControllerUtil.buildResponseEntity(instance);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2022-2024 Alibaba Group Holding Ltd.
*
* Licensed 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.higress.console.controller.dto;

public enum DashboardType {

/**
* Main dashboard
*/
MAIN,
/**
* AI Gateway dashboard
*/
AI,
/**
* Access log dashboard
*/
LOG
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,19 +18,29 @@
import javax.servlet.http.HttpServletResponse;

import com.alibaba.higress.console.controller.dto.DashboardInfo;
import com.alibaba.higress.console.controller.dto.DashboardType;

/**
* @author CH3CHO
*/
public interface DashboardService {

@Deprecated
DashboardInfo getDashboardInfo();

DashboardInfo getDashboardInfo(DashboardType type);

void initializeDashboard(boolean overwrite);

@Deprecated
void setDashboardUrl(String url);

void setDashboardUrl(DashboardType type, String url);

@Deprecated
String buildConfigData(String dataSourceUid);

String buildConfigData(DashboardType type, String dataSourceUid);

void forwardDashboardRequest(HttpServletRequest request, HttpServletResponse response) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,10 @@
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
Expand Down Expand Up @@ -46,6 +49,7 @@
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicHeader;
import org.apache.tomcat.util.http.fileupload.util.Streams;
import org.jetbrains.annotations.NotNull;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

Expand All @@ -58,6 +62,7 @@
import com.alibaba.higress.console.constant.SystemConfigKey;
import com.alibaba.higress.console.constant.UserConfigKey;
import com.alibaba.higress.console.controller.dto.DashboardInfo;
import com.alibaba.higress.console.controller.dto.DashboardType;
import com.alibaba.higress.sdk.exception.BusinessException;
import com.google.common.collect.ImmutableSet;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
Expand All @@ -79,6 +84,7 @@ public class DashboardServiceImpl implements DashboardService {
private static final String DATASOURCE_UID_PLACEHOLDER = "${datasource.id}";
private static final String MAIN_DASHBOARD_DATA_PATH = "/dashboard/main.json";
private static final String LOG_DASHBOARD_DATA_PATH = "/dashboard/logs.json";
private static final String AI_DASHBOARD_DATA_PATH = "/dashboard/ai.json";
private static final String PROM_DATASOURCE_TYPE = "prometheus";
private static final String LOKI_DATASOURCE_TYPE = "loki";
private static final String DATASOURCE_ACCESS = "proxy";
Expand Down Expand Up @@ -129,10 +135,7 @@ public class DashboardServiceImpl implements DashboardService {
private CloseableHttpClient realServerClient;
private String realServerBaseUrl;

private String mainDashboardConfiguration;
private GrafanaDashboard configuredMainDashboard;
private String logDashboardConfiguration;
private GrafanaDashboard configuredLogDashboard;
private Map<DashboardType, DashboardConfiguration> dashboardConfigurations;

@Resource
public void setConfigService(ConfigService configService) {
Expand All @@ -141,14 +144,18 @@ public void setConfigService(ConfigService configService) {

@PostConstruct
public void initialize() {
Map<DashboardType, DashboardConfiguration> dashboardConfigurations = new HashMap<>();
try {
mainDashboardConfiguration = IOUtils.resourceToString(MAIN_DASHBOARD_DATA_PATH, StandardCharsets.UTF_8);
configuredMainDashboard = GrafanaClient.parseDashboardData(mainDashboardConfiguration);
logDashboardConfiguration = IOUtils.resourceToString(LOG_DASHBOARD_DATA_PATH, StandardCharsets.UTF_8);
configuredLogDashboard = GrafanaClient.parseDashboardData(logDashboardConfiguration);
dashboardConfigurations.put(DashboardType.MAIN,
new DashboardConfiguration(DashboardType.MAIN, MAIN_DASHBOARD_DATA_PATH));
dashboardConfigurations.put(DashboardType.AI,
new DashboardConfiguration(DashboardType.AI, AI_DASHBOARD_DATA_PATH));
dashboardConfigurations.put(DashboardType.LOG,
new DashboardConfiguration(DashboardType.LOG, LOG_DASHBOARD_DATA_PATH));
} catch (IOException e) {
throw new IllegalStateException("Error occurs when loading dashboard configurations from resource.", e);
}
this.dashboardConfigurations = Collections.unmodifiableMap(dashboardConfigurations);

if (isBuiltIn()) {
try {
Expand All @@ -170,7 +177,12 @@ public void initialize() {

@Override
public DashboardInfo getDashboardInfo() {
return isBuiltIn() ? getBuiltInDashboardInfo() : getConfiguredDashboardInfo();
return getDashboardInfo(DashboardType.MAIN);
}

@Override
public DashboardInfo getDashboardInfo(DashboardType type) {
return isBuiltIn() ? getBuiltInDashboardInfo(type) : getConfiguredDashboardInfo(type);
}

@Override
Expand All @@ -194,26 +206,39 @@ public void initializeDashboard(boolean overwrite) {
} catch (IOException e) {
throw new BusinessException("Error occurs when loading dashboard info from Grafana.", e);
}
configureDashboard(results, configuredMainDashboard.getTitle(), mainDashboardConfiguration, promDatasourceUid,
overwrite);
configureDashboard(results, configuredLogDashboard.getTitle(), logDashboardConfiguration, lokiDatasourceUid,
overwrite);
for (DashboardConfiguration configuration : dashboardConfigurations.values()) {
String datasourceId = configuration.getType() == DashboardType.LOG ? lokiDatasourceUid : promDatasourceUid;
configureDashboard(results, configuration.getDashboard().getTitle(), configuration.getRaw(), datasourceId,
overwrite);
}
}

@Override
public void setDashboardUrl(String url) {
setDashboardUrl(DashboardType.MAIN, url);
}

@Override
public void setDashboardUrl(DashboardType type, String url) {
if (StringUtils.isBlank(url)) {
throw new IllegalArgumentException("url cannot be null or blank.");
}
if (isBuiltIn()) {
throw new IllegalStateException("Manual dashboard configuration is disabled.");
}
configService.setConfig(UserConfigKey.DASHBOARD_URL, url);
DashboardConfiguration configuration = getDashboardConfiguration(type);
configService.setConfig(configuration.getConfigKey(), url);
}

@Override
public String buildConfigData(String datasourceUid) {
return buildConfigData(mainDashboardConfiguration, datasourceUid);
return buildConfigData(DashboardType.MAIN, datasourceUid);
}

@Override
public String buildConfigData(DashboardType type, String datasourceUid) {
DashboardConfiguration configuration = getDashboardConfiguration(type);
return buildConfigData(configuration.getRaw(), datasourceUid);
}

@Override
Expand Down Expand Up @@ -323,7 +348,11 @@ private void configureDashboard(List<GrafanaSearchResult> results, String title,
}
}

private DashboardInfo getBuiltInDashboardInfo() {
private DashboardInfo getBuiltInDashboardInfo(DashboardType type) {
DashboardConfiguration configuration = dashboardConfigurations.get(type);
if (configuration == null) {
throw new IllegalArgumentException("Invalid dashboard type: " + type);
}
List<GrafanaSearchResult> results;
try {
results = grafanaClient.search(null, SearchType.DB, null, null);
Expand All @@ -333,7 +362,7 @@ private DashboardInfo getBuiltInDashboardInfo() {
if (CollectionUtils.isEmpty(results)) {
return new DashboardInfo(true, null, null);
}
String expectedTitle = configuredMainDashboard.getTitle();
String expectedTitle = configuration.getDashboard().getTitle();
if (StringUtils.isEmpty(expectedTitle)) {
throw new IllegalStateException("No title is found in the configured dashboard.");
}
Expand All @@ -342,8 +371,9 @@ private DashboardInfo getBuiltInDashboardInfo() {
return result.map(r -> new DashboardInfo(true, r.getUid(), r.getUrl())).orElse(null);
}

private DashboardInfo getConfiguredDashboardInfo() {
String url = configService.getString(UserConfigKey.DASHBOARD_URL);
private DashboardInfo getConfiguredDashboardInfo(DashboardType type) {
DashboardConfiguration configuration = dashboardConfigurations.get(type);
String url = configService.getString(configuration.getConfigKey());
return new DashboardInfo(false, null, url);
}

Expand Down Expand Up @@ -411,4 +441,31 @@ public void run() {
}
}
}

private @NotNull DashboardConfiguration getDashboardConfiguration(DashboardType type) {
DashboardConfiguration configuration = dashboardConfigurations.get(type);
if (configuration == null) {
throw new IllegalArgumentException("Invalid dashboard type: " + type);
}
return configuration;
}

@lombok.Value
private static class DashboardConfiguration {

DashboardType type;
String configKey;
String resourcePath;
String raw;
GrafanaDashboard dashboard;

public DashboardConfiguration(DashboardType type, String resourcePath) throws IOException {
this.type = type;
this.configKey = type == DashboardType.MAIN ? UserConfigKey.DASHBOARD_URL
: UserConfigKey.DASHBOARD_URL_PREFIX + type.toString().toLowerCase(Locale.ROOT);
this.resourcePath = resourcePath;
this.raw = IOUtils.resourceToString(resourcePath, StandardCharsets.UTF_8);
this.dashboard = GrafanaClient.parseDashboardData(this.raw);
}
}
}
Loading

0 comments on commit 6e8806e

Please sign in to comment.