Compare commits

...

14 Commits

Author SHA1 Message Date
李佳航
6d4952c483 Merge pull request #109 from dromara/dev
Dev
2025-06-06 16:39:28 +08:00
lijiahangmax
7fab68f8c0 升级版本. 2025-06-06 16:27:29 +08:00
lijiahangmax
3a586c47a3 添加更新日期. 2025-06-06 16:18:48 +08:00
lijiahangmax
1767079249 复制主机. 2025-06-06 15:58:49 +08:00
lijiahangmax
fdd3be5a91 🐛 复制主机. 2025-05-30 01:29:28 +08:00
lijiahangmax
2457a015e4 🐛 修复分组无法删除的问题. 2025-05-29 22:15:08 +08:00
李佳航
40a99eb67a Merge pull request #106 from dromara/dev
Dev
2025-05-29 19:14:53 +08:00
lijiahangmax
1fcf239561 ✏️ 修改文档. 2025-05-29 19:07:16 +08:00
lijiahangmax
bbb1bb0db6 🔨 自定义标签名称. 2025-05-28 20:39:28 +08:00
李佳航
f79d89def9 Merge pull request #101 from dromara/dev
Dev
2025-04-06 21:44:08 +08:00
lijiahangmax
fd535f00c8 🔨 优化前端逻辑. 2025-04-06 21:41:41 +08:00
lijiahangmax
2c07551b88 🐛 修复修改主机配置报错. 2025-04-06 21:17:50 +08:00
lijiahangmax
86914321a6 🔖 修改 sql 脚本. 2025-04-06 11:11:39 +08:00
lijiahangmax
f64c15a01b 🔖 升级版本. 2025-04-06 10:56:28 +08:00
44 changed files with 367 additions and 134 deletions

View File

@@ -146,6 +146,10 @@ QQ群: 755242157
本项目遵循 [Apache-2.0](https://github.com/dromara/orion-visor/blob/main/LICENSE) 开源许可证。
## Gitee 最有价值开源项目 GVP
## Gitee 最有价值开源项目 GVP
![GVP](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2024/10/24/8dd98b8d-9de5-44e6-86d3-04e27ec66123.jpg "GVP")
## GitCode 最有影响力的开源项目 G-Star
![GSTAR](https://bjuimg.obs.cn-north-4.myhuaweicloud.com/images/2025/5/29/d6c17f1c-eddd-408a-ba67-1052a3dad9e9.jpg "GSTAR")

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.7
version=2.3.9
docker build -t orion-visor-adminer:${version} .
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
docker tag orion-visor-adminer:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:latest

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.7
version=2.3.9
cp -r ../../sql ./sql
docker build -t orion-visor-mysql:${version} .
rm -rf ./sql

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.7
version=2.3.9
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-adminer:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-mysql:${version}
docker push registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.7
version=2.3.9
docker build -t orion-visor-redis:${version} .
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:${version}
docker tag orion-visor-redis:${version} registry.cn-hangzhou.aliyuncs.com/orionsec/orion-visor-redis:latest

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.7
version=2.3.9
mv ../../orion-visor-launch/target/orion-visor-launch.jar ./orion-visor-launch.jar
docker build -t orion-visor-service:${version} .
rm -rf ./orion-visor-launch.jar

View File

@@ -1,6 +1,6 @@
#/bin/bash
set -e
version=2.3.7
version=2.3.9
mv ../../orion-visor-ui/dist ./dist
docker build -t orion-visor-ui:${version} .
rm -rf ./orion-visor-launch.jar

View File

@@ -36,7 +36,7 @@ public interface AppConst extends OrionConst {
/**
* 同 ${orion.version} 迭代时候需要手动更改
*/
String VERSION = "2.3.7";
String VERSION = "2.3.9";
/**
* 同 ${spring.application.name}

View File

@@ -14,7 +14,7 @@
<url>https://github.com/dromara/orion-visor</url>
<properties>
<revision>2.3.7</revision>
<revision>2.3.9</revision>
<spring.boot.version>2.7.17</spring.boot.version>
<spring.boot.admin.version>2.7.15</spring.boot.admin.version>
<flatten.maven.plugin.version>1.5.0</flatten.maven.plugin.version>

View File

@@ -39,9 +39,9 @@ import java.util.function.Function;
*/
public class ReplaceVersion {
private static final String TARGET_VERSION = "2.3.6";
private static final String TARGET_VERSION = "2.3.8";
private static final String REPLACE_VERSION = "2.3.7";
private static final String REPLACE_VERSION = "2.3.9";
private static final String PATH = new File("").getAbsolutePath();

View File

@@ -84,6 +84,17 @@ public class HostController {
return hostService.updateHostById(request);
}
@DemoDisableApi
@OperatorLog(HostOperatorType.CREATE)
@PostMapping("/copy")
@Operation(summary = "复制主机")
@PreAuthorize("@ss.hasPermission('asset:host:create')")
public Long copyHost(@Validated @RequestBody HostUpdateRequest request) {
Long id = request.getId();
request.setId(null);
return hostService.copyHost(id, request);
}
@DemoDisableApi
@OperatorLog(HostOperatorType.UPDATE_STATUS)
@PutMapping("/update-status")
@@ -93,6 +104,15 @@ public class HostController {
return hostService.updateHostStatus(request);
}
@DemoDisableApi
@OperatorLog(HostOperatorType.UPDATE_SPEC)
@PutMapping("/update-spec")
@Operation(summary = "修改主机规格信息")
@PreAuthorize("@ss.hasPermission('asset:host:update')")
public Integer updateHostSpec(@Validated @RequestBody HostExtraUpdateRequest request) {
return hostService.updateHostSpec(request);
}
@IgnoreLog(IgnoreLogMode.RET)
@GetMapping("/get")
@Operation(summary = "通过 id 查询主机")

View File

@@ -26,10 +26,13 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
import org.dromara.visor.common.utils.Valid;
import org.dromara.visor.framework.log.core.annotation.IgnoreLog;
import org.dromara.visor.framework.log.core.enums.IgnoreLogMode;
import org.dromara.visor.framework.web.core.annotation.RestWrapper;
import org.dromara.visor.module.asset.entity.request.host.HostExtraUpdateRequest;
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
import org.dromara.visor.module.asset.service.HostExtraService;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
@@ -67,6 +70,8 @@ public class HostExtraController {
@PutMapping("/update")
@Operation(summary = "修改主机拓展信息")
public Integer updateHostExtra(@Validated @RequestBody HostExtraUpdateRequest request) {
HostExtraItemEnum item = Valid.valid(HostExtraItemEnum::of, request.getItem());
Valid.isTrue(item.isUserExtra(), ErrorMessage.PARAM_ERROR);
return hostExtraService.updateHostExtra(request);
}

View File

@@ -61,6 +61,8 @@ public interface HostConvert {
HostBaseVO toBase(HostDO domain);
HostCreateRequest toCreate(HostUpdateRequest request);
List<HostVO> toList(List<HostDO> domain);
List<HostBaseVO> toBaseList(List<HostDO> domain);

View File

@@ -57,6 +57,20 @@ public interface HostConfigDAO extends IMapper<HostConfigDO> {
.getOne();
}
/**
* 通过 hostId 查询
*
* @param hostId hostId
* @return config
*/
default List<HostConfigDO> selectByHostId(Long hostId) {
return this.of()
.createWrapper()
.eq(HostConfigDO::getHostId, hostId)
.then()
.list();
}
/**
* 更新配置状态
*

View File

@@ -48,6 +48,8 @@ public class HostOperatorType extends InitializingOperatorTypes {
public static final String UPDATE_CONFIG = "host:update-config";
public static final String UPDATE_SPEC = "host:update-spec";
@Override
public OperatorType[] types() {
return new OperatorType[]{
@@ -56,6 +58,7 @@ public class HostOperatorType extends InitializingOperatorTypes {
new OperatorType(H, DELETE, "删除主机 <sb>${count}</sb> 条"),
new OperatorType(M, UPDATE_STATUS, "修改主机状态 <sb>${name}</sb> - <sb>${status}</sb>"),
new OperatorType(M, UPDATE_CONFIG, "修改主机配置 <sb>${name}</sb>"),
new OperatorType(M, UPDATE_SPEC, "修改主机规格信息 <sb>${name}</sb>"),
};
}

View File

@@ -49,7 +49,6 @@ public class HostExtraUpdateRequest {
@Schema(description = "主机id")
private Long hostId;
@NotNull
@Schema(description = "配置项")
private String item;

View File

@@ -122,14 +122,12 @@ public class HostSshConfigStrategy extends AbstractGenericsDataStrategy<HostSshC
* @param after after
*/
private void checkEncryptPassword(HostSshConfigModel before, HostSshConfigModel after) {
// 非密码认证则直接赋值
if (!HostSshAuthTypeEnum.PASSWORD.name().equals(after.getAuthType())) {
after.setPassword(before.getPassword());
return;
}
// 使用原始密码
if (!Booleans.isTrue(after.getUseNewPassword())) {
after.setPassword(before.getPassword());
// 非密码认证/使用原始密码则直接赋值
if (!HostSshAuthTypeEnum.PASSWORD.name().equals(after.getAuthType())
|| !Booleans.isTrue(after.getUseNewPassword())) {
if (before != null) {
after.setPassword(before.getPassword());
}
return;
}
// 检查新密码

View File

@@ -26,6 +26,8 @@ import org.dromara.visor.common.handler.data.model.GenericsDataModel;
import org.dromara.visor.module.asset.entity.request.host.HostConfigQueryRequest;
import org.dromara.visor.module.asset.entity.request.host.HostConfigUpdateRequest;
import java.util.List;
/**
* 主机配置 服务类
*
@@ -43,6 +45,15 @@ public interface HostConfigService {
*/
Integer updateHostConfig(HostConfigUpdateRequest request);
/**
* 复制主机配置
*
* @param originId originId
* @param newId newId
* @param types types
*/
void copyHostConfig(Long originId, Long newId, List<String> types);
/**
* 获取主机配置
*

View File

@@ -75,4 +75,12 @@ public interface HostExtraService {
*/
Integer updateHostExtra(HostExtraUpdateRequest request);
/**
* 复制主机拓展信息
*
* @param originId originId
* @param newId newId
*/
void copyHostExtra(Long originId, Long newId);
}

View File

@@ -25,10 +25,7 @@ package org.dromara.visor.module.asset.service;
import cn.orionsec.kit.lang.define.wrapper.DataGrid;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.request.host.HostCreateRequest;
import org.dromara.visor.module.asset.entity.request.host.HostQueryRequest;
import org.dromara.visor.module.asset.entity.request.host.HostUpdateRequest;
import org.dromara.visor.module.asset.entity.request.host.HostUpdateStatusRequest;
import org.dromara.visor.module.asset.entity.request.host.*;
import org.dromara.visor.module.asset.entity.vo.HostVO;
import java.util.List;
@@ -50,6 +47,15 @@ public interface HostService {
*/
Long createHost(HostCreateRequest request);
/**
* 复制主机
*
* @param originId originId
* @param request request
* @return id
*/
Long copyHost(Long originId, HostUpdateRequest request);
/**
* 通过 id 更新主机
*
@@ -66,6 +72,14 @@ public interface HostService {
*/
Integer updateHostStatus(HostUpdateStatusRequest request);
/**
* 修改主机规格
*
* @param request request
* @return effect
*/
Integer updateHostSpec(HostExtraUpdateRequest request);
/**
* 通过 id 查询主机
*

View File

@@ -22,6 +22,8 @@
*/
package org.dromara.visor.module.asset.service.impl;
import cn.orionsec.kit.lang.function.Functions;
import cn.orionsec.kit.lang.utils.Strings;
import com.alibaba.fastjson.JSON;
import lombok.extern.slf4j.Slf4j;
import org.dromara.visor.common.constant.ErrorMessage;
@@ -42,6 +44,10 @@ import org.dromara.visor.module.asset.service.HostConfigService;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* 主机配置 服务实现类
@@ -102,6 +108,34 @@ public class HostConfigServiceImpl implements HostConfigService {
}
}
@Override
public void copyHostConfig(Long originId, Long newId, List<String> types) {
// 查询原始主机配置
Map<String, String> originHostConfigMap = hostConfigDAO.selectByHostId(originId)
.stream()
.collect(Collectors.toMap(HostConfigDO::getType,
HostConfigDO::getConfig,
Functions.right()));
// 新增
List<HostConfigDO> records = new ArrayList<>();
for (String type : types) {
// 获取原始配置
String configValue = originHostConfigMap.get(type);
if (Strings.isBlank(configValue)) {
// 获取默认值
configValue = HostTypeEnum.of(type).getDefault().serial();
}
HostConfigDO newConfig = HostConfigDO.builder()
.hostId(newId)
.type(type)
.status(EnableStatus.ENABLED.name())
.config(configValue)
.build();
records.add(newConfig);
}
hostConfigDAO.insertBatch(records);
}
@Override
public <T extends GenericsDataModel> T getHostConfig(Long hostId, String type) {
// 查询配置信息

View File

@@ -123,6 +123,29 @@ public class HostExtraServiceImpl implements HostExtraService {
return dataExtraApi.updateExtraValue(beforeExtraItem.getId(), newExtra.serial());
}
@Override
public void copyHostExtra(Long originId, Long newId) {
// 查询原始配置
DataExtraQueryDTO query = DataExtraQueryDTO.builder()
.userId(Const.SYSTEM_USER_ID)
.relId(originId)
.build();
List<DataExtraDTO> items = dataExtraApi.getExtraItems(query, DataExtraTypeEnum.HOST);
if (items.isEmpty()) {
return;
}
// 插入新配置
List<DataExtraSetDTO> newItems = items.stream()
.map(s -> DataExtraSetDTO.builder()
.userId(Const.SYSTEM_USER_ID)
.relId(newId)
.item(s.getItem())
.value(s.getValue())
.build())
.collect(Collectors.toList());
dataExtraApi.addExtraItems(newItems, DataExtraTypeEnum.HOST);
}
/**
* 检查配置项并且转为视图 (不存在则初始化默认值)
*

View File

@@ -44,17 +44,12 @@ import org.dromara.visor.module.asset.dao.HostDAO;
import org.dromara.visor.module.asset.define.cache.HostCacheKeyDefine;
import org.dromara.visor.module.asset.entity.domain.HostDO;
import org.dromara.visor.module.asset.entity.dto.HostCacheDTO;
import org.dromara.visor.module.asset.entity.request.host.HostCreateRequest;
import org.dromara.visor.module.asset.entity.request.host.HostQueryRequest;
import org.dromara.visor.module.asset.entity.request.host.HostUpdateRequest;
import org.dromara.visor.module.asset.entity.request.host.HostUpdateStatusRequest;
import org.dromara.visor.module.asset.entity.request.host.*;
import org.dromara.visor.module.asset.entity.vo.HostVO;
import org.dromara.visor.module.asset.enums.HostExtraItemEnum;
import org.dromara.visor.module.asset.enums.HostStatusEnum;
import org.dromara.visor.module.asset.handler.host.extra.model.HostSpecExtraModel;
import org.dromara.visor.module.asset.service.ExecJobHostService;
import org.dromara.visor.module.asset.service.ExecTemplateHostService;
import org.dromara.visor.module.asset.service.HostExtraService;
import org.dromara.visor.module.asset.service.HostService;
import org.dromara.visor.module.asset.service.*;
import org.dromara.visor.module.infra.api.DataExtraApi;
import org.dromara.visor.module.infra.api.DataGroupRelApi;
import org.dromara.visor.module.infra.api.FavoriteApi;
@@ -93,6 +88,9 @@ public class HostServiceImpl implements HostService {
@Resource
private HostConfigDAO hostConfigDAO;
@Resource
private HostConfigService hostConfigService;
@Resource
private HostExtraService hostExtraService;
@@ -140,6 +138,23 @@ public class HostServiceImpl implements HostService {
return id;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long copyHost(Long originId, HostUpdateRequest request) {
log.info("HostService-copyHost originId: {}, request: {}", originId, JSON.toJSONString(request));
// 查询原始主机
HostDO originHost = hostDAO.selectById(originId);
Valid.notNull(originHost, ErrorMessage.HOST_ABSENT);
// 创建主机
Long newId = SpringHolder.getBean(HostService.class)
.createHost(HostConvert.MAPPER.toCreate(request));
// 复制主机额外信息
hostExtraService.copyHostExtra(originId, newId);
// 复制主机配置信息
hostConfigService.copyHostConfig(originId, newId, request.getTypes());
return newId;
}
@Override
@Transactional(rollbackFor = Exception.class)
public Integer updateHostById(HostUpdateRequest request) {
@@ -192,6 +207,19 @@ public class HostServiceImpl implements HostService {
return effect;
}
@Override
public Integer updateHostSpec(HostExtraUpdateRequest request) {
log.info("HostService-updateHostSpec request: {}", JSON.toJSONString(request));
// 查询主机
HostDO record = hostDAO.selectById(request.getHostId());
Valid.notNull(record, ErrorMessage.HOST_ABSENT);
// 设置日志参数
OperatorLogs.add(OperatorLogs.NAME, record.getName());
// 更新
request.setItem(HostExtraItemEnum.SPEC.name());
return hostExtraService.updateHostExtra(request);
}
@Override
@SneakyThrows
public HostVO getHostById(Long id) {

View File

@@ -3,4 +3,4 @@ VITE_API_BASE_URL=http://127.0.0.1:9200/orion-visor/api
# websocket 路径
VITE_WS_BASE_URL=ws://127.0.0.1:9200/orion-visor/keep-alive
# 版本号
VITE_APP_VERSION=2.3.7
VITE_APP_VERSION=2.3.9

View File

@@ -3,4 +3,4 @@ VITE_API_BASE_URL=/orion-visor/api
# websocket 路径
VITE_WS_BASE_URL=/orion-visor/keep-alive
# 版本号
VITE_APP_VERSION=2.3.7
VITE_APP_VERSION=2.3.9

View File

@@ -1,7 +1,7 @@
{
"name": "orion-visor-ui",
"description": "Orion Visor UI",
"version": "2.3.7",
"version": "2.3.9",
"private": true,
"author": "Jiahang Li",
"license": "Apache 2.0",

View File

@@ -1,4 +1,4 @@
import type { HostSpecExtraModel } from './host-extra';
import type { HostExtraUpdateRequest, HostSpecExtraModel } from './host-extra';
import type { TableData } from '@arco-design/web-vue';
import type { DataGrid, OrderDirection, Pagination } from '@/types/global';
import axios from 'axios';
@@ -114,6 +114,13 @@ export function updateHost(request: HostUpdateRequest) {
return axios.put('/asset/host/update', request);
}
/**
* 复制主机
*/
export function copyHost(request: HostUpdateRequest) {
return axios.post('/asset/host/copy', request);
}
/**
* 通过 id 更新主机状态
*/
@@ -121,6 +128,13 @@ export function updateHostStatus(request: HostUpdateStatusRequest) {
return axios.put('/asset/host/update-status', request);
}
/**
* 修改主机规格信息
*/
export function updateHostSpec(request: Partial<HostExtraUpdateRequest>) {
return axios.put('/asset/host/update-spec', request);
}
/**
* 查询主机
*/

View File

@@ -23,6 +23,7 @@ export interface AppInfoResponse {
*/
export interface AppReleaseResponse {
tagName: string;
releaseTime: string;
body: string;
}

View File

@@ -215,8 +215,8 @@
import { triggerMouseEvent } from '@/utils/event';
import { openAppSettingKey, toggleDrawerMenuKey } from '@/types/symbol';
import { preferenceTipsKey } from './const';
import { REDIRECT_ROUTE_NAME, routerToTag } from '@/router/constants';
import { openNewRoute } from '@/router';
import { getRouteTag, openNewRoute } from '@/router';
import { REDIRECT_ROUTE_NAME } from '@/router/constants';
import { checkHasUnreadMessage } from '@/api/system/message';
import SystemMenuTree from '@/components/system/menu/tree/index.vue';
import MessageBox from '@/components/system/message-box/index.vue';
@@ -291,13 +291,13 @@
const reloadCurrent = async () => {
if (appStore.tabBar) {
// 重新加载 tab
const itemData = routerToTag(route);
tabBarStore.deleteCache(itemData);
const tag = getRouteTag(route);
tabBarStore.deleteCache(tag);
await router.push({
name: REDIRECT_ROUTE_NAME,
params: { path: route.fullPath },
});
tabBarStore.addCache(itemData.name);
tabBarStore.addCache(tag.name);
} else {
// 刷新页面
router.go(0);

View File

@@ -18,12 +18,16 @@
<script lang="ts" setup>
import type { RouteLocationNormalized } from 'vue-router';
import { useRouter } from 'vue-router';
import { computed, onUnmounted, ref, watch } from 'vue';
import { routerToTag } from '@/router/constants';
import { listenerRouteChange, removeRouteListener, } from '@/utils/route-listener';
import { getRouteTag, getRouteTitle } from '@/router';
import { listenerRouteChange, removeRouteListener } from '@/utils/route-listener';
import { useAppStore, useTabBarStore } from '@/store';
import qs from 'query-string';
import TabItem from './tab-item.vue';
const router = useRouter();
const appStore = useAppStore();
const tabBarStore = useTabBarStore();
@@ -35,27 +39,33 @@
return appStore.navbar ? 60 : 0;
});
watch(
() => appStore.navbar,
() => {
affixRef.value.updatePosition();
}
);
// 监听修改位置
watch(() => appStore.navbar, () => {
affixRef.value.updatePosition();
});
// 监听路由变化
listenerRouteChange((route: RouteLocationNormalized) => {
if (
!route.meta.noAffix &&
!tagList.value.some((tag) => tag.path === route.path)
) {
// 固定并且没有此 tab 则添加
tabBarStore.addTab(routerToTag(route), route.meta?.ignoreCache as unknown as boolean);
// 不固定
if (route.meta.noAffix) {
return;
}
const tag = tagList.value.find((tag) => tag.path === route.path);
if (tag) {
// 找到 更新信息
tag.fullPath = route.fullPath;
tag.query = qs.parseUrl(route.fullPath).query;
tag.title = getRouteTitle(route);
} else {
// 未找到 添加标签
tabBarStore.addTab(getRouteTag(route), route.meta?.ignoreCache as unknown as boolean);
}
}, true);
onUnmounted(() => {
removeRouteListener();
});
</script>
<style lang="less" scoped>

View File

@@ -60,7 +60,7 @@
position="left"
type="warning"
@ok="deleteNode(node.key)">
<span v-permission="['asset:host-group:delete']"
<span v-permission="['asset:host-group:update']"
class="tree-icon"
title="删除">
<icon-delete />

View File

@@ -1,6 +1,3 @@
import type { RouteLocationNormalized } from 'vue-router';
import type { TagProps } from '@/store/modules/tab-bar/types';
export const LOGIN_ROUTE_NAME = 'login';
export const REDIRECT_ROUTE_NAME = 'redirect';
@@ -36,18 +33,3 @@ export const STATUS_ROUTER_LIST = [
{ name: NOT_FOUND_ROUTER_NAME, children: [] },
{ name: FORBIDDEN_ROUTER_NAME, children: [] },
];
/**
* router 转 tag
*/
export const routerToTag = (route: RouteLocationNormalized): TagProps => {
const { name, meta, path, fullPath, query } = route;
return {
title: meta.locale || String(name),
name: String(name),
path,
fullPath,
query,
ignoreCache: meta.ignoreCache,
};
};

View File

@@ -1,5 +1,6 @@
import type { Router } from 'vue-router';
import { useMenuStore } from '@/store';
import { getRouteTitle } from '@/router';
import { NOT_FOUND_ROUTER_NAME, WHITE_ROUTER_LIST } from '../constants';
import NProgress from 'nprogress';
import usePermission from '@/hooks/permission';
@@ -30,10 +31,7 @@ export default function setupPermissionGuard(router: Router) {
next({ name: NOT_FOUND_ROUTER_NAME });
}
// 修改页面标题
const locale = to.meta?.locale;
if (locale) {
document.title = locale;
}
document.title = getRouteTitle(to);
NProgress.done();
});
}

View File

@@ -1,4 +1,5 @@
import type { RouteLocationRaw } from 'vue-router';
import type { RouteLocationNormalized, RouteLocationRaw } from 'vue-router';
import type { TagProps } from '@/store/modules/tab-bar/types';
import { createRouter, createWebHistory } from 'vue-router';
import { appRoutes } from './routes';
import { openWindow } from '@/utils';
@@ -37,4 +38,27 @@ export const openNewRoute = (route: RouteLocationRaw) => {
}
};
// route 转 tag
export const getRouteTag = (route: RouteLocationNormalized): TagProps => {
const { name, meta, path, fullPath, query } = route;
return {
title: getRouteTitle(route),
name: String(name),
path,
fullPath,
query,
ignoreCache: meta.ignoreCache,
};
};
// 获取 router title
export const getRouteTitle = (route: RouteLocationNormalized) => {
const { meta } = route;
// 如果 meta.localeTemplate 则根据路由生成
if (meta.localeTemplate) {
return meta?.localeTemplate(route) || meta.locale || '';
}
return meta.locale || '';
};
export default router;

View File

@@ -1,3 +1,4 @@
import type { RouteLocationNormalized } from 'vue-router';
import 'vue-router';
/**
@@ -21,5 +22,7 @@ declare module 'vue-router' {
newWindow?: boolean;
// 是否活跃
activeMenu?: string;
// 名称模板
localeTemplate?: (key: RouteLocationNormalized) => string;
}
}

View File

@@ -21,7 +21,7 @@
<host-form-info ref="infoRef"
class="form-panel"
@change-type="(ts: string[]) => types = ts"
@updated="updateHostInfo" />
@updated="onUpdateHostInfo" />
</a-tab-pane>
<!-- 规格配置 -->
<a-tab-pane v-permission="['asset:host:update']"
@@ -31,7 +31,7 @@
<host-form-spec v-if="hostId"
class="form-panel"
:hostId="hostId"
@updated="incrUpdatedCount" />
@updated="onUpdateHostSpec" />
</a-tab-pane>
<!-- SSH 配置 -->
<a-tab-pane v-permission="['asset:host:update-config']"
@@ -59,12 +59,10 @@
import { Message } from '@arco-design/web-vue';
import { useCacheStore } from '@/store';
import { HostType } from '../types/const';
import { useCounter } from '@vueuse/core';
import HostFormInfo from './host-form-info.vue';
import HostFormSsh from './host-form-ssh.vue';
import HostFormSpec from './host-form-spec.vue';
import HostFormSsh from './host-form-ssh.vue';
const { count: updatedCount, inc: incrUpdatedCount, reset: resetCounter } = useCounter();
const { visible, setVisible } = useVisible();
const activeTab = ref<string>('info');
@@ -72,6 +70,7 @@
const hostId = ref<number>();
const types = ref<string[]>([]);
const infoRef = ref();
const hostViewUpdated = ref(false);
const emits = defineEmits(['reload']);
@@ -93,7 +92,7 @@
// 打开复制
const openCopy = (id: number) => {
init('复制主机', id);
init('复制主机', undefined);
nextTick(() => {
infoRef.value.openCopy(id);
});
@@ -104,9 +103,9 @@
title.value = _title;
activeTab.value = 'info';
hostId.value = id;
hostViewUpdated.value = false;
types.value = [];
checkHostGroup();
resetCounter();
setVisible(true);
};
@@ -123,14 +122,20 @@
defineExpose({ openAdd, openUpdate, openCopy });
// 更新主机信息
const updateHostInfo = (id: number) => {
const onUpdateHostInfo = (id: number) => {
hostId.value = id;
incrUpdatedCount();
hostViewUpdated.value = true;
};
// 更新主机信息
const onUpdateHostSpec = () => {
hostViewUpdated.value = true;
};
// 处理关闭
const handleClose = () => {
if (updatedCount.value) {
// 修改主机视图信息后刷新列表
if (hostViewUpdated.value) {
emits('reload');
}
};

View File

@@ -5,28 +5,6 @@
label-align="right"
:auto-label-width="true"
:rules="hostFormRules">
<!-- 主机协议 -->
<a-form-item field="types" label="主机协议">
<a-select v-model="formModel.types"
placeholder="请选择支持的主机协议"
:options="toOptions(hostTypeKey)"
multiple
allow-clear />
</a-form-item>
<!-- 系统类型 -->
<a-form-item field="osType" label="系统类型">
<a-select v-model="formModel.osType"
placeholder="请选择系统类型"
:options="toOptions(hostOsTypeKey)"
allow-clear />
</a-form-item>
<!-- 系统架构 -->
<a-form-item field="archType" label="系统架构">
<a-select v-model="formModel.archType"
placeholder="请选择系统架构"
:options="toOptions(hostArchTypeKey)"
allow-clear />
</a-form-item>
<!-- 主机名称 -->
<a-form-item field="name" label="主机名称">
<a-input v-model="formModel.name"
@@ -45,6 +23,28 @@
placeholder="请输入主机地址"
allow-clear />
</a-form-item>
<!-- 系统类型 -->
<a-form-item field="osType" label="系统类型">
<a-select v-model="formModel.osType"
placeholder="请选择系统类型"
:options="toOptions(hostOsTypeKey)"
allow-clear />
</a-form-item>
<!-- 系统架构 -->
<a-form-item field="archType" label="系统架构">
<a-select v-model="formModel.archType"
placeholder="请选择系统架构"
:options="toOptions(hostArchTypeKey)"
allow-clear />
</a-form-item>
<!-- 主机协议 -->
<a-form-item field="types" label="主机协议">
<a-select v-model="formModel.types"
placeholder="请选择支持的主机协议"
:options="toOptions(hostTypeKey)"
multiple
allow-clear />
</a-form-item>
<!-- 主机分组 -->
<a-form-item field="groupIdList" label="主机分组">
<host-group-tree-selector v-model="formModel.groupIdList"
@@ -90,7 +90,7 @@
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import { hostFormRules } from '../types/form.rules';
import { createHost, getHost, updateHost } from '@/api/asset/host';
import { createHost, getHost, updateHost, copyHost } from '@/api/asset/host';
import { Message } from '@arco-design/web-vue';
import { pick } from 'lodash';
import { tagColor, hostTypeKey, hostOsTypeKey, HostOsType, hostArchTypeKey } from '../types/const';
@@ -118,6 +118,7 @@
};
};
const isCopy = ref(false);
const formRef = ref();
const formModel = ref<HostUpdateRequest>({});
@@ -135,6 +136,7 @@
// 打开复制
const openCopy = async (id: number) => {
renderForm({ ...defaultForm() });
isCopy.value = true;
await fetchHostRender(id);
};
@@ -188,7 +190,12 @@
if (error) {
return;
}
if (!formModel.value.id) {
if (isCopy.value) {
// 复制
const { data } = await copyHost(formModel.value);
Message.success('复制成功');
emits('updated', data);
} else if (!formModel.value.id) {
// 新增
const { data } = await createHost(formModel.value);
Message.success('创建成功');

View File

@@ -252,10 +252,11 @@
<script lang="ts" setup>
import type { HostSpecExtraModel } from '@/api/asset/host-extra';
import { onMounted, ref } from 'vue';
import { updateHostSpec } from '@/api/asset/host';
import { getHostExtraItem } from '@/api/asset/host-extra';
import { addSuffix, dateFormat } from '@/utils';
import { useToggle } from '@vueuse/core';
import useLoading from '@/hooks/loading';
import { getHostExtraItem, updateHostExtra } from '@/api/asset/host-extra';
const props = defineProps<{
hostId: number;
@@ -297,9 +298,8 @@
const saveSpec = async () => {
setLoading(true);
try {
await updateHostExtra({
await updateHostSpec({
hostId: props.hostId,
item: 'SPEC',
extra: JSON.stringify(formModel.value)
});
toggleEditing();

View File

@@ -14,7 +14,7 @@ const columns = [
title: '主机信息',
dataIndex: 'hostInfo',
slotName: 'hostInfo',
minWidth: 328,
width: 288,
align: 'left',
fixed: 'left',
default: true,
@@ -22,7 +22,7 @@ const columns = [
title: '主机规格',
dataIndex: 'hostSpec',
slotName: 'hostSpec',
width: 188,
width: 198,
align: 'left',
default: true,
}, {

View File

@@ -42,7 +42,7 @@
<script lang="ts" setup>
import type { ISshSession, TerminalPanelTabItem } from '../../types/define';
import { onMounted, onUnmounted, ref } from 'vue';
import { useDictStore, useTerminalStore } from '@/store';
import { useTerminalStore } from '@/store';
import SshHeader from './ssh-header.vue';
import ShellEditorModal from '@/components/view/shell-editor/modal/index.vue';
import SshContextMenu from './ssh-context-menu.vue';
@@ -53,7 +53,6 @@
tab: TerminalPanelTabItem;
}>();
const { getDictValue } = useDictStore();
const { preference, sessionManager } = useTerminalStore();
const editorModal = ref();

View File

@@ -32,7 +32,11 @@
<b v-if="app.version && repo.tagName && ('v' + app.version) !== repo.tagName"
class="span-green ml8">新版本已发布, 请及时升级版本</b>
</a-descriptions-item>
<!-- 当前后端版本 -->
<!-- 最近更新时间 -->
<a-descriptions-item label="最近更新时间">
<span>{{ repo.releaseTime }}</span>
</a-descriptions-item>
<!-- 最新更新日志 -->
<a-descriptions-item label="最新更新日志">
<a-textarea v-model="repo.body"
:auto-size="{ minRows: 3, maxRows: 16 }"
@@ -51,22 +55,23 @@
<script lang="ts" setup>
import type { AppInfoResponse, AppReleaseResponse } from '@/api/system/setting';
import { onMounted, reactive } from 'vue';
import { getAppLatestRelease, getSystemAppInfo } from '@/api/system/setting';
import { onMounted, ref } from 'vue';
import { copy } from '@/hooks/copy';
import useLoading from '@/hooks/loading';
import { getAppLatestRelease, getSystemAppInfo } from '@/api/system/setting';
const { loading, setLoading } = useLoading();
const webVersion = import.meta.env.VITE_APP_VERSION;
const app = reactive<AppInfoResponse>({
const app = ref<AppInfoResponse>({
version: '',
uuid: '',
});
const repo = reactive<AppReleaseResponse>({
const repo = ref<AppReleaseResponse>({
tagName: '',
releaseTime: '',
body: '',
});
@@ -75,8 +80,7 @@
setLoading(true);
try {
const { data } = await getSystemAppInfo();
app.version = data.version;
app.uuid = data.uuid;
app.value = data;
} catch (e) {
} finally {
setLoading(false);
@@ -87,8 +91,7 @@
onMounted(async () => {
try {
const { data } = await getAppLatestRelease();
repo.tagName = data.tagName;
repo.body = data.body;
repo.value = data;
} catch (e) {
}
});

View File

@@ -22,7 +22,7 @@
</modules>
<properties>
<revision>2.3.7</revision>
<revision>2.3.9</revision>
<maven.compiler.source>8</maven.compiler.source>
<maven.compiler.target>8</maven.compiler.target>
<maven.surefire.plugin.version>3.0.0-M5</maven.surefire.plugin.version>

View File

@@ -414,14 +414,13 @@ DROP TABLE IF EXISTS `host`;
CREATE TABLE `host`
(
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
`type` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主机类型',
`types` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主机类型',
`os_type` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '系统类型',
`arch_type` char(12) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '系统架构',
`name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主机名称',
`code` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主机编码',
`address` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主机地址',
`port` int(0) NULL DEFAULT NULL COMMENT '主机端口',
`status` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主机状态',
`config` json NULL COMMENT '主机配置',
`description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '主机描述',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
@@ -429,13 +428,37 @@ CREATE TABLE `host`
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新人',
`deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除 0未删除 1已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `idx_type_address` (`type`, `address`) USING BTREE
INDEX `idx_name` (`name`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT = '主机'
ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for host_config
-- ----------------------------
DROP TABLE IF EXISTS `host_config`;
CREATE TABLE `host_config`
(
`id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT 'id',
`host_id` bigint(0) NULL DEFAULT NULL COMMENT '主机id',
`type` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '配置类型',
`status` char(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '配置状态',
`config` json NULL COMMENT '配置值',
`create_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime(0) NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP(0) COMMENT '修改时间',
`creator` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '创建人',
`updater` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci NULL DEFAULT NULL COMMENT '更新人',
`deleted` tinyint(1) NULL DEFAULT 0 COMMENT '是否删除 0未删除 1已删除',
PRIMARY KEY (`id`) USING BTREE,
INDEX `host_type_idx` (`host_id`, `type`) USING BTREE
) ENGINE = InnoDB
AUTO_INCREMENT = 1
CHARACTER SET = utf8mb4
COLLATE = utf8mb4_unicode_ci COMMENT = '主机配置表'
ROW_FORMAT = Dynamic;
-- ----------------------------
-- Table structure for host_identity
-- ----------------------------

View File

@@ -349,6 +349,7 @@ INSERT INTO `dict_value` VALUES (443, 21, 'terminalFontFamily', 'Cascadia Mono',
INSERT INTO `dict_value` VALUES (448, 65, 'hostArchType', 'AMD64', 'amd64', '{}', 10, '2025-03-26 16:33:07', '2025-03-26 16:33:07', 'admin', 'admin', 0);
INSERT INTO `dict_value` VALUES (449, 65, 'hostArchType', 'ARM64', 'arm64', '{}', 20, '2025-03-26 16:33:23', '2025-03-26 16:33:23', 'admin', 'admin', 0);
INSERT INTO `dict_value` VALUES (452, 38, 'hostOsType', 'DRAWIN', 'Drawin', '{}', 30, '2024-04-16 22:19:25', '2025-03-26 22:46:23', 'admin', 'admin', 0);
INSERT INTO `dict_value` VALUES (458, 2, 'operatorLogType', 'host:update-spec', '修改主机规格', '{}', 60, '2025-04-06 12:02:13', '2025-04-06 12:02:13', 'admin', 'admin', 0);
-- 菜单配置
INSERT INTO `system_menu` VALUES (1, 0, '工作台', NULL, 1, 10, 1, 1, 1, 0, 'IconComputer', NULL, 'workplace', '2023-07-28 10:51:50', '2024-08-11 00:05:44', 'admin', 'admin', 0);